@jungjaehoon/mama-core 1.8.1 → 2.0.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/dist/agent-graph/alias-write.js +3 -0
- package/dist/agent-graph/entity-resolve.js +3 -7
- package/dist/agent-graph/graph-query.js +5 -4
- package/dist/agent-graph/types.d.ts +30 -0
- package/dist/cases/store.d.ts +0 -4
- package/dist/cases/store.js +0 -66
- package/dist/cases/wiki-page-index.d.ts +0 -13
- package/dist/cases/wiki-page-index.js +0 -153
- package/dist/connectors/event-index.d.ts +1 -22
- package/dist/connectors/event-index.js +0 -157
- package/dist/context-compile/channel-grant.d.ts +46 -0
- package/dist/context-compile/channel-grant.js +66 -0
- package/dist/context-compile/compiler.js +5 -2
- package/dist/context-compile/index.d.ts +1 -0
- package/dist/context-compile/index.js +1 -0
- package/dist/context-compile/packet-store.d.ts +0 -1
- package/dist/context-compile/packet-store.js +0 -16
- package/dist/context-compile/source-readers.d.ts +1 -0
- package/dist/context-compile/source-readers.js +156 -23
- package/dist/context-compile/types.d.ts +17 -0
- package/dist/context-compile/visibility.js +28 -4
- package/dist/db-adapter/node-sqlite-adapter.js +4 -0
- package/dist/db-manager.d.ts +0 -13
- package/dist/db-manager.js +0 -66
- package/dist/decision-formatter.d.ts +0 -11
- package/dist/decision-formatter.js +0 -122
- package/dist/decision-tracker.d.ts +0 -12
- package/dist/decision-tracker.js +0 -50
- package/dist/edges/ref-validation.d.ts +1 -2
- package/dist/edges/ref-validation.js +33 -13
- package/dist/edges/store.d.ts +1 -3
- package/dist/edges/store.js +0 -214
- package/dist/edges/types.d.ts +10 -0
- package/dist/embedding-client.d.ts +0 -17
- package/dist/embedding-client.js +0 -80
- package/dist/embedding-server/mobile/auth.d.ts +1 -10
- package/dist/embedding-server/mobile/auth.js +0 -15
- package/dist/embeddings.d.ts +0 -12
- package/dist/embeddings.js +0 -68
- package/dist/entities/audit-metrics.d.ts +0 -5
- package/dist/entities/audit-metrics.js +0 -4
- package/dist/entities/candidate-generator.d.ts +1 -2
- package/dist/entities/candidate-generator.js +0 -412
- package/dist/entities/entity-list.d.ts +0 -1
- package/dist/entities/entity-list.js +0 -96
- package/dist/entities/entity-orphan-list.d.ts +0 -9
- package/dist/entities/entity-orphan-list.js +0 -88
- package/dist/entities/entity-search.d.ts +0 -1
- package/dist/entities/entity-search.js +0 -147
- package/dist/entities/errors.d.ts +0 -10
- package/dist/entities/errors.js +1 -25
- package/dist/entities/lineage-store.d.ts +0 -23
- package/dist/entities/lineage-store.js +0 -104
- package/dist/entities/policy-store.d.ts +0 -9
- package/dist/entities/policy-store.js +0 -188
- package/dist/entities/provenance-query.d.ts +0 -1
- package/dist/entities/provenance-query.js +0 -164
- package/dist/entities/resolution-engine.d.ts +1 -8
- package/dist/entities/resolution-engine.js +0 -66
- package/dist/entities/rollback-preview.d.ts +0 -4
- package/dist/entities/rollback-preview.js +0 -232
- package/dist/entities/store.d.ts +1 -10
- package/dist/entities/store.js +0 -49
- package/dist/index.d.ts +15 -31
- package/dist/index.js +4 -46
- package/dist/mama-api.d.ts +19 -1
- package/dist/mama-api.js +97 -2
- package/dist/memory/api.d.ts +9 -0
- package/dist/memory/api.js +68 -4
- package/dist/memory/event-store.d.ts +0 -1
- package/dist/memory/event-store.js +0 -15
- package/dist/memory/finding-store.d.ts +0 -1
- package/dist/memory/finding-store.js +0 -12
- package/dist/memory/scope-backfill.d.ts +0 -3
- package/dist/memory/scope-backfill.js +0 -74
- package/dist/memory/scope-store.d.ts +0 -1
- package/dist/memory/scope-store.js +0 -4
- package/dist/memory/truth-store.d.ts +0 -3
- package/dist/memory/truth-store.js +0 -22
- package/dist/memory/types.d.ts +0 -1
- package/dist/memory/types.js +0 -13
- package/dist/memory-store.d.ts +0 -16
- package/dist/memory-store.js +0 -51
- package/dist/ollama-client.d.ts +0 -9
- package/dist/ollama-client.js +0 -125
- package/dist/outcome-tracker.d.ts +0 -14
- package/dist/outcome-tracker.js +0 -44
- package/dist/progress-indicator.d.ts +0 -4
- package/dist/progress-indicator.js +0 -7
- package/dist/provenance/source-ref.d.ts +0 -9
- package/dist/provenance/source-ref.js +0 -145
- package/dist/query-intent.d.ts +0 -4
- package/dist/query-intent.js +0 -30
- package/dist/relevance-scorer.d.ts +0 -9
- package/dist/relevance-scorer.js +0 -81
- package/dist/search/feedback-store.d.ts +0 -1
- package/dist/search/feedback-store.js +0 -4
- package/dist/search/ranker-rescore.d.ts +0 -3
- package/dist/search/ranker-rescore.js +0 -27
- package/dist/search/ranker-trainer.d.ts +0 -8
- package/dist/search/ranker-trainer.js +0 -376
- package/dist/tier-validator.d.ts +0 -12
- package/dist/tier-validator.js +0 -56
- package/package.json +1 -10
- package/dist/cases/case-links.d.ts +0 -116
- package/dist/cases/case-links.js +0 -794
- package/dist/cases/composition-overrides.d.ts +0 -90
- package/dist/cases/composition-overrides.js +0 -724
- package/dist/cases/corrections.d.ts +0 -156
- package/dist/cases/corrections.js +0 -1045
- package/dist/cases/freshness.d.ts +0 -57
- package/dist/cases/freshness.js +0 -283
- package/dist/cases/live-state.d.ts +0 -48
- package/dist/cases/live-state.js +0 -256
- package/dist/cases/membership-explain.d.ts +0 -56
- package/dist/cases/membership-explain.js +0 -140
- package/dist/cases/membership-matcher.d.ts +0 -23
- package/dist/cases/membership-matcher.js +0 -193
- package/dist/cases/merge-split.d.ts +0 -61
- package/dist/cases/merge-split.js +0 -368
- package/dist/cases/role-inference.d.ts +0 -10
- package/dist/cases/role-inference.js +0 -85
- package/dist/cases/sqlite-transaction.d.ts +0 -10
- package/dist/cases/sqlite-transaction.js +0 -62
- package/dist/cases/target-ref.d.ts +0 -25
- package/dist/cases/target-ref.js +0 -22
- package/dist/cases/tombstone-sweeper.d.ts +0 -13
- package/dist/cases/tombstone-sweeper.js +0 -128
- package/dist/entities/entity-impact.d.ts +0 -41
- package/dist/entities/entity-impact.js +0 -140
- package/dist/entities/exact-merge-backfill.d.ts +0 -11
- package/dist/entities/exact-merge-backfill.js +0 -161
- package/dist/entities/lineage-backfill.d.ts +0 -12
- package/dist/entities/lineage-backfill.js +0 -101
- package/dist/memory-inject.d.ts +0 -24
- package/dist/memory-inject.js +0 -116
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
-
export type CaseFreshnessState = 'fresh' | 'stale' | 'drifted' | 'unknown';
|
|
3
|
-
export interface CaseFreshnessReason {
|
|
4
|
-
code: 'missing_compile' | 'activity_after_compile' | 'state_update_after_compile' | 'wiki_index_missing' | 'timestamps_absent';
|
|
5
|
-
penalty: number;
|
|
6
|
-
detail?: string;
|
|
7
|
-
}
|
|
8
|
-
export interface CaseFreshnessCalculation {
|
|
9
|
-
freshness_score: number;
|
|
10
|
-
freshness_state: CaseFreshnessState;
|
|
11
|
-
freshness_score_is_drifted: 0 | 1;
|
|
12
|
-
freshness_drift_threshold: number;
|
|
13
|
-
reasons: CaseFreshnessReason[];
|
|
14
|
-
freshness_reason_json: string;
|
|
15
|
-
}
|
|
16
|
-
export interface CaseFreshnessResult extends CaseFreshnessCalculation {
|
|
17
|
-
case_id: string;
|
|
18
|
-
terminal_case_id: string;
|
|
19
|
-
resolved_via_case_id: string | null;
|
|
20
|
-
chain: string[];
|
|
21
|
-
freshness_checked_at: string;
|
|
22
|
-
changed: boolean;
|
|
23
|
-
}
|
|
24
|
-
export interface SweepCaseFreshnessInput {
|
|
25
|
-
case_ids?: string[];
|
|
26
|
-
now?: string;
|
|
27
|
-
drift_threshold?: number;
|
|
28
|
-
}
|
|
29
|
-
export interface SweepCaseFreshnessResult {
|
|
30
|
-
checked: number;
|
|
31
|
-
fresh: number;
|
|
32
|
-
stale: number;
|
|
33
|
-
drifted: number;
|
|
34
|
-
drifted_case_ids: string[];
|
|
35
|
-
results: CaseFreshnessResult[];
|
|
36
|
-
rejected?: Array<{
|
|
37
|
-
case_id: string;
|
|
38
|
-
code: 'case.terminal_status';
|
|
39
|
-
message: string;
|
|
40
|
-
}>;
|
|
41
|
-
}
|
|
42
|
-
export declare function calculateCaseFreshness(input: {
|
|
43
|
-
last_activity_at?: string | null;
|
|
44
|
-
state_updated_at?: string | null;
|
|
45
|
-
compiled_at?: string | null;
|
|
46
|
-
wiki_index_updated_at?: string | null;
|
|
47
|
-
current_wiki_path?: string | null;
|
|
48
|
-
drift_threshold?: number;
|
|
49
|
-
now?: string;
|
|
50
|
-
}): CaseFreshnessCalculation;
|
|
51
|
-
export declare function sweepCaseFreshness(adapter: DatabaseAdapter, input?: SweepCaseFreshnessInput): SweepCaseFreshnessResult;
|
|
52
|
-
export declare function listDriftedCases(adapter: DatabaseAdapter): Array<{
|
|
53
|
-
case_id: string;
|
|
54
|
-
freshness_score: number;
|
|
55
|
-
freshness_checked_at: string | null;
|
|
56
|
-
}>;
|
|
57
|
-
//# sourceMappingURL=freshness.d.ts.map
|
package/dist/cases/freshness.js
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateCaseFreshness = calculateCaseFreshness;
|
|
4
|
-
exports.sweepCaseFreshness = sweepCaseFreshness;
|
|
5
|
-
exports.listDriftedCases = listDriftedCases;
|
|
6
|
-
const node_crypto_1 = require("node:crypto");
|
|
7
|
-
const canonicalize_js_1 = require("../canonicalize.js");
|
|
8
|
-
const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
|
|
9
|
-
const store_js_1 = require("./store.js");
|
|
10
|
-
const DEFAULT_DRIFT_THRESHOLD = 0.5;
|
|
11
|
-
const TERMINAL_CASE_STATUSES = new Set(['merged', 'archived', 'split']);
|
|
12
|
-
function parseRequiredTimestamp(value, errorMessage) {
|
|
13
|
-
const parsed = Date.parse(value);
|
|
14
|
-
if (!Number.isFinite(parsed)) {
|
|
15
|
-
throw new Error(errorMessage);
|
|
16
|
-
}
|
|
17
|
-
return parsed;
|
|
18
|
-
}
|
|
19
|
-
function normalizeNow(value) {
|
|
20
|
-
if (value === undefined) {
|
|
21
|
-
return new Date().toISOString();
|
|
22
|
-
}
|
|
23
|
-
return new Date(parseRequiredTimestamp(value, 'invalid now timestamp')).toISOString();
|
|
24
|
-
}
|
|
25
|
-
function createdAtMs(value) {
|
|
26
|
-
return parseRequiredTimestamp(value, 'invalid now timestamp');
|
|
27
|
-
}
|
|
28
|
-
function timestampMs(value) {
|
|
29
|
-
if (!value) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const parsed = Date.parse(value);
|
|
33
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
34
|
-
}
|
|
35
|
-
function normalizedThreshold(value) {
|
|
36
|
-
if (value === undefined) {
|
|
37
|
-
return DEFAULT_DRIFT_THRESHOLD;
|
|
38
|
-
}
|
|
39
|
-
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
40
|
-
throw new Error('freshness drift_threshold must be between 0 and 1.');
|
|
41
|
-
}
|
|
42
|
-
return Number(value.toFixed(4));
|
|
43
|
-
}
|
|
44
|
-
function score(value) {
|
|
45
|
-
return Number(Math.max(0, Math.min(1, value)).toFixed(4));
|
|
46
|
-
}
|
|
47
|
-
function resolveChain(adapter, caseId) {
|
|
48
|
-
try {
|
|
49
|
-
return (0, store_js_1.resolveCanonicalCaseChain)(adapter, caseId);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
if (error instanceof Error && /not found/i.test(error.message)) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function loadCase(adapter, caseId) {
|
|
59
|
-
const row = adapter
|
|
60
|
-
.prepare(`
|
|
61
|
-
SELECT case_id, status, current_wiki_path, last_activity_at, state_updated_at,
|
|
62
|
-
compiled_at, freshness_score, freshness_state, freshness_score_is_drifted,
|
|
63
|
-
freshness_drift_threshold, freshness_checked_at, freshness_reason_json
|
|
64
|
-
FROM case_truth
|
|
65
|
-
WHERE case_id = ?
|
|
66
|
-
`)
|
|
67
|
-
.get(caseId);
|
|
68
|
-
return row ?? null;
|
|
69
|
-
}
|
|
70
|
-
function wikiIndexUpdatedAt(adapter, caseId) {
|
|
71
|
-
const row = adapter
|
|
72
|
-
.prepare(`
|
|
73
|
-
SELECT updated_at
|
|
74
|
-
FROM wiki_page_index
|
|
75
|
-
WHERE case_id = ?
|
|
76
|
-
AND page_type = 'case'
|
|
77
|
-
ORDER BY updated_at DESC, page_id ASC
|
|
78
|
-
LIMIT 1
|
|
79
|
-
`)
|
|
80
|
-
.get(caseId);
|
|
81
|
-
return row?.updated_at ?? null;
|
|
82
|
-
}
|
|
83
|
-
function allCandidateCaseIds(adapter) {
|
|
84
|
-
const rows = adapter
|
|
85
|
-
.prepare(`
|
|
86
|
-
SELECT case_id
|
|
87
|
-
FROM case_truth
|
|
88
|
-
WHERE status NOT IN ('merged','archived','split')
|
|
89
|
-
ORDER BY case_id ASC
|
|
90
|
-
`)
|
|
91
|
-
.all();
|
|
92
|
-
return rows.map((row) => row.case_id);
|
|
93
|
-
}
|
|
94
|
-
function insertDriftEvent(input) {
|
|
95
|
-
input.adapter
|
|
96
|
-
.prepare(`
|
|
97
|
-
INSERT INTO memory_events (
|
|
98
|
-
event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
99
|
-
scope_refs, evidence_refs, reason, created_at
|
|
100
|
-
)
|
|
101
|
-
VALUES (?, 'case.freshness_drifted', 'system', NULL, NULL, ?, ?, ?, ?, ?)
|
|
102
|
-
`)
|
|
103
|
-
.run(`me_${(0, node_crypto_1.randomUUID)()}`, `case:${input.case_id}`, (0, canonicalize_js_1.canonicalizeJSON)([{ type: 'case', id: input.case_id }]), (0, canonicalize_js_1.canonicalizeJSON)([input.case_id]), (0, canonicalize_js_1.canonicalizeJSON)({
|
|
104
|
-
freshness_score: input.result.freshness_score,
|
|
105
|
-
freshness_drift_threshold: input.result.freshness_drift_threshold,
|
|
106
|
-
reasons: input.result.reasons,
|
|
107
|
-
}), createdAtMs(input.now));
|
|
108
|
-
}
|
|
109
|
-
function hasFreshnessChanged(row, result) {
|
|
110
|
-
return (row.freshness_score !== result.freshness_score ||
|
|
111
|
-
row.freshness_state !== result.freshness_state ||
|
|
112
|
-
Number(row.freshness_score_is_drifted) !== result.freshness_score_is_drifted ||
|
|
113
|
-
row.freshness_drift_threshold !== result.freshness_drift_threshold ||
|
|
114
|
-
row.freshness_reason_json !== result.freshness_reason_json);
|
|
115
|
-
}
|
|
116
|
-
function calculateCaseFreshness(input) {
|
|
117
|
-
const threshold = normalizedThreshold(input.drift_threshold);
|
|
118
|
-
const reasons = [];
|
|
119
|
-
let value = 1.0;
|
|
120
|
-
const compiledMs = timestampMs(input.compiled_at);
|
|
121
|
-
const activityMs = timestampMs(input.last_activity_at);
|
|
122
|
-
const stateUpdatedMs = timestampMs(input.state_updated_at);
|
|
123
|
-
const wikiUpdatedMs = timestampMs(input.wiki_index_updated_at);
|
|
124
|
-
const hasWikiPath = Boolean(input.current_wiki_path && input.current_wiki_path.trim().length > 0);
|
|
125
|
-
if (compiledMs === null) {
|
|
126
|
-
value -= 0.5;
|
|
127
|
-
reasons.push({ code: 'missing_compile', penalty: 0.5 });
|
|
128
|
-
}
|
|
129
|
-
if (activityMs !== null && compiledMs !== null && activityMs > compiledMs) {
|
|
130
|
-
value -= 0.35;
|
|
131
|
-
reasons.push({ code: 'activity_after_compile', penalty: 0.35 });
|
|
132
|
-
}
|
|
133
|
-
if (stateUpdatedMs !== null && compiledMs !== null && stateUpdatedMs > compiledMs) {
|
|
134
|
-
value -= 0.25;
|
|
135
|
-
reasons.push({ code: 'state_update_after_compile', penalty: 0.25 });
|
|
136
|
-
}
|
|
137
|
-
if (hasWikiPath && wikiUpdatedMs === null) {
|
|
138
|
-
value -= 0.25;
|
|
139
|
-
reasons.push({
|
|
140
|
-
code: 'wiki_index_missing',
|
|
141
|
-
penalty: 0.25,
|
|
142
|
-
detail: input.current_wiki_path ?? '',
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
const timestampsAbsent = compiledMs === null && activityMs === null && stateUpdatedMs === null;
|
|
146
|
-
if (timestampsAbsent) {
|
|
147
|
-
reasons.push({ code: 'timestamps_absent', penalty: 0 });
|
|
148
|
-
}
|
|
149
|
-
const freshnessScore = score(value);
|
|
150
|
-
let state;
|
|
151
|
-
if (timestampsAbsent) {
|
|
152
|
-
state = 'unknown';
|
|
153
|
-
}
|
|
154
|
-
else if (freshnessScore >= 0.8) {
|
|
155
|
-
state = 'fresh';
|
|
156
|
-
}
|
|
157
|
-
else if (freshnessScore < threshold) {
|
|
158
|
-
state = 'drifted';
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
state = 'stale';
|
|
162
|
-
}
|
|
163
|
-
const result = {
|
|
164
|
-
freshness_score: freshnessScore,
|
|
165
|
-
freshness_state: state,
|
|
166
|
-
freshness_score_is_drifted: state === 'drifted' ? 1 : 0,
|
|
167
|
-
freshness_drift_threshold: threshold,
|
|
168
|
-
reasons,
|
|
169
|
-
freshness_reason_json: (0, canonicalize_js_1.canonicalizeJSON)(reasons),
|
|
170
|
-
};
|
|
171
|
-
return result;
|
|
172
|
-
}
|
|
173
|
-
function sweepCaseFreshness(adapter, input = {}) {
|
|
174
|
-
const freshnessAdapter = adapter;
|
|
175
|
-
return (0, sqlite_transaction_js_1.runImmediateTransaction)(freshnessAdapter, () => {
|
|
176
|
-
const now = normalizeNow(input.now);
|
|
177
|
-
const requestedCaseIds = input.case_ids ?? allCandidateCaseIds(freshnessAdapter);
|
|
178
|
-
const seenTerminalIds = new Set();
|
|
179
|
-
const results = [];
|
|
180
|
-
const rejected = [];
|
|
181
|
-
for (const requestedCaseId of requestedCaseIds) {
|
|
182
|
-
const resolution = resolveChain(freshnessAdapter, requestedCaseId);
|
|
183
|
-
if (!resolution) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (seenTerminalIds.has(resolution.terminal_case_id)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const row = loadCase(freshnessAdapter, resolution.terminal_case_id);
|
|
190
|
-
if (!row) {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (TERMINAL_CASE_STATUSES.has(row.status)) {
|
|
194
|
-
if (input.case_ids) {
|
|
195
|
-
rejected.push({
|
|
196
|
-
case_id: requestedCaseId,
|
|
197
|
-
code: 'case.terminal_status',
|
|
198
|
-
message: `Freshness cannot be written to terminal case status ${row.status}.`,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
seenTerminalIds.add(resolution.terminal_case_id);
|
|
204
|
-
const chain = (0, store_js_1.expandCaseChainForAssembly)(freshnessAdapter, resolution.terminal_case_id, resolution.chain);
|
|
205
|
-
const calculation = calculateCaseFreshness({
|
|
206
|
-
last_activity_at: row.last_activity_at,
|
|
207
|
-
state_updated_at: row.state_updated_at,
|
|
208
|
-
compiled_at: row.compiled_at,
|
|
209
|
-
wiki_index_updated_at: wikiIndexUpdatedAt(freshnessAdapter, resolution.terminal_case_id),
|
|
210
|
-
current_wiki_path: row.current_wiki_path,
|
|
211
|
-
drift_threshold: input.drift_threshold,
|
|
212
|
-
now,
|
|
213
|
-
});
|
|
214
|
-
const changed = hasFreshnessChanged(row, calculation);
|
|
215
|
-
const driftedTransition = changed && row.freshness_state !== 'drifted' && calculation.freshness_state === 'drifted';
|
|
216
|
-
if (changed) {
|
|
217
|
-
freshnessAdapter
|
|
218
|
-
.prepare(`
|
|
219
|
-
UPDATE case_truth
|
|
220
|
-
SET freshness_score = ?,
|
|
221
|
-
freshness_state = ?,
|
|
222
|
-
freshness_score_is_drifted = ?,
|
|
223
|
-
freshness_drift_threshold = ?,
|
|
224
|
-
freshness_checked_at = ?,
|
|
225
|
-
freshness_reason_json = ?
|
|
226
|
-
WHERE case_id = ?
|
|
227
|
-
`)
|
|
228
|
-
.run(calculation.freshness_score, calculation.freshness_state, calculation.freshness_score_is_drifted, calculation.freshness_drift_threshold, now, calculation.freshness_reason_json, resolution.terminal_case_id);
|
|
229
|
-
if (driftedTransition) {
|
|
230
|
-
insertDriftEvent({
|
|
231
|
-
adapter: freshnessAdapter,
|
|
232
|
-
case_id: resolution.terminal_case_id,
|
|
233
|
-
result: calculation,
|
|
234
|
-
now,
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
freshnessAdapter
|
|
240
|
-
.prepare(`
|
|
241
|
-
UPDATE case_truth
|
|
242
|
-
SET freshness_checked_at = ?
|
|
243
|
-
WHERE case_id = ?
|
|
244
|
-
`)
|
|
245
|
-
.run(now, resolution.terminal_case_id);
|
|
246
|
-
}
|
|
247
|
-
results.push({
|
|
248
|
-
case_id: requestedCaseId,
|
|
249
|
-
terminal_case_id: resolution.terminal_case_id,
|
|
250
|
-
resolved_via_case_id: resolution.resolved_via_case_id,
|
|
251
|
-
chain,
|
|
252
|
-
freshness_checked_at: now,
|
|
253
|
-
changed,
|
|
254
|
-
...calculation,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
return {
|
|
258
|
-
checked: results.length,
|
|
259
|
-
fresh: results.filter((result) => result.freshness_state === 'fresh').length,
|
|
260
|
-
stale: results.filter((result) => result.freshness_state === 'stale').length,
|
|
261
|
-
drifted: results.filter((result) => result.freshness_state === 'drifted').length,
|
|
262
|
-
drifted_case_ids: results
|
|
263
|
-
.filter((result) => result.freshness_state === 'drifted')
|
|
264
|
-
.map((result) => result.terminal_case_id),
|
|
265
|
-
results,
|
|
266
|
-
...(rejected.length > 0 ? { rejected } : {}),
|
|
267
|
-
};
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
function listDriftedCases(adapter) {
|
|
271
|
-
const rows = adapter
|
|
272
|
-
.prepare(`
|
|
273
|
-
SELECT case_id, freshness_score, freshness_checked_at
|
|
274
|
-
FROM case_truth
|
|
275
|
-
WHERE freshness_state = 'drifted'
|
|
276
|
-
AND freshness_score_is_drifted = 1
|
|
277
|
-
AND status NOT IN ('merged','archived','split')
|
|
278
|
-
ORDER BY freshness_score ASC, case_id ASC
|
|
279
|
-
`)
|
|
280
|
-
.all();
|
|
281
|
-
return rows;
|
|
282
|
-
}
|
|
283
|
-
//# sourceMappingURL=freshness.js.map
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
-
import type { CaseFastWriteStatus as CaseFastWriteStatusFromTypes } from './types.js';
|
|
3
|
-
export type CaseFastWriteErrorCode = 'case.precompile_gap' | 'case.lock_held' | 'case.terminal_status' | 'case.correction_active_conflict';
|
|
4
|
-
export interface CaseMembershipWritePlan {
|
|
5
|
-
source_type: 'decision' | 'event' | 'observation' | 'artifact';
|
|
6
|
-
source_id: string;
|
|
7
|
-
role?: 'requester' | 'implementer' | 'reviewer' | 'observer' | 'affected' | null;
|
|
8
|
-
confidence: number;
|
|
9
|
-
reason: string;
|
|
10
|
-
status: 'active' | 'candidate';
|
|
11
|
-
}
|
|
12
|
-
type CaseFastWriteStatus = CaseFastWriteStatusFromTypes;
|
|
13
|
-
export interface WriteCaseLiveStateInput {
|
|
14
|
-
case_id: string;
|
|
15
|
-
source_event_id: string;
|
|
16
|
-
source_type: 'event';
|
|
17
|
-
status?: CaseFastWriteStatus;
|
|
18
|
-
last_activity_at?: string;
|
|
19
|
-
membership?: CaseMembershipWritePlan;
|
|
20
|
-
actor?: 'memory_agent';
|
|
21
|
-
now?: string;
|
|
22
|
-
}
|
|
23
|
-
export type WriteCaseLiveStateResult = {
|
|
24
|
-
kind: 'applied';
|
|
25
|
-
case_id: string;
|
|
26
|
-
applied_targets: Array<{
|
|
27
|
-
target_kind: 'case_field' | 'membership';
|
|
28
|
-
target_ref_json: string;
|
|
29
|
-
}>;
|
|
30
|
-
skipped_targets: Array<{
|
|
31
|
-
target_kind: 'case_field' | 'membership';
|
|
32
|
-
target_ref_json: string;
|
|
33
|
-
correction_id: string;
|
|
34
|
-
}>;
|
|
35
|
-
audit_event_ids: string[];
|
|
36
|
-
} | {
|
|
37
|
-
kind: 'precompile_gap';
|
|
38
|
-
code: 'case.precompile_gap';
|
|
39
|
-
case_id: string;
|
|
40
|
-
} | {
|
|
41
|
-
kind: 'rejected';
|
|
42
|
-
code: CaseFastWriteErrorCode;
|
|
43
|
-
message: string;
|
|
44
|
-
case_id: string;
|
|
45
|
-
};
|
|
46
|
-
export declare function writeCaseLiveStateFromEvent(adapter: DatabaseAdapter, input: WriteCaseLiveStateInput): WriteCaseLiveStateResult;
|
|
47
|
-
export {};
|
|
48
|
-
//# sourceMappingURL=live-state.d.ts.map
|
package/dist/cases/live-state.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.writeCaseLiveStateFromEvent = writeCaseLiveStateFromEvent;
|
|
4
|
-
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
const canonicalize_js_1 = require("../canonicalize.js");
|
|
6
|
-
const target_ref_js_1 = require("./target-ref.js");
|
|
7
|
-
const sqlite_transaction_js_1 = require("./sqlite-transaction.js");
|
|
8
|
-
const TERMINAL_CASE_STATUSES = new Set(['merged', 'archived', 'split']);
|
|
9
|
-
const FAST_WRITE_STATUSES = new Set([
|
|
10
|
-
'active',
|
|
11
|
-
'blocked',
|
|
12
|
-
'resolved',
|
|
13
|
-
'stale',
|
|
14
|
-
]);
|
|
15
|
-
function isCaseFastWriteStatus(value) {
|
|
16
|
-
return typeof value === 'string' && FAST_WRITE_STATUSES.has(value);
|
|
17
|
-
}
|
|
18
|
-
function normalizeNow(value) {
|
|
19
|
-
return value ?? new Date().toISOString();
|
|
20
|
-
}
|
|
21
|
-
function createdAtMs(now) {
|
|
22
|
-
const parsed = Date.parse(now);
|
|
23
|
-
return Number.isFinite(parsed) ? parsed : Date.now();
|
|
24
|
-
}
|
|
25
|
-
function placeholders(values) {
|
|
26
|
-
if (values.length === 0) {
|
|
27
|
-
throw new Error('Cannot build SQL IN clause for an empty value list.');
|
|
28
|
-
}
|
|
29
|
-
return values.map(() => '?').join(', ');
|
|
30
|
-
}
|
|
31
|
-
function toHashKey(value) {
|
|
32
|
-
return Buffer.from(value).toString('hex');
|
|
33
|
-
}
|
|
34
|
-
function targetPlanForCanonical(target_kind, canonical) {
|
|
35
|
-
return {
|
|
36
|
-
target_kind,
|
|
37
|
-
target_ref_json: canonical.json,
|
|
38
|
-
target_ref_hash: canonical.hash,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function buildTargetPlans(input) {
|
|
42
|
-
const targets = [];
|
|
43
|
-
if (input.status !== undefined) {
|
|
44
|
-
targets.push(targetPlanForCanonical('case_field', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildCaseFieldTargetRef)('status'))));
|
|
45
|
-
}
|
|
46
|
-
if (input.last_activity_at !== undefined) {
|
|
47
|
-
targets.push(targetPlanForCanonical('case_field', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildCaseFieldTargetRef)('last_activity_at'))));
|
|
48
|
-
}
|
|
49
|
-
if (input.membership) {
|
|
50
|
-
targets.push(targetPlanForCanonical('membership', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildMembershipTargetRef)(input.membership.source_type, input.membership.source_id))));
|
|
51
|
-
}
|
|
52
|
-
return targets;
|
|
53
|
-
}
|
|
54
|
-
function readCaseChain(adapter, caseId) {
|
|
55
|
-
const rows = adapter
|
|
56
|
-
.prepare(`
|
|
57
|
-
WITH RECURSIVE case_chain(case_id) AS (
|
|
58
|
-
SELECT case_id
|
|
59
|
-
FROM case_truth
|
|
60
|
-
WHERE case_id = ?
|
|
61
|
-
|
|
62
|
-
UNION
|
|
63
|
-
|
|
64
|
-
SELECT ct.case_id
|
|
65
|
-
FROM case_truth ct
|
|
66
|
-
JOIN case_chain cc ON ct.canonical_case_id = cc.case_id
|
|
67
|
-
)
|
|
68
|
-
SELECT case_id
|
|
69
|
-
FROM case_chain
|
|
70
|
-
`)
|
|
71
|
-
.all(caseId);
|
|
72
|
-
return rows.map((row) => row.case_id);
|
|
73
|
-
}
|
|
74
|
-
function readActiveLocks(adapter, chainIds, targets) {
|
|
75
|
-
if (chainIds.length === 0 || targets.length === 0) {
|
|
76
|
-
return new Map();
|
|
77
|
-
}
|
|
78
|
-
const targetHashes = targets.map((target) => target.target_ref_hash);
|
|
79
|
-
const rows = adapter
|
|
80
|
-
.prepare(`
|
|
81
|
-
SELECT correction_id, case_id, target_kind, field_name, target_ref_json, target_ref_hash
|
|
82
|
-
FROM case_corrections
|
|
83
|
-
WHERE case_id IN (${placeholders(chainIds)})
|
|
84
|
-
AND is_lock_active = 1
|
|
85
|
-
AND reverted_at IS NULL
|
|
86
|
-
AND target_ref_hash IN (${placeholders(targetHashes)})
|
|
87
|
-
`)
|
|
88
|
-
.all(...chainIds, ...targetHashes);
|
|
89
|
-
const locks = new Map();
|
|
90
|
-
for (const row of rows) {
|
|
91
|
-
locks.set(toHashKey(row.target_ref_hash), row);
|
|
92
|
-
}
|
|
93
|
-
return locks;
|
|
94
|
-
}
|
|
95
|
-
function findTarget(targets, target_kind, target_ref_json) {
|
|
96
|
-
return (targets.find((target) => target.target_kind === target_kind && target.target_ref_json === target_ref_json) ?? null);
|
|
97
|
-
}
|
|
98
|
-
function hasLock(locks, target) {
|
|
99
|
-
if (!target) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
return locks.get(toHashKey(target.target_ref_hash)) ?? null;
|
|
103
|
-
}
|
|
104
|
-
function insertSkippedAuditEvent(input) {
|
|
105
|
-
const eventId = `me_${(0, node_crypto_1.randomUUID)()}`;
|
|
106
|
-
input.adapter
|
|
107
|
-
.prepare(`
|
|
108
|
-
INSERT INTO memory_events (
|
|
109
|
-
event_id, event_type, actor, source_turn_id, memory_id, topic,
|
|
110
|
-
scope_refs, evidence_refs, reason, created_at
|
|
111
|
-
)
|
|
112
|
-
VALUES (?, 'case.fast_write_lock_skipped', 'memory_agent', ?, NULL, ?, ?, ?, ?, ?)
|
|
113
|
-
`)
|
|
114
|
-
.run(eventId, input.source_event_id, `case:${input.case_id}`, (0, canonicalize_js_1.canonicalizeJSON)([{ type: 'case', id: input.case_id }]), (0, canonicalize_js_1.canonicalizeJSON)([input.source_event_id]), (0, canonicalize_js_1.canonicalizeJSON)({ skipped_targets: input.skipped_targets }), createdAtMs(input.now));
|
|
115
|
-
return eventId;
|
|
116
|
-
}
|
|
117
|
-
function writeCaseLiveStateFromEvent(adapter, input) {
|
|
118
|
-
const liveAdapter = adapter;
|
|
119
|
-
return (0, sqlite_transaction_js_1.runImmediateTransaction)(liveAdapter, () => {
|
|
120
|
-
const caseRow = liveAdapter
|
|
121
|
-
.prepare(`
|
|
122
|
-
SELECT case_id, status
|
|
123
|
-
FROM case_truth
|
|
124
|
-
WHERE case_id = ?
|
|
125
|
-
`)
|
|
126
|
-
.get(input.case_id);
|
|
127
|
-
if (!caseRow) {
|
|
128
|
-
return { kind: 'precompile_gap', code: 'case.precompile_gap', case_id: input.case_id };
|
|
129
|
-
}
|
|
130
|
-
if (TERMINAL_CASE_STATUSES.has(caseRow.status)) {
|
|
131
|
-
return {
|
|
132
|
-
kind: 'rejected',
|
|
133
|
-
code: 'case.terminal_status',
|
|
134
|
-
message: `Memory-agent cannot update terminal case status ${caseRow.status}.`,
|
|
135
|
-
case_id: input.case_id,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (input.status !== undefined && !isCaseFastWriteStatus(input.status)) {
|
|
139
|
-
return {
|
|
140
|
-
kind: 'rejected',
|
|
141
|
-
code: 'case.terminal_status',
|
|
142
|
-
message: `Memory-agent cannot write terminal case status ${String(input.status)}.`,
|
|
143
|
-
case_id: input.case_id,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
const now = normalizeNow(input.now);
|
|
147
|
-
const chainIds = readCaseChain(liveAdapter, input.case_id);
|
|
148
|
-
const targets = buildTargetPlans(input);
|
|
149
|
-
const locks = readActiveLocks(liveAdapter, chainIds, targets);
|
|
150
|
-
const applied_targets = [];
|
|
151
|
-
const skipped_targets = [];
|
|
152
|
-
const statusTarget = targets[0] ?? null;
|
|
153
|
-
const lastActivityTarget = input.status !== undefined ? (targets[1] ?? null) : (targets[0] ?? null);
|
|
154
|
-
const membershipTarget = input.membership
|
|
155
|
-
? findTarget(targets, 'membership', (0, target_ref_js_1.canonicalTargetRef)((0, target_ref_js_1.buildMembershipTargetRef)(input.membership.source_type, input.membership.source_id)).json)
|
|
156
|
-
: null;
|
|
157
|
-
const statusLock = input.status !== undefined ? hasLock(locks, statusTarget) : null;
|
|
158
|
-
const lastActivityLock = input.last_activity_at !== undefined ? hasLock(locks, lastActivityTarget) : null;
|
|
159
|
-
const membershipLock = input.membership ? hasLock(locks, membershipTarget) : null;
|
|
160
|
-
if (statusLock && statusTarget) {
|
|
161
|
-
skipped_targets.push({
|
|
162
|
-
target_kind: statusTarget.target_kind,
|
|
163
|
-
target_ref_json: statusTarget.target_ref_json,
|
|
164
|
-
correction_id: statusLock.correction_id,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
if (lastActivityLock && lastActivityTarget) {
|
|
168
|
-
skipped_targets.push({
|
|
169
|
-
target_kind: lastActivityTarget.target_kind,
|
|
170
|
-
target_ref_json: lastActivityTarget.target_ref_json,
|
|
171
|
-
correction_id: lastActivityLock.correction_id,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
if (membershipLock && membershipTarget) {
|
|
175
|
-
skipped_targets.push({
|
|
176
|
-
target_kind: membershipTarget.target_kind,
|
|
177
|
-
target_ref_json: membershipTarget.target_ref_json,
|
|
178
|
-
correction_id: membershipLock.correction_id,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
const shouldWriteStatus = input.status !== undefined && !statusLock;
|
|
182
|
-
const shouldWriteLastActivity = input.last_activity_at !== undefined && !lastActivityLock;
|
|
183
|
-
if (shouldWriteStatus || shouldWriteLastActivity) {
|
|
184
|
-
const updateResult = liveAdapter
|
|
185
|
-
.prepare(`
|
|
186
|
-
UPDATE case_truth
|
|
187
|
-
SET status = CASE WHEN ? = 1 THEN ? ELSE status END,
|
|
188
|
-
last_activity_at = CASE WHEN ? = 1 THEN ? ELSE last_activity_at END,
|
|
189
|
-
state_updated_at = ?,
|
|
190
|
-
updated_at = ?
|
|
191
|
-
WHERE case_id = ?
|
|
192
|
-
AND status NOT IN ('merged','archived','split')
|
|
193
|
-
`)
|
|
194
|
-
.run(shouldWriteStatus ? 1 : 0, input.status ?? null, shouldWriteLastActivity ? 1 : 0, input.last_activity_at ?? null, now, now, input.case_id);
|
|
195
|
-
if (updateResult.changes > 0) {
|
|
196
|
-
if (shouldWriteStatus && statusTarget) {
|
|
197
|
-
applied_targets.push({
|
|
198
|
-
target_kind: statusTarget.target_kind,
|
|
199
|
-
target_ref_json: statusTarget.target_ref_json,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
if (shouldWriteLastActivity && lastActivityTarget) {
|
|
203
|
-
applied_targets.push({
|
|
204
|
-
target_kind: lastActivityTarget.target_kind,
|
|
205
|
-
target_ref_json: lastActivityTarget.target_ref_json,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
if (input.membership && !membershipLock && membershipTarget) {
|
|
211
|
-
const membershipResult = liveAdapter
|
|
212
|
-
.prepare(`
|
|
213
|
-
INSERT INTO case_memberships (
|
|
214
|
-
case_id, source_type, source_id, role, confidence, reason, status,
|
|
215
|
-
added_by, added_at, user_locked, updated_at
|
|
216
|
-
)
|
|
217
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 'memory-agent', ?, 0, ?)
|
|
218
|
-
ON CONFLICT(case_id, source_type, source_id) DO UPDATE SET
|
|
219
|
-
role = excluded.role,
|
|
220
|
-
confidence = excluded.confidence,
|
|
221
|
-
reason = excluded.reason,
|
|
222
|
-
status = excluded.status,
|
|
223
|
-
added_by = 'memory-agent',
|
|
224
|
-
updated_at = excluded.updated_at
|
|
225
|
-
WHERE case_memberships.user_locked = 0
|
|
226
|
-
AND case_memberships.status <> 'stale'
|
|
227
|
-
`)
|
|
228
|
-
.run(input.case_id, input.membership.source_type, input.membership.source_id, input.membership.role ?? null, input.membership.confidence, input.membership.reason, input.membership.status, now, now);
|
|
229
|
-
if (membershipResult.changes > 0) {
|
|
230
|
-
applied_targets.push({
|
|
231
|
-
target_kind: membershipTarget.target_kind,
|
|
232
|
-
target_ref_json: membershipTarget.target_ref_json,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const audit_event_ids = skipped_targets.length > 0
|
|
237
|
-
? [
|
|
238
|
-
insertSkippedAuditEvent({
|
|
239
|
-
adapter: liveAdapter,
|
|
240
|
-
case_id: input.case_id,
|
|
241
|
-
source_event_id: input.source_event_id,
|
|
242
|
-
skipped_targets,
|
|
243
|
-
now,
|
|
244
|
-
}),
|
|
245
|
-
]
|
|
246
|
-
: [];
|
|
247
|
-
return {
|
|
248
|
-
kind: 'applied',
|
|
249
|
-
case_id: input.case_id,
|
|
250
|
-
applied_targets,
|
|
251
|
-
skipped_targets,
|
|
252
|
-
audit_event_ids,
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
//# sourceMappingURL=live-state.js.map
|