@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,25 @@
|
|
|
1
|
+
import type { CaseMembershipSourceType } from './types.js';
|
|
2
|
+
export type CaseFieldName = 'case_id' | 'canonical_case_id' | 'title' | 'status' | 'status_reason' | 'primary_actors' | 'blockers' | 'confidence' | 'last_activity_at';
|
|
3
|
+
export interface CaseFieldTargetRef {
|
|
4
|
+
kind: 'case_field';
|
|
5
|
+
field: CaseFieldName;
|
|
6
|
+
}
|
|
7
|
+
export interface CaseMembershipTargetRef {
|
|
8
|
+
kind: 'membership';
|
|
9
|
+
source_type: CaseMembershipSourceType;
|
|
10
|
+
source_id: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CaseWikiSectionTargetRef {
|
|
13
|
+
kind: 'wiki_section';
|
|
14
|
+
section_heading: string;
|
|
15
|
+
}
|
|
16
|
+
export type CaseTargetRef = CaseFieldTargetRef | CaseMembershipTargetRef | CaseWikiSectionTargetRef;
|
|
17
|
+
export interface CanonicalTargetRef {
|
|
18
|
+
json: string;
|
|
19
|
+
hash: Buffer;
|
|
20
|
+
}
|
|
21
|
+
export declare function buildCaseFieldTargetRef(fieldName: CaseFieldName): CaseFieldTargetRef;
|
|
22
|
+
export declare function buildMembershipTargetRef(sourceType: CaseMembershipSourceType, sourceId: string): CaseMembershipTargetRef;
|
|
23
|
+
export declare function buildWikiSectionTargetRef(sectionHeading: string): CaseWikiSectionTargetRef;
|
|
24
|
+
export declare function canonicalTargetRef(targetRef: CaseTargetRef): CanonicalTargetRef;
|
|
25
|
+
//# sourceMappingURL=target-ref.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildCaseFieldTargetRef = buildCaseFieldTargetRef;
|
|
4
|
+
exports.buildMembershipTargetRef = buildMembershipTargetRef;
|
|
5
|
+
exports.buildWikiSectionTargetRef = buildWikiSectionTargetRef;
|
|
6
|
+
exports.canonicalTargetRef = canonicalTargetRef;
|
|
7
|
+
const canonicalize_js_1 = require("../canonicalize.js");
|
|
8
|
+
function buildCaseFieldTargetRef(fieldName) {
|
|
9
|
+
return { kind: 'case_field', field: fieldName };
|
|
10
|
+
}
|
|
11
|
+
function buildMembershipTargetRef(sourceType, sourceId) {
|
|
12
|
+
return { kind: 'membership', source_type: sourceType, source_id: sourceId };
|
|
13
|
+
}
|
|
14
|
+
function buildWikiSectionTargetRef(sectionHeading) {
|
|
15
|
+
return { kind: 'wiki_section', section_heading: sectionHeading };
|
|
16
|
+
}
|
|
17
|
+
function canonicalTargetRef(targetRef) {
|
|
18
|
+
const json = (0, canonicalize_js_1.canonicalizeJSON)(targetRef);
|
|
19
|
+
const hash = (0, canonicalize_js_1.targetRefHashCanonicalJSON)(json);
|
|
20
|
+
return { json, hash };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=target-ref.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
import type { CaseTimelineRangeInput, CaseTimelineRangeResult } from './types.js';
|
|
3
|
+
type CaseTimelineRangeAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
|
|
4
|
+
export declare function caseTimelineRange(adapter: CaseTimelineRangeAdapter, input: CaseTimelineRangeInput): CaseTimelineRangeResult;
|
|
5
|
+
export type { CaseTimelineRangeInput, CaseTimelineRangeItem, CaseTimelineRangeResult, } from './types.js';
|
|
6
|
+
//# sourceMappingURL=timeline-range.d.ts.map
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.caseTimelineRange = caseTimelineRange;
|
|
4
|
+
const event_index_js_1 = require("../connectors/event-index.js");
|
|
5
|
+
const store_js_1 = require("./store.js");
|
|
6
|
+
function codedError(code, message, context) {
|
|
7
|
+
const error = new Error(`${code}: ${message}`);
|
|
8
|
+
error.code = code;
|
|
9
|
+
error.context = context;
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
function placeholders(values) {
|
|
13
|
+
if (values.length === 0) {
|
|
14
|
+
throw new Error('Cannot build SQL placeholders for an empty list.');
|
|
15
|
+
}
|
|
16
|
+
return values.map(() => '?').join(', ');
|
|
17
|
+
}
|
|
18
|
+
function nullableString(value) {
|
|
19
|
+
if (value === null || value === undefined) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
function nullableNumber(value) {
|
|
25
|
+
if (value === null || value === undefined) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const numberValue = Number(value);
|
|
29
|
+
return Number.isFinite(numberValue) ? numberValue : null;
|
|
30
|
+
}
|
|
31
|
+
function parseDateBound(value, field) {
|
|
32
|
+
if (value === undefined) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === 'number') {
|
|
36
|
+
if (!Number.isFinite(value)) {
|
|
37
|
+
throw codedError('case.timeline_range_invalid_date', `${field} must be a finite number.`, {
|
|
38
|
+
field,
|
|
39
|
+
value,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return Math.floor(value);
|
|
43
|
+
}
|
|
44
|
+
const parsed = Date.parse(value);
|
|
45
|
+
if (!Number.isFinite(parsed)) {
|
|
46
|
+
throw codedError('case.timeline_range_invalid_date', `${field} is not a valid date.`, {
|
|
47
|
+
field,
|
|
48
|
+
value,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
function parseBounds(input) {
|
|
54
|
+
const fromMs = parseDateBound(input.from, 'from');
|
|
55
|
+
const toMs = parseDateBound(input.to, 'to');
|
|
56
|
+
if (fromMs !== null && toMs !== null && fromMs > toMs) {
|
|
57
|
+
throw codedError('case.timeline_range_invalid_order', 'from must be before or equal to to.', {
|
|
58
|
+
from: input.from,
|
|
59
|
+
to: input.to,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { fromMs, toMs };
|
|
63
|
+
}
|
|
64
|
+
function normalizeLimit(limit) {
|
|
65
|
+
if (limit === undefined) {
|
|
66
|
+
return 100;
|
|
67
|
+
}
|
|
68
|
+
return Math.min(500, Math.max(0, Math.floor(limit)));
|
|
69
|
+
}
|
|
70
|
+
function timestampMs(value) {
|
|
71
|
+
if (value === null || value === undefined) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (typeof value === 'number') {
|
|
75
|
+
return Number.isFinite(value) ? value : null;
|
|
76
|
+
}
|
|
77
|
+
const text = String(value);
|
|
78
|
+
if (text.length === 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const numeric = Number(text);
|
|
82
|
+
if (Number.isFinite(numeric) && /^\d+$/u.test(text)) {
|
|
83
|
+
return numeric;
|
|
84
|
+
}
|
|
85
|
+
const parsed = Date.parse(text);
|
|
86
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
87
|
+
}
|
|
88
|
+
function eventDateFromMs(ms) {
|
|
89
|
+
if (ms === null) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
93
|
+
}
|
|
94
|
+
function withinBounds(ms, bounds) {
|
|
95
|
+
if (ms === null) {
|
|
96
|
+
return bounds.fromMs === null && bounds.toMs === null;
|
|
97
|
+
}
|
|
98
|
+
if (bounds.fromMs !== null && ms < bounds.fromMs) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (bounds.toMs !== null && ms > bounds.toMs) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
function compareItemsAsc(left, right) {
|
|
107
|
+
const leftMs = left.event_datetime ?? Number.NEGATIVE_INFINITY;
|
|
108
|
+
const rightMs = right.event_datetime ?? Number.NEGATIVE_INFINITY;
|
|
109
|
+
if (leftMs !== rightMs) {
|
|
110
|
+
return leftMs - rightMs;
|
|
111
|
+
}
|
|
112
|
+
const itemDelta = left.item_type.localeCompare(right.item_type);
|
|
113
|
+
if (itemDelta !== 0) {
|
|
114
|
+
return itemDelta;
|
|
115
|
+
}
|
|
116
|
+
const sourceTypeDelta = left.source_type.localeCompare(right.source_type);
|
|
117
|
+
if (sourceTypeDelta !== 0) {
|
|
118
|
+
return sourceTypeDelta;
|
|
119
|
+
}
|
|
120
|
+
return left.source_id.localeCompare(right.source_id);
|
|
121
|
+
}
|
|
122
|
+
function connectorEventSnapshot(row) {
|
|
123
|
+
if (!row) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
event_index_id: row.event_index_id,
|
|
128
|
+
source_connector: row.source_connector,
|
|
129
|
+
source_id: row.source_id,
|
|
130
|
+
source_locator: row.source_locator,
|
|
131
|
+
artifact_locator: row.artifact_locator,
|
|
132
|
+
title: row.title,
|
|
133
|
+
content: row.content,
|
|
134
|
+
event_datetime: row.event_datetime,
|
|
135
|
+
event_date: row.event_date,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function membershipById(memberships, sourceType) {
|
|
139
|
+
return new Map(memberships
|
|
140
|
+
.filter((membership) => membership.source_type === sourceType)
|
|
141
|
+
.map((membership) => [membership.source_id, membership]));
|
|
142
|
+
}
|
|
143
|
+
function loadDecisionItems(adapter, memberships, bounds) {
|
|
144
|
+
const membershipMap = membershipById(memberships, 'decision');
|
|
145
|
+
const ids = Array.from(membershipMap.keys());
|
|
146
|
+
if (ids.length === 0) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const rows = adapter
|
|
150
|
+
.prepare(`
|
|
151
|
+
SELECT id, topic, decision, summary, reasoning, confidence, event_datetime, event_date, created_at
|
|
152
|
+
FROM decisions
|
|
153
|
+
WHERE id IN (${placeholders(ids)})
|
|
154
|
+
`)
|
|
155
|
+
.all(...ids);
|
|
156
|
+
return rows
|
|
157
|
+
.map((row) => {
|
|
158
|
+
const sourceId = String(row.id);
|
|
159
|
+
const membership = membershipMap.get(sourceId);
|
|
160
|
+
const eventDatetime = timestampMs(row.event_datetime) ??
|
|
161
|
+
timestampMs(row.event_date) ??
|
|
162
|
+
timestampMs(row.created_at);
|
|
163
|
+
return {
|
|
164
|
+
item_type: 'decision',
|
|
165
|
+
source_type: 'decision',
|
|
166
|
+
source_id: sourceId,
|
|
167
|
+
source_locator: null,
|
|
168
|
+
event_datetime: eventDatetime,
|
|
169
|
+
event_date: nullableString(row.event_date) ?? eventDateFromMs(eventDatetime),
|
|
170
|
+
title: nullableString(row.topic),
|
|
171
|
+
summary: nullableString(row.summary ?? row.decision),
|
|
172
|
+
role: membership?.role ?? null,
|
|
173
|
+
confidence: membership?.confidence ?? nullableNumber(row.confidence),
|
|
174
|
+
membership_reason: membership?.reason ?? null,
|
|
175
|
+
};
|
|
176
|
+
})
|
|
177
|
+
.filter((item) => withinBounds(item.event_datetime, bounds));
|
|
178
|
+
}
|
|
179
|
+
function loadEventItems(adapter, memberships, bounds) {
|
|
180
|
+
const membershipMap = membershipById(memberships, 'event');
|
|
181
|
+
const ids = Array.from(membershipMap.keys());
|
|
182
|
+
if (ids.length === 0) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
const rows = adapter
|
|
186
|
+
.prepare(`
|
|
187
|
+
SELECT id, event_type, role, observed_at, source_ref, summary, details, created_at
|
|
188
|
+
FROM entity_timeline_events
|
|
189
|
+
WHERE id IN (${placeholders(ids)})
|
|
190
|
+
`)
|
|
191
|
+
.all(...ids);
|
|
192
|
+
return rows
|
|
193
|
+
.map((row) => {
|
|
194
|
+
const sourceId = String(row.id);
|
|
195
|
+
const membership = membershipMap.get(sourceId);
|
|
196
|
+
const eventDatetime = timestampMs(row.observed_at) ?? timestampMs(row.created_at);
|
|
197
|
+
return {
|
|
198
|
+
item_type: 'event',
|
|
199
|
+
source_type: 'event',
|
|
200
|
+
source_id: sourceId,
|
|
201
|
+
source_locator: nullableString(row.source_ref),
|
|
202
|
+
event_datetime: eventDatetime,
|
|
203
|
+
event_date: eventDateFromMs(eventDatetime),
|
|
204
|
+
title: nullableString(row.event_type),
|
|
205
|
+
summary: nullableString(row.summary),
|
|
206
|
+
role: membership?.role ?? nullableString(row.role),
|
|
207
|
+
confidence: membership?.confidence ?? null,
|
|
208
|
+
membership_reason: membership?.reason ?? null,
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
.filter((item) => withinBounds(item.event_datetime, bounds));
|
|
212
|
+
}
|
|
213
|
+
function loadObservationItems(adapter, memberships, bounds, includeConnectorEnrichments) {
|
|
214
|
+
const membershipMap = membershipById(memberships, 'observation');
|
|
215
|
+
const ids = Array.from(membershipMap.keys());
|
|
216
|
+
if (ids.length === 0) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const rows = adapter
|
|
220
|
+
.prepare(`
|
|
221
|
+
SELECT id, surface_form, context_summary, source_connector, source_raw_record_id,
|
|
222
|
+
source_locator, timestamp_observed, created_at
|
|
223
|
+
FROM entity_observations
|
|
224
|
+
WHERE id IN (${placeholders(ids)})
|
|
225
|
+
`)
|
|
226
|
+
.all(...ids);
|
|
227
|
+
return rows
|
|
228
|
+
.map((row) => {
|
|
229
|
+
const sourceId = String(row.id);
|
|
230
|
+
const membership = membershipMap.get(sourceId);
|
|
231
|
+
const eventDatetime = timestampMs(row.timestamp_observed) ?? timestampMs(row.created_at);
|
|
232
|
+
const connectorEvent = includeConnectorEnrichments &&
|
|
233
|
+
typeof row.source_connector === 'string' &&
|
|
234
|
+
typeof row.source_raw_record_id === 'string'
|
|
235
|
+
? (0, event_index_js_1.getConnectorEventIndexRecord)(adapter, String(row.source_connector), String(row.source_raw_record_id))
|
|
236
|
+
: null;
|
|
237
|
+
return {
|
|
238
|
+
item_type: 'observation',
|
|
239
|
+
source_type: 'observation',
|
|
240
|
+
source_id: sourceId,
|
|
241
|
+
source_locator: nullableString(row.source_locator),
|
|
242
|
+
event_datetime: eventDatetime,
|
|
243
|
+
event_date: eventDateFromMs(eventDatetime),
|
|
244
|
+
title: nullableString(row.surface_form),
|
|
245
|
+
summary: nullableString(row.context_summary ?? row.surface_form),
|
|
246
|
+
role: membership?.role ?? null,
|
|
247
|
+
confidence: membership?.confidence ?? null,
|
|
248
|
+
membership_reason: membership?.reason ?? null,
|
|
249
|
+
...(includeConnectorEnrichments
|
|
250
|
+
? { connector_event: connectorEventSnapshot(connectorEvent) }
|
|
251
|
+
: {}),
|
|
252
|
+
};
|
|
253
|
+
})
|
|
254
|
+
.filter((item) => withinBounds(item.event_datetime, bounds));
|
|
255
|
+
}
|
|
256
|
+
function loadArtifactConnectorEvent(adapter, sourceId) {
|
|
257
|
+
const row = adapter
|
|
258
|
+
.prepare(`
|
|
259
|
+
SELECT *
|
|
260
|
+
FROM connector_event_index
|
|
261
|
+
WHERE event_index_id = ?
|
|
262
|
+
OR artifact_locator = ?
|
|
263
|
+
ORDER BY event_datetime ASC, event_index_id ASC
|
|
264
|
+
LIMIT 1
|
|
265
|
+
`)
|
|
266
|
+
.get(sourceId, sourceId);
|
|
267
|
+
if (!row) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const sourceConnector = String(row.source_connector);
|
|
271
|
+
const connectorSourceId = String(row.source_id);
|
|
272
|
+
return (0, event_index_js_1.getConnectorEventIndexRecord)(adapter, sourceConnector, connectorSourceId);
|
|
273
|
+
}
|
|
274
|
+
function loadArtifactItems(adapter, memberships, bounds, includeConnectorEnrichments) {
|
|
275
|
+
const artifactMemberships = memberships.filter((membership) => membership.source_type === 'artifact');
|
|
276
|
+
return artifactMemberships
|
|
277
|
+
.map((membership) => {
|
|
278
|
+
const connectorEvent = loadArtifactConnectorEvent(adapter, membership.source_id);
|
|
279
|
+
const eventDatetime = connectorEvent?.event_datetime ?? null;
|
|
280
|
+
return {
|
|
281
|
+
item_type: 'artifact',
|
|
282
|
+
source_type: 'artifact',
|
|
283
|
+
source_id: membership.source_id,
|
|
284
|
+
source_locator: connectorEvent?.artifact_locator ??
|
|
285
|
+
connectorEvent?.source_locator ??
|
|
286
|
+
membership.source_id,
|
|
287
|
+
event_datetime: eventDatetime,
|
|
288
|
+
event_date: connectorEvent?.event_date ?? eventDateFromMs(eventDatetime),
|
|
289
|
+
title: connectorEvent?.artifact_title ?? connectorEvent?.title ?? membership.source_id,
|
|
290
|
+
summary: connectorEvent?.content ?? null,
|
|
291
|
+
role: membership.role,
|
|
292
|
+
confidence: membership.confidence,
|
|
293
|
+
membership_reason: membership.reason,
|
|
294
|
+
...(includeConnectorEnrichments
|
|
295
|
+
? { connector_event: connectorEventSnapshot(connectorEvent) }
|
|
296
|
+
: {}),
|
|
297
|
+
};
|
|
298
|
+
})
|
|
299
|
+
.filter((item) => withinBounds(item.event_datetime, bounds));
|
|
300
|
+
}
|
|
301
|
+
function caseTimelineRange(adapter, input) {
|
|
302
|
+
const bounds = parseBounds(input);
|
|
303
|
+
const limit = normalizeLimit(input.limit);
|
|
304
|
+
const resolution = (0, store_js_1.resolveCanonicalCaseChain)(adapter, input.case_id);
|
|
305
|
+
const chainResult = (0, store_js_1.listActiveCorrectionsForCase)(adapter, input.case_id);
|
|
306
|
+
const chain = chainResult.chain;
|
|
307
|
+
const memberships = (0, store_js_1.listActiveMembershipsForCaseChain)(adapter, chain);
|
|
308
|
+
const items = [
|
|
309
|
+
...loadDecisionItems(adapter, memberships, bounds),
|
|
310
|
+
...loadEventItems(adapter, memberships, bounds),
|
|
311
|
+
...loadObservationItems(adapter, memberships, bounds, input.include_connector_enrichments === true),
|
|
312
|
+
...loadArtifactItems(adapter, memberships, bounds, input.include_connector_enrichments === true),
|
|
313
|
+
].sort(compareItemsAsc);
|
|
314
|
+
const orderedItems = input.order === 'desc' ? [...items].reverse() : items;
|
|
315
|
+
return {
|
|
316
|
+
terminal_case_id: resolution.terminal_case_id,
|
|
317
|
+
resolved_via_case_id: resolution.resolved_via_case_id,
|
|
318
|
+
chain,
|
|
319
|
+
items: orderedItems.slice(0, limit),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=timeline-range.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
export interface SweepStaleCaseMembershipsOptions {
|
|
3
|
+
now?: string;
|
|
4
|
+
limit?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SweepStaleCaseMembershipsResult {
|
|
7
|
+
scanned_count: number;
|
|
8
|
+
stale_count: number;
|
|
9
|
+
locked_stale_count: number;
|
|
10
|
+
audit_event_ids: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function sweepStaleCaseMemberships(adapter: DatabaseAdapter, options?: SweepStaleCaseMembershipsOptions): SweepStaleCaseMembershipsResult;
|
|
13
|
+
//# sourceMappingURL=tombstone-sweeper.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sweepStaleCaseMemberships = sweepStaleCaseMemberships;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const canonicalize_js_1 = require("../canonicalize.js");
|
|
6
|
+
const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
|
|
7
|
+
const SWEEP_SOURCE_TYPES = ['decision', 'event', 'observation'];
|
|
8
|
+
const SOURCE_EXISTS_SQL = {
|
|
9
|
+
decision: 'SELECT 1 AS id FROM decisions WHERE id = ?',
|
|
10
|
+
event: 'SELECT 1 AS id FROM entity_timeline_events WHERE id = ?',
|
|
11
|
+
observation: 'SELECT 1 AS id FROM entity_observations WHERE id = ?',
|
|
12
|
+
};
|
|
13
|
+
function normalizeNow(value) {
|
|
14
|
+
return value ?? new Date().toISOString();
|
|
15
|
+
}
|
|
16
|
+
function createdAtMs(now) {
|
|
17
|
+
const parsed = Date.parse(now);
|
|
18
|
+
return Number.isFinite(parsed) ? parsed : Date.now();
|
|
19
|
+
}
|
|
20
|
+
function isSweptSourceType(value) {
|
|
21
|
+
return SWEEP_SOURCE_TYPES.includes(value);
|
|
22
|
+
}
|
|
23
|
+
function readCandidates(adapter, limit) {
|
|
24
|
+
const sql = `
|
|
25
|
+
SELECT source_type, source_id
|
|
26
|
+
FROM case_memberships
|
|
27
|
+
WHERE status IN ('active', 'candidate', 'removed', 'excluded')
|
|
28
|
+
AND source_type IN ('decision', 'event', 'observation')
|
|
29
|
+
GROUP BY source_type, source_id
|
|
30
|
+
ORDER BY source_type ASC, source_id ASC
|
|
31
|
+
${limit === undefined ? '' : 'LIMIT ?'}
|
|
32
|
+
`;
|
|
33
|
+
const stmt = adapter.prepare(sql);
|
|
34
|
+
return (limit === undefined ? stmt.all() : stmt.all(limit));
|
|
35
|
+
}
|
|
36
|
+
function sourceExists(adapter, sourceType, sourceId) {
|
|
37
|
+
const row = adapter.prepare(SOURCE_EXISTS_SQL[sourceType]).get(sourceId);
|
|
38
|
+
return row !== undefined;
|
|
39
|
+
}
|
|
40
|
+
function countTransitionTargets(adapter, sourceType, sourceId) {
|
|
41
|
+
return adapter
|
|
42
|
+
.prepare(`
|
|
43
|
+
SELECT COUNT(*) AS count,
|
|
44
|
+
SUM(CASE WHEN user_locked = 1 THEN 1 ELSE 0 END) AS locked_count
|
|
45
|
+
FROM case_memberships
|
|
46
|
+
WHERE status <> 'stale'
|
|
47
|
+
AND source_type = ?
|
|
48
|
+
AND source_id = ?
|
|
49
|
+
`)
|
|
50
|
+
.get(sourceType, sourceId);
|
|
51
|
+
}
|
|
52
|
+
function markSourceStale(adapter, sourceType, sourceId, now) {
|
|
53
|
+
const result = adapter
|
|
54
|
+
.prepare(`
|
|
55
|
+
UPDATE case_memberships
|
|
56
|
+
SET status = 'stale',
|
|
57
|
+
updated_at = ?
|
|
58
|
+
WHERE status <> 'stale'
|
|
59
|
+
AND source_type = ?
|
|
60
|
+
AND source_id = ?
|
|
61
|
+
AND NOT EXISTS (${SOURCE_EXISTS_SQL[sourceType]})
|
|
62
|
+
`)
|
|
63
|
+
.run(now, sourceType, sourceId, sourceId);
|
|
64
|
+
return result.changes;
|
|
65
|
+
}
|
|
66
|
+
function insertTombstoneAuditEvent(input) {
|
|
67
|
+
const eventId = `me_${(0, node_crypto_1.randomUUID)()}`;
|
|
68
|
+
input.adapter
|
|
69
|
+
.prepare(`
|
|
70
|
+
INSERT INTO memory_events (
|
|
71
|
+
event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
72
|
+
scope_refs, evidence_refs, reason, created_at
|
|
73
|
+
)
|
|
74
|
+
VALUES (?, 'case.membership_tombstoned', 'system', NULL, NULL, ?, ?, ?, ?, ?)
|
|
75
|
+
`)
|
|
76
|
+
.run(eventId, 'case:tombstone-sweeper', (0, canonicalize_js_1.canonicalizeJSON)([{ kind: 'project', id: 'case-first' }]), (0, canonicalize_js_1.canonicalizeJSON)(input.sources), (0, canonicalize_js_1.canonicalizeJSON)({
|
|
77
|
+
stale_count: input.stale_count,
|
|
78
|
+
locked_stale_count: input.locked_stale_count,
|
|
79
|
+
}), createdAtMs(input.now));
|
|
80
|
+
return eventId;
|
|
81
|
+
}
|
|
82
|
+
function sweepStaleCaseMemberships(adapter, options = {}) {
|
|
83
|
+
const sweepAdapter = adapter;
|
|
84
|
+
return (0, sqlite_transaction_js_1.runImmediateTransaction)(sweepAdapter, () => {
|
|
85
|
+
const now = normalizeNow(options.now);
|
|
86
|
+
const candidates = readCandidates(sweepAdapter, options.limit);
|
|
87
|
+
let staleCount = 0;
|
|
88
|
+
let lockedStaleCount = 0;
|
|
89
|
+
const tombstonedSources = [];
|
|
90
|
+
for (const candidate of candidates) {
|
|
91
|
+
if (!isSweptSourceType(candidate.source_type)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (sourceExists(sweepAdapter, candidate.source_type, candidate.source_id)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const before = countTransitionTargets(sweepAdapter, candidate.source_type, candidate.source_id);
|
|
98
|
+
if (before.count === 0) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const changes = markSourceStale(sweepAdapter, candidate.source_type, candidate.source_id, now);
|
|
102
|
+
if (changes === 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
staleCount += changes;
|
|
106
|
+
lockedStaleCount += Math.min(changes, before.locked_count ?? 0);
|
|
107
|
+
tombstonedSources.push(candidate);
|
|
108
|
+
}
|
|
109
|
+
const auditEventIds = staleCount > 0
|
|
110
|
+
? [
|
|
111
|
+
insertTombstoneAuditEvent({
|
|
112
|
+
adapter: sweepAdapter,
|
|
113
|
+
now,
|
|
114
|
+
stale_count: staleCount,
|
|
115
|
+
locked_stale_count: lockedStaleCount,
|
|
116
|
+
sources: tombstonedSources,
|
|
117
|
+
}),
|
|
118
|
+
]
|
|
119
|
+
: [];
|
|
120
|
+
return {
|
|
121
|
+
scanned_count: candidates.length,
|
|
122
|
+
stale_count: staleCount,
|
|
123
|
+
locked_stale_count: lockedStaleCount,
|
|
124
|
+
audit_event_ids: auditEventIds,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=tombstone-sweeper.js.map
|