@jungjaehoon/mama-core 1.4.1 → 1.5.0
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/db/migrations/026-create-canonical-entity-tables.sql +99 -0
- package/db/migrations/027-create-entity-review-support.sql +44 -0
- package/db/migrations/028-create-entity-audit-tables.sql +45 -0
- package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
- package/db/migrations/030-case-first-memory-substrate.sql +901 -0
- package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
- package/dist/canonicalize.d.ts +38 -0
- package/dist/canonicalize.js +106 -0
- package/dist/cases/case-links.d.ts +116 -0
- package/dist/cases/case-links.js +794 -0
- package/dist/cases/composition-overrides.d.ts +90 -0
- package/dist/cases/composition-overrides.js +724 -0
- package/dist/cases/corrections.d.ts +156 -0
- package/dist/cases/corrections.js +1045 -0
- package/dist/cases/freshness.d.ts +57 -0
- package/dist/cases/freshness.js +283 -0
- package/dist/cases/live-state.d.ts +48 -0
- package/dist/cases/live-state.js +256 -0
- package/dist/cases/membership-explain.d.ts +56 -0
- package/dist/cases/membership-explain.js +140 -0
- package/dist/cases/membership-matcher.d.ts +23 -0
- package/dist/cases/membership-matcher.js +193 -0
- package/dist/cases/merge-split.d.ts +61 -0
- package/dist/cases/merge-split.js +368 -0
- package/dist/cases/role-inference.d.ts +10 -0
- package/dist/cases/role-inference.js +85 -0
- package/dist/cases/search-rollup.d.ts +23 -0
- package/dist/cases/search-rollup.js +225 -0
- package/dist/cases/sqlite-transaction.d.ts +10 -0
- package/dist/cases/sqlite-transaction.js +62 -0
- package/dist/cases/store.d.ts +74 -0
- package/dist/cases/store.js +816 -0
- package/dist/cases/target-ref.d.ts +25 -0
- package/dist/cases/target-ref.js +22 -0
- package/dist/cases/timeline-range.d.ts +6 -0
- package/dist/cases/timeline-range.js +322 -0
- package/dist/cases/tombstone-sweeper.d.ts +13 -0
- package/dist/cases/tombstone-sweeper.js +128 -0
- package/dist/cases/types.d.ts +244 -0
- package/dist/cases/types.js +47 -0
- package/dist/cases/wiki-page-index.d.ts +50 -0
- package/dist/cases/wiki-page-index.js +350 -0
- package/dist/config-loader.d.ts +1 -0
- package/dist/config-loader.js +43 -0
- package/dist/connectors/event-index.d.ts +44 -0
- package/dist/connectors/event-index.js +366 -0
- package/dist/connectors/types.d.ts +69 -0
- package/dist/connectors/types.js +3 -0
- package/dist/db-manager.d.ts +4 -0
- package/dist/db-manager.js +10 -3
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/decision-formatter.js +11 -8
- package/dist/embedding-server/mobile/daemon.d.ts +1 -0
- package/dist/embedding-server/mobile/daemon.js +11 -1
- package/dist/entities/audit-metrics.d.ts +73 -0
- package/dist/entities/audit-metrics.js +128 -0
- package/dist/entities/candidate-generator.d.ts +9 -0
- package/dist/entities/candidate-generator.js +436 -0
- package/dist/entities/entity-impact.d.ts +41 -0
- package/dist/entities/entity-impact.js +140 -0
- package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
- package/dist/entities/entity-linked-decision-counts.js +29 -0
- package/dist/entities/entity-list.d.ts +23 -0
- package/dist/entities/entity-list.js +99 -0
- package/dist/entities/entity-orphan-list.d.ts +26 -0
- package/dist/entities/entity-orphan-list.js +91 -0
- package/dist/entities/entity-search.d.ts +21 -0
- package/dist/entities/entity-search.js +150 -0
- package/dist/entities/errors.d.ts +65 -0
- package/dist/entities/errors.js +124 -0
- package/dist/entities/exact-merge-backfill.d.ts +11 -0
- package/dist/entities/exact-merge-backfill.js +161 -0
- package/dist/entities/lineage-backfill.d.ts +12 -0
- package/dist/entities/lineage-backfill.js +101 -0
- package/dist/entities/lineage-store.d.ts +51 -0
- package/dist/entities/lineage-store.js +308 -0
- package/dist/entities/normalization.d.ts +14 -0
- package/dist/entities/normalization.js +63 -0
- package/dist/entities/policy-store.d.ts +17 -0
- package/dist/entities/policy-store.js +263 -0
- package/dist/entities/policy-types.d.ts +72 -0
- package/dist/entities/policy-types.js +59 -0
- package/dist/entities/projection.d.ts +8 -0
- package/dist/entities/projection.js +82 -0
- package/dist/entities/provenance-query.d.ts +33 -0
- package/dist/entities/provenance-query.js +167 -0
- package/dist/entities/read-identity.d.ts +31 -0
- package/dist/entities/read-identity.js +264 -0
- package/dist/entities/recall-bridge.d.ts +5 -0
- package/dist/entities/recall-bridge.js +171 -0
- package/dist/entities/resolution-engine.d.ts +15 -0
- package/dist/entities/resolution-engine.js +69 -0
- package/dist/entities/rollback-preview.d.ts +39 -0
- package/dist/entities/rollback-preview.js +235 -0
- package/dist/entities/source-locator.d.ts +3 -0
- package/dist/entities/source-locator.js +32 -0
- package/dist/entities/store.d.ts +75 -0
- package/dist/entities/store.js +465 -0
- package/dist/entities/types.d.ts +162 -0
- package/dist/entities/types.js +30 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +66 -25
- package/dist/mama-api.d.ts +22 -1
- package/dist/mama-api.js +272 -64
- package/dist/memory/api.d.ts +28 -1
- package/dist/memory/api.js +293 -31
- package/dist/memory/types.d.ts +11 -2
- package/dist/search/feedback-store.d.ts +59 -0
- package/dist/search/feedback-store.js +211 -0
- package/dist/search/question-type.d.ts +5 -0
- package/dist/search/question-type.js +41 -0
- package/dist/search/ranker-features.d.ts +23 -0
- package/dist/search/ranker-features.js +104 -0
- package/dist/search/ranker-rescore.d.ts +31 -0
- package/dist/search/ranker-rescore.js +226 -0
- package/dist/search/ranker-trainer.d.ts +94 -0
- package/dist/search/ranker-trainer.js +860 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +52 -0
- package/package.json +21 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listCanonicalEntities = listCanonicalEntities;
|
|
4
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
5
|
+
const entity_linked_decision_counts_js_1 = require("./entity-linked-decision-counts.js");
|
|
6
|
+
function normalizeBrowseLabel(input) {
|
|
7
|
+
return input.normalize('NFKC').trim().replace(/\s+/gu, ' ').toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function buildBrowseCollapseKey(row) {
|
|
10
|
+
return JSON.stringify([
|
|
11
|
+
row.kind,
|
|
12
|
+
row.scope_kind,
|
|
13
|
+
row.scope_id ?? '',
|
|
14
|
+
normalizeBrowseLabel(row.preferred_label),
|
|
15
|
+
]);
|
|
16
|
+
}
|
|
17
|
+
function compareCanonicalRows(left, right) {
|
|
18
|
+
if (right.created_at !== left.created_at) {
|
|
19
|
+
return right.created_at - left.created_at;
|
|
20
|
+
}
|
|
21
|
+
const labelCompare = left.preferred_label.localeCompare(right.preferred_label);
|
|
22
|
+
if (labelCompare !== 0) {
|
|
23
|
+
return labelCompare;
|
|
24
|
+
}
|
|
25
|
+
return left.id.localeCompare(right.id);
|
|
26
|
+
}
|
|
27
|
+
function decodeCursor(cursor) {
|
|
28
|
+
if (!cursor) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const decoded = Number(Buffer.from(cursor, 'base64').toString('utf8'));
|
|
33
|
+
return Number.isFinite(decoded) && decoded >= 0 ? decoded : 0;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function encodeCursor(offset) {
|
|
40
|
+
return Buffer.from(String(offset), 'utf8').toString('base64');
|
|
41
|
+
}
|
|
42
|
+
function isNoisyCanonicalEntity(row) {
|
|
43
|
+
const label = row.preferred_label.trim().toLowerCase();
|
|
44
|
+
if (row.kind === 'person') {
|
|
45
|
+
return label === 'user' || label === 'claude';
|
|
46
|
+
}
|
|
47
|
+
if (row.kind === 'project') {
|
|
48
|
+
return label === 'workspace';
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
async function listCanonicalEntities(input) {
|
|
53
|
+
await (0, db_manager_js_1.initDB)();
|
|
54
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
55
|
+
const rows = adapter
|
|
56
|
+
.prepare(`
|
|
57
|
+
SELECT id, kind, preferred_label, scope_kind, scope_id, created_at
|
|
58
|
+
FROM entity_nodes
|
|
59
|
+
WHERE status = 'active'
|
|
60
|
+
AND merged_into IS NULL
|
|
61
|
+
`)
|
|
62
|
+
.all();
|
|
63
|
+
const sorted = rows.sort(compareCanonicalRows);
|
|
64
|
+
const collapsedByKey = new Map();
|
|
65
|
+
for (const row of sorted) {
|
|
66
|
+
const key = buildBrowseCollapseKey(row);
|
|
67
|
+
const existing = collapsedByKey.get(key);
|
|
68
|
+
if (!existing) {
|
|
69
|
+
collapsedByKey.set(key, row);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (row.created_at > existing.created_at ||
|
|
73
|
+
(row.created_at === existing.created_at && row.id.localeCompare(existing.id) < 0)) {
|
|
74
|
+
collapsedByKey.set(key, row);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const collapsed = Array.from(collapsedByKey.values()).sort(compareCanonicalRows);
|
|
78
|
+
const totalCount = sorted.length;
|
|
79
|
+
const visibleBase = input.include_noisy === false
|
|
80
|
+
? collapsed.filter((row) => !isNoisyCanonicalEntity(row))
|
|
81
|
+
: collapsed;
|
|
82
|
+
const offset = decodeCursor(input.cursor);
|
|
83
|
+
const limit = Math.max(1, input.limit);
|
|
84
|
+
const page = visibleBase.slice(offset, offset + limit);
|
|
85
|
+
const linkedCounts = (0, entity_linked_decision_counts_js_1.loadLinkedDecisionCounts)(adapter, page.map((row) => row.id));
|
|
86
|
+
const entities = page.map((row) => ({
|
|
87
|
+
...row,
|
|
88
|
+
linked_decision_count: linkedCounts.get(row.id) ?? 0,
|
|
89
|
+
}));
|
|
90
|
+
const visibleCount = visibleBase.length;
|
|
91
|
+
const nextCursor = offset + limit < visibleBase.length ? encodeCursor(offset + limit) : null;
|
|
92
|
+
return {
|
|
93
|
+
entities,
|
|
94
|
+
next_cursor: nextCursor,
|
|
95
|
+
total_count: totalCount,
|
|
96
|
+
visible_count: visibleCount,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=entity-list.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { EntityNode } from './types.js';
|
|
2
|
+
export interface CanonicalEntityOrphanRow {
|
|
3
|
+
entity_id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
kind: EntityNode['kind'];
|
|
6
|
+
scope_kind: EntityNode['scope_kind'];
|
|
7
|
+
scope_id: string | null;
|
|
8
|
+
scope_label: string;
|
|
9
|
+
created_at: number;
|
|
10
|
+
linked_decision_count: number;
|
|
11
|
+
evidence_summary: {
|
|
12
|
+
lineage_rows: number;
|
|
13
|
+
raw_evidence_rows: number;
|
|
14
|
+
last_seen_at: number | null;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface ReadAdapter {
|
|
18
|
+
prepare(sql: string): {
|
|
19
|
+
all: (...params: unknown[]) => unknown[];
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function listCanonicalEntityOrphans(adapter?: ReadAdapter, options?: {
|
|
23
|
+
min_age_ms?: number | null;
|
|
24
|
+
}): CanonicalEntityOrphanRow[];
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=entity-orphan-list.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listCanonicalEntityOrphans = listCanonicalEntityOrphans;
|
|
4
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
5
|
+
const entity_linked_decision_counts_js_1 = require("./entity-linked-decision-counts.js");
|
|
6
|
+
function buildScopeLabel(scopeKind, scopeId) {
|
|
7
|
+
return scopeKind ? `${scopeKind}:${scopeId ?? ''}` : 'global';
|
|
8
|
+
}
|
|
9
|
+
function isNoisyCanonicalEntity(row) {
|
|
10
|
+
const label = row.title.trim().toLowerCase();
|
|
11
|
+
if (row.kind === 'person') {
|
|
12
|
+
return label === 'user' || label === 'claude';
|
|
13
|
+
}
|
|
14
|
+
if (row.kind === 'project') {
|
|
15
|
+
return label === 'workspace';
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function compareOrphanRows(left, right) {
|
|
20
|
+
const leftSeen = typeof left.evidence_summary.last_seen_at === 'number' ? left.evidence_summary.last_seen_at : 0;
|
|
21
|
+
const rightSeen = typeof right.evidence_summary.last_seen_at === 'number'
|
|
22
|
+
? right.evidence_summary.last_seen_at
|
|
23
|
+
: 0;
|
|
24
|
+
if (rightSeen !== leftSeen) {
|
|
25
|
+
return rightSeen - leftSeen;
|
|
26
|
+
}
|
|
27
|
+
return left.entity_id.localeCompare(right.entity_id);
|
|
28
|
+
}
|
|
29
|
+
function listCanonicalEntityOrphans(adapter = (0, db_manager_js_1.getAdapter)(), options = {}) {
|
|
30
|
+
const minAgeMs = typeof options.min_age_ms === 'number' && Number.isFinite(options.min_age_ms)
|
|
31
|
+
? Math.max(0, options.min_age_ms)
|
|
32
|
+
: 0;
|
|
33
|
+
const minCreatedAt = Date.now() - minAgeMs;
|
|
34
|
+
const rows = adapter
|
|
35
|
+
.prepare(`
|
|
36
|
+
SELECT
|
|
37
|
+
n.id AS entity_id,
|
|
38
|
+
n.kind AS kind,
|
|
39
|
+
n.preferred_label AS title,
|
|
40
|
+
n.scope_kind AS scope_kind,
|
|
41
|
+
n.scope_id AS scope_id,
|
|
42
|
+
n.created_at AS created_at,
|
|
43
|
+
COUNT(DISTINCT l.entity_observation_id) AS lineage_rows,
|
|
44
|
+
COUNT(
|
|
45
|
+
DISTINCT CASE
|
|
46
|
+
WHEN COALESCE(o.source_raw_record_id, '') <> '' THEN l.entity_observation_id
|
|
47
|
+
ELSE NULL
|
|
48
|
+
END
|
|
49
|
+
) AS raw_evidence_rows,
|
|
50
|
+
MAX(COALESCE(o.timestamp_observed, o.created_at)) AS last_seen_at
|
|
51
|
+
FROM entity_nodes n
|
|
52
|
+
INNER JOIN entity_lineage_links l
|
|
53
|
+
ON l.canonical_entity_id = n.id
|
|
54
|
+
AND l.status = 'active'
|
|
55
|
+
INNER JOIN entity_observations o
|
|
56
|
+
ON o.id = l.entity_observation_id
|
|
57
|
+
WHERE n.status = 'active'
|
|
58
|
+
AND n.merged_into IS NULL
|
|
59
|
+
AND n.created_at <= ?
|
|
60
|
+
GROUP BY n.id, n.kind, n.preferred_label, n.scope_kind, n.scope_id, n.created_at
|
|
61
|
+
`)
|
|
62
|
+
.all(minCreatedAt);
|
|
63
|
+
const orphanRows = rows.map((row) => ({
|
|
64
|
+
entity_id: row.entity_id,
|
|
65
|
+
title: row.title,
|
|
66
|
+
kind: row.kind,
|
|
67
|
+
scope_kind: row.scope_kind,
|
|
68
|
+
scope_id: row.scope_id,
|
|
69
|
+
scope_label: buildScopeLabel(row.scope_kind, row.scope_id),
|
|
70
|
+
created_at: Number(row.created_at),
|
|
71
|
+
linked_decision_count: 0,
|
|
72
|
+
evidence_summary: {
|
|
73
|
+
lineage_rows: Number(row.lineage_rows ?? 0),
|
|
74
|
+
raw_evidence_rows: Number(row.raw_evidence_rows ?? 0),
|
|
75
|
+
last_seen_at: row.last_seen_at === null || row.last_seen_at === undefined
|
|
76
|
+
? null
|
|
77
|
+
: Number(row.last_seen_at),
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
const linkedCounts = (0, entity_linked_decision_counts_js_1.loadLinkedDecisionCounts)(adapter, orphanRows.map((row) => row.entity_id));
|
|
81
|
+
return orphanRows
|
|
82
|
+
.map((row) => ({
|
|
83
|
+
...row,
|
|
84
|
+
linked_decision_count: linkedCounts.get(row.entity_id) ?? 0,
|
|
85
|
+
}))
|
|
86
|
+
.filter((row) => row.evidence_summary.lineage_rows > 0 &&
|
|
87
|
+
row.linked_decision_count === 0 &&
|
|
88
|
+
!isNoisyCanonicalEntity(row))
|
|
89
|
+
.sort(compareOrphanRows);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=entity-orphan-list.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EntityNode } from './types.js';
|
|
2
|
+
export interface SearchCanonicalEntitiesInput {
|
|
3
|
+
query: string;
|
|
4
|
+
limit: number;
|
|
5
|
+
cursor?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface EntitySearchRow {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: EntityNode['kind'];
|
|
10
|
+
preferred_label: string;
|
|
11
|
+
scope_kind: EntityNode['scope_kind'];
|
|
12
|
+
scope_id: string | null;
|
|
13
|
+
score: number;
|
|
14
|
+
linked_decision_count: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SearchCanonicalEntitiesResult {
|
|
17
|
+
entities: EntitySearchRow[];
|
|
18
|
+
next_cursor: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function searchCanonicalEntities(input: SearchCanonicalEntitiesInput): Promise<SearchCanonicalEntitiesResult>;
|
|
21
|
+
//# sourceMappingURL=entity-search.d.ts.map
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchCanonicalEntities = searchCanonicalEntities;
|
|
4
|
+
const db_manager_js_1 = require("../db-manager.js");
|
|
5
|
+
const entity_linked_decision_counts_js_1 = require("./entity-linked-decision-counts.js");
|
|
6
|
+
const store_js_1 = require("./store.js");
|
|
7
|
+
function normalizeSearchLabel(input) {
|
|
8
|
+
return input.normalize('NFKC').trim().replace(/\s+/gu, ' ').toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
function buildSearchCollapseKey(row) {
|
|
11
|
+
return JSON.stringify([
|
|
12
|
+
row.kind,
|
|
13
|
+
row.scope_kind,
|
|
14
|
+
row.scope_id ?? '',
|
|
15
|
+
normalizeSearchLabel(row.preferred_label),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
function escapeLikeQuery(query) {
|
|
19
|
+
return query.replace(/[\\%_]/g, '\\$&');
|
|
20
|
+
}
|
|
21
|
+
function decodeCursor(cursor) {
|
|
22
|
+
if (!cursor) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const decoded = Number(Buffer.from(cursor, 'base64').toString('utf8'));
|
|
27
|
+
return Number.isFinite(decoded) && decoded >= 0 ? decoded : 0;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function encodeCursor(offset) {
|
|
34
|
+
return Buffer.from(String(offset), 'utf8').toString('base64');
|
|
35
|
+
}
|
|
36
|
+
async function searchCanonicalEntities(input) {
|
|
37
|
+
await (0, db_manager_js_1.initDB)();
|
|
38
|
+
const adapter = (0, db_manager_js_1.getAdapter)();
|
|
39
|
+
const query = input.query.trim();
|
|
40
|
+
if (!query) {
|
|
41
|
+
return { entities: [], next_cursor: null };
|
|
42
|
+
}
|
|
43
|
+
const escaped = escapeLikeQuery(query);
|
|
44
|
+
const normalizedQuery = normalizeSearchLabel(query);
|
|
45
|
+
const rows = adapter
|
|
46
|
+
.prepare(`
|
|
47
|
+
SELECT
|
|
48
|
+
n.id AS matched_id,
|
|
49
|
+
n.preferred_label,
|
|
50
|
+
n.kind,
|
|
51
|
+
n.scope_kind,
|
|
52
|
+
n.scope_id,
|
|
53
|
+
a.label AS alias_label,
|
|
54
|
+
o.surface_form AS observation_surface_form
|
|
55
|
+
FROM entity_nodes n
|
|
56
|
+
LEFT JOIN entity_aliases a ON a.entity_id = n.id
|
|
57
|
+
LEFT JOIN entity_lineage_links l ON l.canonical_entity_id = n.id AND l.status = 'active'
|
|
58
|
+
LEFT JOIN entity_observations o ON o.id = l.entity_observation_id
|
|
59
|
+
WHERE (
|
|
60
|
+
lower(n.preferred_label) LIKE '%' || lower(?) || '%' ESCAPE '\\'
|
|
61
|
+
OR lower(COALESCE(a.label, '')) LIKE '%' || lower(?) || '%' ESCAPE '\\'
|
|
62
|
+
OR lower(COALESCE(o.surface_form, '')) LIKE '%' || lower(?) || '%' ESCAPE '\\'
|
|
63
|
+
)
|
|
64
|
+
`)
|
|
65
|
+
.all(escaped, escaped, escaped);
|
|
66
|
+
const canonicalIdByMatchedId = new Map();
|
|
67
|
+
for (const matchedId of new Set(rows.map((row) => row.matched_id))) {
|
|
68
|
+
try {
|
|
69
|
+
canonicalIdByMatchedId.set(matchedId, (0, store_js_1.resolveCanonicalEntityId)(adapter, matchedId));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof store_js_1.EntityMergeError) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const canonicalIds = Array.from(new Set(canonicalIdByMatchedId.values()));
|
|
79
|
+
const canonicalLookup = new Map();
|
|
80
|
+
if (canonicalIds.length > 0) {
|
|
81
|
+
const fetched = adapter
|
|
82
|
+
.prepare(`
|
|
83
|
+
SELECT id, kind, preferred_label, scope_kind, scope_id
|
|
84
|
+
FROM entity_nodes
|
|
85
|
+
WHERE id IN (${canonicalIds.map(() => '?').join(', ')})
|
|
86
|
+
AND status = 'active'
|
|
87
|
+
AND merged_into IS NULL
|
|
88
|
+
`)
|
|
89
|
+
.all(...canonicalIds);
|
|
90
|
+
for (const row of fetched) {
|
|
91
|
+
canonicalLookup.set(row.id, row);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const canonicalRows = new Map();
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
const canonicalId = canonicalIdByMatchedId.get(row.matched_id);
|
|
97
|
+
const canonical = canonicalId ? canonicalLookup.get(canonicalId) : undefined;
|
|
98
|
+
if (!canonical) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const score = normalizeSearchLabel(row.preferred_label).includes(normalizedQuery)
|
|
102
|
+
? 3
|
|
103
|
+
: row.alias_label && normalizeSearchLabel(row.alias_label).includes(normalizedQuery)
|
|
104
|
+
? 2
|
|
105
|
+
: 1;
|
|
106
|
+
const existing = canonicalRows.get(canonical.id);
|
|
107
|
+
if (!existing || score > existing.score) {
|
|
108
|
+
canonicalRows.set(canonical.id, {
|
|
109
|
+
id: canonical.id,
|
|
110
|
+
kind: canonical.kind,
|
|
111
|
+
preferred_label: canonical.preferred_label,
|
|
112
|
+
scope_kind: canonical.scope_kind,
|
|
113
|
+
scope_id: canonical.scope_id,
|
|
114
|
+
score,
|
|
115
|
+
linked_decision_count: 0,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const collapsedRows = new Map();
|
|
120
|
+
for (const row of canonicalRows.values()) {
|
|
121
|
+
const key = buildSearchCollapseKey(row);
|
|
122
|
+
const existing = collapsedRows.get(key);
|
|
123
|
+
if (!existing ||
|
|
124
|
+
row.score > existing.score ||
|
|
125
|
+
(row.score === existing.score && row.id.localeCompare(existing.id) < 0)) {
|
|
126
|
+
collapsedRows.set(key, row);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const sorted = Array.from(collapsedRows.values()).sort((left, right) => {
|
|
130
|
+
if (right.score !== left.score) {
|
|
131
|
+
return right.score - left.score;
|
|
132
|
+
}
|
|
133
|
+
const labelCompare = left.preferred_label.localeCompare(right.preferred_label);
|
|
134
|
+
if (labelCompare !== 0) {
|
|
135
|
+
return labelCompare;
|
|
136
|
+
}
|
|
137
|
+
return left.id.localeCompare(right.id);
|
|
138
|
+
});
|
|
139
|
+
const offset = decodeCursor(input.cursor);
|
|
140
|
+
const limit = Math.max(1, input.limit);
|
|
141
|
+
const page = sorted.slice(offset, offset + limit);
|
|
142
|
+
const linkedCounts = (0, entity_linked_decision_counts_js_1.loadLinkedDecisionCounts)(adapter, page.map((row) => row.id));
|
|
143
|
+
const entities = page.map((row) => ({
|
|
144
|
+
...row,
|
|
145
|
+
linked_decision_count: linkedCounts.get(row.id) ?? 0,
|
|
146
|
+
}));
|
|
147
|
+
const next_cursor = offset + limit < sorted.length ? encodeCursor(offset + limit) : null;
|
|
148
|
+
return { entities, next_cursor };
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=entity-search.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export interface EntityErrorEnvelope {
|
|
2
|
+
error: {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
hint: string;
|
|
6
|
+
doc_url: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare abstract class EntityError extends Error {
|
|
10
|
+
abstract readonly code: string;
|
|
11
|
+
abstract readonly doc_section: string;
|
|
12
|
+
readonly context: Record<string, unknown>;
|
|
13
|
+
readonly hint: string;
|
|
14
|
+
constructor(opts: {
|
|
15
|
+
message: string;
|
|
16
|
+
context?: Record<string, unknown>;
|
|
17
|
+
hint: string;
|
|
18
|
+
});
|
|
19
|
+
toErrorEnvelope(): EntityErrorEnvelope;
|
|
20
|
+
}
|
|
21
|
+
export declare class InvalidEntityLabelError extends EntityError {
|
|
22
|
+
readonly code = "entity.invalid_label";
|
|
23
|
+
readonly doc_section = "#invalid-label";
|
|
24
|
+
constructor(context?: Record<string, unknown>);
|
|
25
|
+
}
|
|
26
|
+
export declare class EmbeddingUnavailableError extends EntityError {
|
|
27
|
+
readonly code = "entity.embedding_unavailable";
|
|
28
|
+
readonly doc_section = "#embedding-unavailable";
|
|
29
|
+
constructor(context?: Record<string, unknown>);
|
|
30
|
+
}
|
|
31
|
+
export declare class OntologyViolationError extends EntityError {
|
|
32
|
+
readonly code = "entity.ontology_violation";
|
|
33
|
+
readonly doc_section = "#ontology-violation";
|
|
34
|
+
constructor(context?: Record<string, unknown>);
|
|
35
|
+
}
|
|
36
|
+
export declare class MergeTargetStaleError extends EntityError {
|
|
37
|
+
readonly code = "entity.merge_target_stale";
|
|
38
|
+
readonly doc_section = "#merge-target-stale";
|
|
39
|
+
constructor(context?: Record<string, unknown>);
|
|
40
|
+
}
|
|
41
|
+
export declare class EntityLabelMissingError extends EntityError {
|
|
42
|
+
readonly code = "entity.label_missing";
|
|
43
|
+
readonly doc_section = "#label-missing";
|
|
44
|
+
constructor(context?: Record<string, unknown>);
|
|
45
|
+
}
|
|
46
|
+
export declare class CandidateStaleError extends EntityError {
|
|
47
|
+
readonly code = "entity.candidate_stale";
|
|
48
|
+
readonly doc_section = "#candidate-stale";
|
|
49
|
+
constructor(context?: Record<string, unknown>);
|
|
50
|
+
}
|
|
51
|
+
export declare class AuditRunInProgressError extends EntityError {
|
|
52
|
+
readonly code = "entity.audit_run_in_progress";
|
|
53
|
+
readonly doc_section = "#audit-run-in-progress";
|
|
54
|
+
constructor(context?: Record<string, unknown>);
|
|
55
|
+
}
|
|
56
|
+
export declare class CaseMergeChainCycleError extends EntityError {
|
|
57
|
+
readonly code = "case.merge_chain_cycle";
|
|
58
|
+
readonly doc_section = "#case-merge-chain-cycle";
|
|
59
|
+
constructor(context: {
|
|
60
|
+
case_id: string;
|
|
61
|
+
chain: string[];
|
|
62
|
+
detected_at_depth: number;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CaseMergeChainCycleError = exports.AuditRunInProgressError = exports.CandidateStaleError = exports.EntityLabelMissingError = exports.MergeTargetStaleError = exports.OntologyViolationError = exports.EmbeddingUnavailableError = exports.InvalidEntityLabelError = exports.EntityError = void 0;
|
|
4
|
+
class EntityError extends Error {
|
|
5
|
+
context;
|
|
6
|
+
hint;
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
super(opts.message);
|
|
9
|
+
this.name = new.target.name;
|
|
10
|
+
this.context = opts.context ?? {};
|
|
11
|
+
this.hint = opts.hint;
|
|
12
|
+
if (Error.captureStackTrace) {
|
|
13
|
+
Error.captureStackTrace(this, new.target);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
toErrorEnvelope() {
|
|
17
|
+
return {
|
|
18
|
+
error: {
|
|
19
|
+
code: this.code,
|
|
20
|
+
message: this.message,
|
|
21
|
+
hint: this.hint,
|
|
22
|
+
doc_url: `docs/operations/entity-substrate-runbook.md${this.doc_section}`,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.EntityError = EntityError;
|
|
28
|
+
class InvalidEntityLabelError extends EntityError {
|
|
29
|
+
code = 'entity.invalid_label';
|
|
30
|
+
doc_section = '#invalid-label';
|
|
31
|
+
constructor(context = {}) {
|
|
32
|
+
super({
|
|
33
|
+
message: 'Invalid entity label.',
|
|
34
|
+
context,
|
|
35
|
+
hint: 'Provide a non-empty entity label before normalization.',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.InvalidEntityLabelError = InvalidEntityLabelError;
|
|
40
|
+
class EmbeddingUnavailableError extends EntityError {
|
|
41
|
+
code = 'entity.embedding_unavailable';
|
|
42
|
+
doc_section = '#embedding-unavailable';
|
|
43
|
+
constructor(context = {}) {
|
|
44
|
+
super({
|
|
45
|
+
message: 'Entity embedding model is unavailable.',
|
|
46
|
+
context,
|
|
47
|
+
hint: 'Verify the embedding model is installed and reachable before generating candidates.',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.EmbeddingUnavailableError = EmbeddingUnavailableError;
|
|
52
|
+
class OntologyViolationError extends EntityError {
|
|
53
|
+
code = 'entity.ontology_violation';
|
|
54
|
+
doc_section = '#ontology-violation';
|
|
55
|
+
constructor(context = {}) {
|
|
56
|
+
super({
|
|
57
|
+
message: 'Entity operation violates ontology constraints.',
|
|
58
|
+
context,
|
|
59
|
+
hint: 'Check entity kinds and relation signatures before applying the change.',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.OntologyViolationError = OntologyViolationError;
|
|
64
|
+
class MergeTargetStaleError extends EntityError {
|
|
65
|
+
code = 'entity.merge_target_stale';
|
|
66
|
+
doc_section = '#merge-target-stale';
|
|
67
|
+
constructor(context = {}) {
|
|
68
|
+
super({
|
|
69
|
+
message: 'Merge target is stale.',
|
|
70
|
+
context,
|
|
71
|
+
hint: 'Reload the target entity state and retry the merge against the latest version.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.MergeTargetStaleError = MergeTargetStaleError;
|
|
76
|
+
class EntityLabelMissingError extends EntityError {
|
|
77
|
+
code = 'entity.label_missing';
|
|
78
|
+
doc_section = '#label-missing';
|
|
79
|
+
constructor(context = {}) {
|
|
80
|
+
super({
|
|
81
|
+
message: 'Canonical entity label is missing.',
|
|
82
|
+
context,
|
|
83
|
+
hint: 'Set a preferred label before projecting or displaying the entity.',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.EntityLabelMissingError = EntityLabelMissingError;
|
|
88
|
+
class CandidateStaleError extends EntityError {
|
|
89
|
+
code = 'entity.candidate_stale';
|
|
90
|
+
doc_section = '#candidate-stale';
|
|
91
|
+
constructor(context = {}) {
|
|
92
|
+
super({
|
|
93
|
+
message: 'Resolution candidate is stale.',
|
|
94
|
+
context,
|
|
95
|
+
hint: 'Refresh the candidate list and review the latest evidence before acting.',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.CandidateStaleError = CandidateStaleError;
|
|
100
|
+
class AuditRunInProgressError extends EntityError {
|
|
101
|
+
code = 'entity.audit_run_in_progress';
|
|
102
|
+
doc_section = '#audit-run-in-progress';
|
|
103
|
+
constructor(context = {}) {
|
|
104
|
+
super({
|
|
105
|
+
message: 'Another entity audit run is already in progress.',
|
|
106
|
+
context,
|
|
107
|
+
hint: 'Wait for the current audit to finish or inspect its run status before starting a new one.',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.AuditRunInProgressError = AuditRunInProgressError;
|
|
112
|
+
class CaseMergeChainCycleError extends EntityError {
|
|
113
|
+
code = 'case.merge_chain_cycle';
|
|
114
|
+
doc_section = '#case-merge-chain-cycle';
|
|
115
|
+
constructor(context) {
|
|
116
|
+
super({
|
|
117
|
+
message: `Case merge chain cycle detected at depth ${context.detected_at_depth} starting from case_id=${context.case_id}. Chain=${context.chain.join(' -> ')}.`,
|
|
118
|
+
context,
|
|
119
|
+
hint: 'Inspect case_truth.canonical_case_id links and remove the cycle before retrying.',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.CaseMergeChainCycleError = CaseMergeChainCycleError;
|
|
124
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ExactMergeBackfillOptions {
|
|
2
|
+
dryRun?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface ExactMergeBackfillResult {
|
|
5
|
+
groups: number;
|
|
6
|
+
merged: number;
|
|
7
|
+
incomplete: number;
|
|
8
|
+
skipped: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function backfillExactDuplicateCanonicals(opts?: ExactMergeBackfillOptions): Promise<ExactMergeBackfillResult>;
|
|
11
|
+
//# sourceMappingURL=exact-merge-backfill.d.ts.map
|