@jungjaehoon/mama-core 1.4.2 → 1.6.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/README.md +48 -10
- 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/db/migrations/032-add-memory-provenance-columns.sql +21 -0
- package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
- package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
- package/db/migrations/035-create-twin-edges.sql +63 -0
- package/db/migrations/036-create-agent-situation-packets.sql +61 -0
- package/dist/agent-graph/alias-write.d.ts +3 -0
- package/dist/agent-graph/alias-write.js +231 -0
- package/dist/agent-graph/entity-resolve.d.ts +3 -0
- package/dist/agent-graph/entity-resolve.js +275 -0
- package/dist/agent-graph/errors.d.ts +4 -0
- package/dist/agent-graph/errors.js +11 -0
- package/dist/agent-graph/graph-query.d.ts +5 -0
- package/dist/agent-graph/graph-query.js +359 -0
- package/dist/agent-graph/index.d.ts +6 -0
- package/dist/agent-graph/index.js +22 -0
- package/dist/agent-graph/types.d.ts +162 -0
- package/dist/agent-graph/types.js +3 -0
- package/dist/agent-situation/builder.d.ts +8 -0
- package/dist/agent-situation/builder.js +239 -0
- package/dist/agent-situation/cache-key.d.ts +4 -0
- package/dist/agent-situation/cache-key.js +64 -0
- package/dist/agent-situation/index.d.ts +7 -0
- package/dist/agent-situation/index.js +28 -0
- package/dist/agent-situation/packet-store.d.ts +27 -0
- package/dist/agent-situation/packet-store.js +228 -0
- package/dist/agent-situation/ranking-policy.d.ts +18 -0
- package/dist/agent-situation/ranking-policy.js +75 -0
- package/dist/agent-situation/source-readers.d.ts +52 -0
- package/dist/agent-situation/source-readers.js +300 -0
- package/dist/agent-situation/types.d.ts +195 -0
- package/dist/agent-situation/types.js +3 -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 +27 -0
- package/dist/cases/search-rollup.js +309 -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/connectors/event-index.d.ts +45 -0
- package/dist/connectors/event-index.js +381 -0
- package/dist/connectors/raw-query.d.ts +18 -0
- package/dist/connectors/raw-query.js +305 -0
- package/dist/connectors/types.d.ts +109 -0
- package/dist/connectors/types.js +3 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
- package/dist/db-adapter/node-sqlite-adapter.js +131 -0
- package/dist/db-manager.d.ts +12 -1
- package/dist/db-manager.js +60 -5
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/decision-formatter.js +11 -8
- package/dist/edges/ref-validation.d.ts +8 -0
- package/dist/edges/ref-validation.js +262 -0
- package/dist/edges/store.d.ts +10 -0
- package/dist/edges/store.js +343 -0
- package/dist/edges/types.d.ts +86 -0
- package/dist/edges/types.js +18 -0
- package/dist/embedding-server/mobile/daemon.d.ts +1 -0
- package/dist/embedding-server/mobile/daemon.js +11 -1
- package/dist/embeddings.js +13 -0
- 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 +62 -3
- package/dist/index.js +116 -26
- package/dist/mama-api.d.ts +53 -35
- package/dist/mama-api.js +437 -78
- package/dist/memory/api.d.ts +26 -38
- package/dist/memory/api.js +772 -68
- package/dist/memory/event-store.d.ts +3 -0
- package/dist/memory/event-store.js +21 -2
- package/dist/memory/provenance-audit.d.ts +22 -0
- package/dist/memory/provenance-audit.js +112 -0
- package/dist/memory/provenance-query.d.ts +11 -0
- package/dist/memory/provenance-query.js +193 -0
- package/dist/memory/provenance.d.ts +29 -0
- package/dist/memory/provenance.js +136 -0
- package/dist/memory/scope-backfill.d.ts +17 -0
- package/dist/memory/scope-backfill.js +77 -0
- package/dist/memory/types.d.ts +97 -16
- package/dist/model-runs/store.d.ts +13 -0
- package/dist/model-runs/store.js +457 -0
- package/dist/model-runs/tool-trace-store.d.ts +4 -0
- package/dist/model-runs/tool-trace-store.js +114 -0
- package/dist/model-runs/types.d.ts +69 -0
- package/dist/model-runs/types.js +5 -0
- package/dist/runtime/trusted-provenance.d.ts +3 -0
- package/dist/runtime/trusted-provenance.js +6 -0
- 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/search/search-quality.d.ts +36 -0
- package/dist/search/search-quality.js +29 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +52 -0
- package/package.json +28 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
import type { CaseMembershipSourceType } from './types.js';
|
|
3
|
+
export interface CaseMembershipScoreBreakdown {
|
|
4
|
+
entity_overlap: number | null;
|
|
5
|
+
embedding_similarity: number | null;
|
|
6
|
+
temporal_proximity: number | null;
|
|
7
|
+
explicit_from_wiki: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface CaseMembershipExplanation {
|
|
10
|
+
case_id: string;
|
|
11
|
+
terminal_case_id: string;
|
|
12
|
+
resolved_via_case_id: string | null;
|
|
13
|
+
chain: string[];
|
|
14
|
+
source_type: CaseMembershipSourceType;
|
|
15
|
+
source_id: string;
|
|
16
|
+
membership: {
|
|
17
|
+
status: string;
|
|
18
|
+
role: string | null;
|
|
19
|
+
confidence: number | null;
|
|
20
|
+
reason: string | null;
|
|
21
|
+
user_locked: boolean;
|
|
22
|
+
assignment_strategy: string | null;
|
|
23
|
+
assigned_at: string | null;
|
|
24
|
+
};
|
|
25
|
+
score_breakdown: CaseMembershipScoreBreakdown | null;
|
|
26
|
+
score_breakdown_reason?: 'breakdown_not_recorded' | 'breakdown_malformed';
|
|
27
|
+
source_locator: string | null;
|
|
28
|
+
explanation_updated_at: string | null;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface PopulateScoreBreakdownInput {
|
|
32
|
+
case_id: string;
|
|
33
|
+
source_type: CaseMembershipSourceType;
|
|
34
|
+
source_id: string;
|
|
35
|
+
assignment_strategy?: string | null;
|
|
36
|
+
source_locator?: string | null;
|
|
37
|
+
now?: string;
|
|
38
|
+
}
|
|
39
|
+
export type ExplainCaseMembershipResult = CaseMembershipExplanation | {
|
|
40
|
+
kind: 'not_found';
|
|
41
|
+
code: 'case.membership_not_found';
|
|
42
|
+
message: string;
|
|
43
|
+
terminal_case_id: string;
|
|
44
|
+
resolved_via_case_id: string | null;
|
|
45
|
+
chain: string[];
|
|
46
|
+
};
|
|
47
|
+
export declare function explainCaseMembership(adapter: DatabaseAdapter, input: {
|
|
48
|
+
case_id: string;
|
|
49
|
+
source_type: CaseMembershipSourceType;
|
|
50
|
+
source_id: string;
|
|
51
|
+
}): ExplainCaseMembershipResult;
|
|
52
|
+
export declare function populateScoreBreakdown(adapter: DatabaseAdapter, membershipRow: PopulateScoreBreakdownInput, breakdown: CaseMembershipScoreBreakdown): {
|
|
53
|
+
kind: 'populated';
|
|
54
|
+
changes: number;
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=membership-explain.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.explainCaseMembership = explainCaseMembership;
|
|
4
|
+
exports.populateScoreBreakdown = populateScoreBreakdown;
|
|
5
|
+
const canonicalize_js_1 = require("../canonicalize.js");
|
|
6
|
+
const store_js_1 = require("./store.js");
|
|
7
|
+
function normalizeNow(value) {
|
|
8
|
+
return value ?? new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function placeholders(values) {
|
|
11
|
+
if (values.length === 0) {
|
|
12
|
+
throw new Error('Cannot build SQL IN clause for an empty value list.');
|
|
13
|
+
}
|
|
14
|
+
return values.map(() => '?').join(', ');
|
|
15
|
+
}
|
|
16
|
+
function loadMembership(adapter, chain, sourceType, sourceId) {
|
|
17
|
+
const row = adapter
|
|
18
|
+
.prepare(`
|
|
19
|
+
SELECT case_id, source_type, source_id, status, role, confidence, reason,
|
|
20
|
+
user_locked, assignment_strategy, added_at AS assigned_at,
|
|
21
|
+
score_breakdown_json, source_locator, explanation_updated_at, updated_at
|
|
22
|
+
FROM case_memberships
|
|
23
|
+
WHERE case_id IN (${placeholders(chain)})
|
|
24
|
+
AND source_type = ?
|
|
25
|
+
AND source_id = ?
|
|
26
|
+
ORDER BY
|
|
27
|
+
CASE status
|
|
28
|
+
WHEN 'active' THEN 0
|
|
29
|
+
WHEN 'candidate' THEN 1
|
|
30
|
+
WHEN 'excluded' THEN 2
|
|
31
|
+
WHEN 'removed' THEN 3
|
|
32
|
+
ELSE 4
|
|
33
|
+
END ASC,
|
|
34
|
+
user_locked DESC,
|
|
35
|
+
updated_at DESC,
|
|
36
|
+
case_id ASC
|
|
37
|
+
LIMIT 1
|
|
38
|
+
`)
|
|
39
|
+
.get(...chain, sourceType, sourceId);
|
|
40
|
+
return row ?? null;
|
|
41
|
+
}
|
|
42
|
+
function nullableNumber(value) {
|
|
43
|
+
if (value === null || value === undefined) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const n = Number(value);
|
|
47
|
+
return Number.isFinite(n) ? n : null;
|
|
48
|
+
}
|
|
49
|
+
function parseScoreBreakdown(value) {
|
|
50
|
+
if (value === null || value.trim().length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
breakdown: null,
|
|
53
|
+
reason: 'breakdown_not_recorded',
|
|
54
|
+
warnings: ['breakdown_not_recorded'],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(value);
|
|
59
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
60
|
+
return {
|
|
61
|
+
breakdown: null,
|
|
62
|
+
reason: 'breakdown_malformed',
|
|
63
|
+
warnings: ['breakdown_malformed'],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const record = parsed;
|
|
67
|
+
return {
|
|
68
|
+
breakdown: {
|
|
69
|
+
entity_overlap: nullableNumber(record.entity_overlap),
|
|
70
|
+
embedding_similarity: nullableNumber(record.embedding_similarity),
|
|
71
|
+
temporal_proximity: nullableNumber(record.temporal_proximity),
|
|
72
|
+
explicit_from_wiki: nullableNumber(record.explicit_from_wiki),
|
|
73
|
+
},
|
|
74
|
+
warnings: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return {
|
|
79
|
+
breakdown: null,
|
|
80
|
+
reason: 'breakdown_malformed',
|
|
81
|
+
warnings: ['breakdown_malformed'],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function explainCaseMembership(adapter, input) {
|
|
86
|
+
const explainAdapter = adapter;
|
|
87
|
+
const resolution = (0, store_js_1.resolveCanonicalCaseChain)(explainAdapter, input.case_id);
|
|
88
|
+
const chain = (0, store_js_1.expandCaseChainForAssembly)(explainAdapter, resolution.terminal_case_id, resolution.chain);
|
|
89
|
+
const row = loadMembership(explainAdapter, chain, input.source_type, input.source_id);
|
|
90
|
+
if (!row) {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'not_found',
|
|
93
|
+
code: 'case.membership_not_found',
|
|
94
|
+
message: 'Membership row was not found in the canonical case chain.',
|
|
95
|
+
terminal_case_id: resolution.terminal_case_id,
|
|
96
|
+
resolved_via_case_id: resolution.resolved_via_case_id,
|
|
97
|
+
chain,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const parsedBreakdown = parseScoreBreakdown(row.score_breakdown_json);
|
|
101
|
+
return {
|
|
102
|
+
case_id: row.case_id,
|
|
103
|
+
terminal_case_id: resolution.terminal_case_id,
|
|
104
|
+
resolved_via_case_id: resolution.resolved_via_case_id,
|
|
105
|
+
chain,
|
|
106
|
+
source_type: row.source_type,
|
|
107
|
+
source_id: row.source_id,
|
|
108
|
+
membership: {
|
|
109
|
+
status: row.status,
|
|
110
|
+
role: row.role,
|
|
111
|
+
confidence: row.confidence,
|
|
112
|
+
reason: row.reason,
|
|
113
|
+
user_locked: Number(row.user_locked) === 1,
|
|
114
|
+
assignment_strategy: row.assignment_strategy,
|
|
115
|
+
assigned_at: row.assigned_at,
|
|
116
|
+
},
|
|
117
|
+
score_breakdown: parsedBreakdown.breakdown,
|
|
118
|
+
...(parsedBreakdown.reason ? { score_breakdown_reason: parsedBreakdown.reason } : {}),
|
|
119
|
+
source_locator: row.source_locator,
|
|
120
|
+
explanation_updated_at: row.explanation_updated_at,
|
|
121
|
+
warnings: parsedBreakdown.warnings,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function populateScoreBreakdown(adapter, membershipRow, breakdown) {
|
|
125
|
+
const now = normalizeNow(membershipRow.now);
|
|
126
|
+
const result = adapter
|
|
127
|
+
.prepare(`
|
|
128
|
+
UPDATE case_memberships
|
|
129
|
+
SET score_breakdown_json = ?,
|
|
130
|
+
source_locator = COALESCE(?, source_locator),
|
|
131
|
+
assignment_strategy = COALESCE(?, assignment_strategy),
|
|
132
|
+
explanation_updated_at = ?
|
|
133
|
+
WHERE case_id = ?
|
|
134
|
+
AND source_type = ?
|
|
135
|
+
AND source_id = ?
|
|
136
|
+
`)
|
|
137
|
+
.run((0, canonicalize_js_1.canonicalizeJSON)(breakdown), membershipRow.source_locator ?? null, membershipRow.assignment_strategy ?? null, now, membershipRow.case_id, membershipRow.source_type, membershipRow.source_id);
|
|
138
|
+
return { kind: 'populated', changes: result.changes };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=membership-explain.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
export interface CaseMembershipMatchInput {
|
|
3
|
+
event_id: string;
|
|
4
|
+
event_text: string;
|
|
5
|
+
event_entities: string[];
|
|
6
|
+
observed_at: string;
|
|
7
|
+
explicit_case_id?: string | null;
|
|
8
|
+
query_embedding?: Float32Array | null;
|
|
9
|
+
now?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CaseMembershipMatch {
|
|
12
|
+
case_id: string;
|
|
13
|
+
score: number;
|
|
14
|
+
status: 'active' | 'candidate';
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
export type CaseMembershipMatchResult = CaseMembershipMatch[] | {
|
|
18
|
+
kind: 'precompile_gap';
|
|
19
|
+
code: 'case.precompile_gap';
|
|
20
|
+
case_id: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function matchEventToExistingCases(adapter: DatabaseAdapter, input: CaseMembershipMatchInput): CaseMembershipMatchResult;
|
|
23
|
+
//# sourceMappingURL=membership-matcher.d.ts.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchEventToExistingCases = matchEventToExistingCases;
|
|
4
|
+
const wiki_page_index_js_1 = require("./wiki-page-index.js");
|
|
5
|
+
const ACTIVE_CASE_STATUSES = new Set(['active', 'blocked']);
|
|
6
|
+
const ACTIVE_THRESHOLD = 0.7;
|
|
7
|
+
const CANDIDATE_THRESHOLD = 0.52;
|
|
8
|
+
const RECENCY_WINDOW_MS = 14 * 24 * 60 * 60 * 1000;
|
|
9
|
+
function normalizeEntity(value) {
|
|
10
|
+
return value.trim().toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
function parsePrimaryActors(value) {
|
|
13
|
+
if (!value || value.trim().length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const parsed = JSON.parse(value);
|
|
17
|
+
if (!Array.isArray(parsed)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const actorIds = [];
|
|
21
|
+
for (const item of parsed) {
|
|
22
|
+
if (typeof item === 'string') {
|
|
23
|
+
actorIds.push(item);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
27
|
+
const entityId = item.entity_id;
|
|
28
|
+
if (typeof entityId === 'string' && entityId.trim().length > 0) {
|
|
29
|
+
actorIds.push(entityId);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return actorIds;
|
|
34
|
+
}
|
|
35
|
+
function overlapCount(left, right) {
|
|
36
|
+
const rightSet = new Set(right.map(normalizeEntity).filter(Boolean));
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
let count = 0;
|
|
39
|
+
for (const value of left) {
|
|
40
|
+
const normalized = normalizeEntity(value);
|
|
41
|
+
if (normalized.length === 0 || seen.has(normalized)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
seen.add(normalized);
|
|
45
|
+
if (rightSet.has(normalized)) {
|
|
46
|
+
count += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return count;
|
|
50
|
+
}
|
|
51
|
+
function timestampMs(value) {
|
|
52
|
+
if (!value) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const parsed = Date.parse(value);
|
|
56
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
57
|
+
}
|
|
58
|
+
function hasRecentActivity(row, observedAt) {
|
|
59
|
+
const observedMs = timestampMs(observedAt);
|
|
60
|
+
if (observedMs === null) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const caseMs = timestampMs(row.last_activity_at) ?? timestampMs(row.compiled_at);
|
|
64
|
+
if (caseMs === null) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return Math.abs(observedMs - caseMs) <= RECENCY_WINDOW_MS;
|
|
68
|
+
}
|
|
69
|
+
function wikiSimilarityByCase(adapter, queryEmbedding) {
|
|
70
|
+
const scores = new Map();
|
|
71
|
+
if (!queryEmbedding) {
|
|
72
|
+
return scores;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const hits = (0, wiki_page_index_js_1.vectorSearchWikiPages)(adapter, queryEmbedding, 20);
|
|
76
|
+
for (const hit of hits) {
|
|
77
|
+
if (hit.record.page_type !== 'case' || !hit.record.case_id) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const existing = scores.get(hit.record.case_id) ?? -Infinity;
|
|
81
|
+
if (hit.raw_score > existing) {
|
|
82
|
+
scores.set(hit.record.case_id, hit.raw_score);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return scores;
|
|
88
|
+
}
|
|
89
|
+
return scores;
|
|
90
|
+
}
|
|
91
|
+
function scoreCandidate(input) {
|
|
92
|
+
let score = 0;
|
|
93
|
+
const signals = [];
|
|
94
|
+
const actorOverlap = overlapCount(input.eventEntities, parsePrimaryActors(input.row.primary_actors));
|
|
95
|
+
if (actorOverlap > 0) {
|
|
96
|
+
const actorScore = Math.min(0.45 * actorOverlap, 0.9);
|
|
97
|
+
score += actorScore;
|
|
98
|
+
signals.push(`primary_actors_overlap:${actorOverlap}`);
|
|
99
|
+
}
|
|
100
|
+
if (input.wikiScore >= 0.82) {
|
|
101
|
+
score += 0.3;
|
|
102
|
+
signals.push(`wiki_embedding:${input.wikiScore.toFixed(3)}`);
|
|
103
|
+
}
|
|
104
|
+
else if (input.wikiScore >= 0.74) {
|
|
105
|
+
score += 0.18;
|
|
106
|
+
signals.push(`wiki_embedding:${input.wikiScore.toFixed(3)}`);
|
|
107
|
+
}
|
|
108
|
+
if (hasRecentActivity(input.row, input.observedAt)) {
|
|
109
|
+
score += 0.15;
|
|
110
|
+
signals.push('recent_activity');
|
|
111
|
+
}
|
|
112
|
+
return { score, signals };
|
|
113
|
+
}
|
|
114
|
+
function toMatch(case_id, score, signals) {
|
|
115
|
+
if (score >= ACTIVE_THRESHOLD) {
|
|
116
|
+
return {
|
|
117
|
+
case_id,
|
|
118
|
+
score: Number(score.toFixed(4)),
|
|
119
|
+
status: 'active',
|
|
120
|
+
reason: signals.join(','),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (score >= CANDIDATE_THRESHOLD) {
|
|
124
|
+
return {
|
|
125
|
+
case_id,
|
|
126
|
+
score: Number(score.toFixed(4)),
|
|
127
|
+
status: 'candidate',
|
|
128
|
+
reason: signals.join(','),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function matchEventToExistingCases(adapter, input) {
|
|
134
|
+
const matcherAdapter = adapter;
|
|
135
|
+
if (input.explicit_case_id) {
|
|
136
|
+
const row = matcherAdapter
|
|
137
|
+
.prepare(`
|
|
138
|
+
SELECT case_id, status
|
|
139
|
+
FROM case_truth
|
|
140
|
+
WHERE case_id = ?
|
|
141
|
+
`)
|
|
142
|
+
.get(input.explicit_case_id);
|
|
143
|
+
if (!row) {
|
|
144
|
+
return {
|
|
145
|
+
kind: 'precompile_gap',
|
|
146
|
+
code: 'case.precompile_gap',
|
|
147
|
+
case_id: input.explicit_case_id,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!ACTIVE_CASE_STATUSES.has(row.status)) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
return [
|
|
154
|
+
{
|
|
155
|
+
case_id: row.case_id,
|
|
156
|
+
score: 1,
|
|
157
|
+
status: 'active',
|
|
158
|
+
reason: 'explicit_case_id',
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
const rows = matcherAdapter
|
|
163
|
+
.prepare(`
|
|
164
|
+
SELECT case_id, title, primary_actors, current_wiki_path, last_activity_at,
|
|
165
|
+
compiled_at, status, confidence
|
|
166
|
+
FROM case_truth
|
|
167
|
+
WHERE status IN ('active','blocked')
|
|
168
|
+
ORDER BY COALESCE(last_activity_at, compiled_at, created_at) DESC
|
|
169
|
+
LIMIT 200
|
|
170
|
+
`)
|
|
171
|
+
.all();
|
|
172
|
+
const wikiScores = wikiSimilarityByCase(matcherAdapter, input.query_embedding);
|
|
173
|
+
const matches = [];
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
const { score, signals } = scoreCandidate({
|
|
176
|
+
row,
|
|
177
|
+
eventEntities: input.event_entities,
|
|
178
|
+
observedAt: input.observed_at,
|
|
179
|
+
wikiScore: wikiScores.get(row.case_id) ?? 0,
|
|
180
|
+
});
|
|
181
|
+
const match = toMatch(row.case_id, score, signals);
|
|
182
|
+
if (match) {
|
|
183
|
+
matches.push(match);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return matches.sort((left, right) => {
|
|
187
|
+
if (right.score !== left.score) {
|
|
188
|
+
return right.score - left.score;
|
|
189
|
+
}
|
|
190
|
+
return left.case_id.localeCompare(right.case_id);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=membership-matcher.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
export type MergeCaseErrorCode = 'case.confirmation_required' | 'case.merge_self' | 'case.merge_chain_cycle' | 'case.terminal_status';
|
|
3
|
+
export interface MergeCasesInput {
|
|
4
|
+
loser_case_id: string;
|
|
5
|
+
survivor_case_id: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
confirmed: true;
|
|
8
|
+
confirmed_by: string;
|
|
9
|
+
confirmation_summary: string;
|
|
10
|
+
now?: string;
|
|
11
|
+
}
|
|
12
|
+
export type MergeCasesResult = {
|
|
13
|
+
kind: 'merged';
|
|
14
|
+
loser_case_id: string;
|
|
15
|
+
survivor_case_id: string;
|
|
16
|
+
audit_event_id: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'precompile_gap';
|
|
19
|
+
code: 'case.precompile_gap';
|
|
20
|
+
case_id: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'rejected';
|
|
23
|
+
code: MergeCaseErrorCode;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
export interface SplitCaseChildPlan {
|
|
27
|
+
title: string;
|
|
28
|
+
current_wiki_path?: string | null;
|
|
29
|
+
membership_sources: Array<{
|
|
30
|
+
source_type: 'decision' | 'event' | 'observation' | 'artifact';
|
|
31
|
+
source_id: string;
|
|
32
|
+
remove_from_parent?: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface SplitCaseInput {
|
|
36
|
+
parent_case_id: string;
|
|
37
|
+
children: SplitCaseChildPlan[];
|
|
38
|
+
trusted_child_case_ids: string[];
|
|
39
|
+
reason: string;
|
|
40
|
+
confirmed: true;
|
|
41
|
+
confirmed_by: string;
|
|
42
|
+
confirmation_summary: string;
|
|
43
|
+
now?: string;
|
|
44
|
+
}
|
|
45
|
+
export type SplitCaseResult = {
|
|
46
|
+
kind: 'split';
|
|
47
|
+
parent_case_id: string;
|
|
48
|
+
child_case_ids: string[];
|
|
49
|
+
audit_event_id: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'precompile_gap';
|
|
52
|
+
code: 'case.precompile_gap';
|
|
53
|
+
case_id: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'rejected';
|
|
56
|
+
code: 'case.confirmation_required' | 'case.terminal_status' | 'case.split_requires_two_children' | 'case.child_id_not_trusted' | 'case.child_id_count_mismatch';
|
|
57
|
+
message: string;
|
|
58
|
+
};
|
|
59
|
+
export declare function mergeCases(adapter: DatabaseAdapter, input: MergeCasesInput): MergeCasesResult;
|
|
60
|
+
export declare function splitCase(adapter: DatabaseAdapter, input: SplitCaseInput): SplitCaseResult;
|
|
61
|
+
//# sourceMappingURL=merge-split.d.ts.map
|