@shadowforge0/aquifer-memory 1.7.0 → 1.8.1
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/.env.example +8 -0
- package/README.md +66 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +217 -14
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +442 -3
- package/consumers/codex.js +164 -107
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +351 -840
- package/core/backends/capabilities.js +89 -0
- package/core/backends/local.js +430 -0
- package/core/legacy-bootstrap.js +140 -0
- package/core/mcp-manifest.js +66 -2
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +128 -8
- package/core/memory-serving.js +132 -0
- package/core/postgres-migrations.js +533 -0
- package/core/public-session-filter.js +40 -0
- package/core/recall-runtime.js +115 -0
- package/core/scope-attribution.js +279 -0
- package/core/session-checkpoint-producer.js +412 -0
- package/core/session-checkpoints.js +432 -0
- package/core/session-finalization.js +82 -1
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/setup.md +22 -0
- package/package.json +8 -4
- package/schema/014-v1-checkpoint-runs.sql +349 -0
- package/schema/015-v1-evidence-items.sql +92 -0
- package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
- package/schema/017-v1-memory-record-embeddings.sql +25 -0
- package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
- package/scripts/codex-checkpoint-commands.js +464 -0
- package/scripts/codex-checkpoint-runtime.js +520 -0
- package/scripts/codex-recovery.js +105 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { sanitizeSummaryResult } = require('./memory-safety-gate');
|
|
5
|
+
const { buildScopeEnvelope, getScopeByEnvelopeId } = require('./scope-attribution');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_POLICY_VERSION = 'session_checkpoint_producer_v1';
|
|
8
|
+
const DEFAULT_COVERAGE_COORDINATE_SYSTEM = 'codex_sanitized_view_v1';
|
|
9
|
+
const STRUCTURED_SUMMARY_SHAPE = '{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}';
|
|
10
|
+
|
|
11
|
+
function stableJson(value) {
|
|
12
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
13
|
+
if (value && typeof value === 'object') {
|
|
14
|
+
return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${stableJson(value[k])}`).join(',')}}`;
|
|
15
|
+
}
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hashSnapshot(value) {
|
|
20
|
+
return crypto.createHash('sha256').update(stableJson(value)).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function optionalNonNegativeInteger(value) {
|
|
24
|
+
if (value === undefined || value === null || value === '') return null;
|
|
25
|
+
const n = Number(value);
|
|
26
|
+
if (!Number.isInteger(n) || n < 0) return null;
|
|
27
|
+
return n;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function requiredPositiveInteger(value, field) {
|
|
31
|
+
const n = Number(value);
|
|
32
|
+
if (!Number.isSafeInteger(n) || n <= 0) {
|
|
33
|
+
throw new Error(`${field} must be a positive integer`);
|
|
34
|
+
}
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeFinalizationRange(input = {}) {
|
|
39
|
+
const from = Number(input.fromFinalizationIdExclusive ?? input.from_finalization_id_exclusive ?? 0);
|
|
40
|
+
const to = Number(input.toFinalizationIdInclusive ?? input.to_finalization_id_inclusive);
|
|
41
|
+
if (!Number.isSafeInteger(from) || from < 0) {
|
|
42
|
+
throw new Error('fromFinalizationIdExclusive must be a non-negative integer');
|
|
43
|
+
}
|
|
44
|
+
if (!Number.isSafeInteger(to) || to <= from) {
|
|
45
|
+
throw new Error('toFinalizationIdInclusive must be greater than fromFinalizationIdExclusive');
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
fromFinalizationIdExclusive: from,
|
|
49
|
+
toFinalizationIdInclusive: to,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertOkTranscriptView(view = {}) {
|
|
54
|
+
if (!view || view.status !== 'ok') {
|
|
55
|
+
throw new Error(`checkpoint synthesis requires an ok transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof view.text !== 'string') {
|
|
58
|
+
throw new Error('checkpoint synthesis requires view.text');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeCoverageNumber(...values) {
|
|
63
|
+
for (const value of values) {
|
|
64
|
+
const n = optionalNonNegativeInteger(value);
|
|
65
|
+
if (n !== null) return n;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildCheckpointCoverageFromView(view = {}, opts = {}) {
|
|
71
|
+
assertOkTranscriptView(view);
|
|
72
|
+
const explicit = opts.coverage && typeof opts.coverage === 'object'
|
|
73
|
+
? opts.coverage
|
|
74
|
+
: (view.coverage && typeof view.coverage === 'object' ? view.coverage : {});
|
|
75
|
+
const transcript = explicit.transcript && typeof explicit.transcript === 'object' ? explicit.transcript : {};
|
|
76
|
+
const messageCount = Number.isFinite(Number(view.counts?.safeMessageCount))
|
|
77
|
+
? Number(view.counts.safeMessageCount)
|
|
78
|
+
: (Array.isArray(view.messages) ? view.messages.length : 0);
|
|
79
|
+
const text = typeof view.text === 'string' ? view.text : '';
|
|
80
|
+
const fullCharCount = Number.isFinite(Number(view.fullCharCount ?? view.counts?.fullCharCount))
|
|
81
|
+
? Number(view.fullCharCount ?? view.counts.fullCharCount)
|
|
82
|
+
: text.length;
|
|
83
|
+
const coveredUntilMessageIndex = normalizeCoverageNumber(
|
|
84
|
+
opts.coveredUntilMessageIndex,
|
|
85
|
+
explicit.coveredUntilMessageIndex,
|
|
86
|
+
explicit.covered_until_message_index,
|
|
87
|
+
explicit.messageIndex,
|
|
88
|
+
explicit.message_index,
|
|
89
|
+
transcript.coveredUntilMessageIndex,
|
|
90
|
+
transcript.covered_until_message_index
|
|
91
|
+
);
|
|
92
|
+
const coveredUntilChar = normalizeCoverageNumber(
|
|
93
|
+
opts.coveredUntilChar,
|
|
94
|
+
opts.coveredUntilCharIndex,
|
|
95
|
+
explicit.coveredUntilChar,
|
|
96
|
+
explicit.coveredUntilCharIndex,
|
|
97
|
+
explicit.covered_until_char,
|
|
98
|
+
explicit.covered_until_char_index,
|
|
99
|
+
transcript.coveredUntilChar,
|
|
100
|
+
transcript.covered_until_char
|
|
101
|
+
);
|
|
102
|
+
const coveredUntilLine = normalizeCoverageNumber(
|
|
103
|
+
opts.coveredUntilLine,
|
|
104
|
+
explicit.coveredUntilLine,
|
|
105
|
+
explicit.coveredUntilLineIndex,
|
|
106
|
+
explicit.covered_until_line,
|
|
107
|
+
explicit.covered_until_line_index,
|
|
108
|
+
transcript.coveredUntilLine,
|
|
109
|
+
transcript.covered_until_line
|
|
110
|
+
);
|
|
111
|
+
const coveredUntilLineChar = normalizeCoverageNumber(
|
|
112
|
+
opts.coveredUntilLineChar,
|
|
113
|
+
explicit.coveredUntilLineChar,
|
|
114
|
+
explicit.coveredUntilLineCharIndex,
|
|
115
|
+
explicit.covered_until_line_char,
|
|
116
|
+
explicit.covered_until_line_char_index,
|
|
117
|
+
transcript.coveredUntilLineChar,
|
|
118
|
+
transcript.covered_until_line_char
|
|
119
|
+
);
|
|
120
|
+
const coverage = {
|
|
121
|
+
coordinateSystem: explicit.coordinateSystem || explicit.coordinate_system || DEFAULT_COVERAGE_COORDINATE_SYSTEM,
|
|
122
|
+
messageIndexBase: 0,
|
|
123
|
+
charIndexBase: 0,
|
|
124
|
+
semantics: 'coveredUntilChar is the first uncovered zero-based char offset; messages up to coveredUntilMessageIndex are covered.',
|
|
125
|
+
};
|
|
126
|
+
if (coveredUntilMessageIndex !== null) coverage.coveredUntilMessageIndex = coveredUntilMessageIndex;
|
|
127
|
+
if (coveredUntilChar !== null) coverage.coveredUntilChar = coveredUntilChar;
|
|
128
|
+
if (coveredUntilLine !== null) coverage.coveredUntilLine = coveredUntilLine;
|
|
129
|
+
if (coveredUntilLineChar !== null) coverage.coveredUntilLineChar = coveredUntilLineChar;
|
|
130
|
+
if (coverage.coveredUntilMessageIndex === undefined && messageCount > 0) {
|
|
131
|
+
coverage.coveredUntilMessageIndex = messageCount - 1;
|
|
132
|
+
}
|
|
133
|
+
if (coverage.coveredUntilChar === undefined) coverage.coveredUntilChar = fullCharCount;
|
|
134
|
+
return coverage;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function compactText(value, maxChars = 360) {
|
|
138
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
139
|
+
if (text.length <= maxChars) return text;
|
|
140
|
+
return `${text.slice(0, Math.max(0, maxChars - 1)).trim()}...`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function compactCurrentMemoryRow(row = {}) {
|
|
144
|
+
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
145
|
+
return {
|
|
146
|
+
memoryType: row.memoryType || row.memory_type || 'memory',
|
|
147
|
+
canonicalKey: row.canonicalKey || row.canonical_key || null,
|
|
148
|
+
scopeKey: row.scopeKey || row.scope_key || null,
|
|
149
|
+
summary: compactText(row.summary || row.title || '', 420),
|
|
150
|
+
authority: row.authority || null,
|
|
151
|
+
confidence: payload.confidence || payload.currentMemoryConfidence || null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function compactCurrentMemory(currentMemory = null, opts = {}) {
|
|
156
|
+
const rows = Array.isArray(currentMemory?.memories)
|
|
157
|
+
? currentMemory.memories
|
|
158
|
+
: (Array.isArray(currentMemory?.items) ? currentMemory.items : []);
|
|
159
|
+
const maxItems = Math.max(0, Math.min(20, opts.maxCurrentMemoryItems || opts.currentMemoryLimit || 12));
|
|
160
|
+
return rows
|
|
161
|
+
.map(compactCurrentMemoryRow)
|
|
162
|
+
.filter(row => row.summary)
|
|
163
|
+
.slice(0, maxItems);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function compactCheckpointRow(row = {}) {
|
|
167
|
+
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
168
|
+
return {
|
|
169
|
+
checkpointKey: row.checkpointKey || row.checkpoint_key || null,
|
|
170
|
+
scopeKey: row.scopeKey || row.scope_key || null,
|
|
171
|
+
topicKey: row.topicKey || row.topic_key || payload.topicKey || null,
|
|
172
|
+
triggerKind: row.triggerKind || row.trigger_kind || payload.triggerKind || null,
|
|
173
|
+
summaryText: compactText(row.summaryText || row.summary_text || row.summary || payload.summaryText, 520),
|
|
174
|
+
coverage: row.coverage || payload.coverage || {},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function compactPreviousCheckpoints(checkpoints = [], opts = {}) {
|
|
179
|
+
const rows = Array.isArray(checkpoints?.checkpoints)
|
|
180
|
+
? checkpoints.checkpoints
|
|
181
|
+
: (Array.isArray(checkpoints?.items) ? checkpoints.items : checkpoints);
|
|
182
|
+
const maxItems = Math.max(0, Math.min(12, opts.maxCheckpoints || opts.checkpointLimit || 6));
|
|
183
|
+
return (Array.isArray(rows) ? rows : [])
|
|
184
|
+
.map(compactCheckpointRow)
|
|
185
|
+
.filter(row => row.summaryText || Object.keys(row.coverage || {}).length > 0)
|
|
186
|
+
.slice(0, maxItems);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function normalizeScopeEnvelope(input = {}) {
|
|
190
|
+
const envelope = input.scopeEnvelope || input.scope_envelope || null;
|
|
191
|
+
if (envelope && typeof envelope === 'object') {
|
|
192
|
+
return {
|
|
193
|
+
...envelope,
|
|
194
|
+
scopeById: envelope.scopeById || Object.fromEntries((envelope.slots || []).map(scope => [scope.id, scope])),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
const scopeInput = input.scope && typeof input.scope === 'object' ? input.scope : input;
|
|
198
|
+
const built = buildScopeEnvelope(scopeInput);
|
|
199
|
+
if (built.activeScopeKey === 'global' && (!built.slots || built.slots.length === 0)) {
|
|
200
|
+
throw new Error('checkpoint synthesis requires a bounded scope envelope');
|
|
201
|
+
}
|
|
202
|
+
return built;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeTargetScope(envelope = {}, input = {}) {
|
|
206
|
+
const targetScopeEnvelopeId = input.targetScopeEnvelopeId
|
|
207
|
+
|| input.target_scope_envelope_id
|
|
208
|
+
|| input.targetScopeId
|
|
209
|
+
|| input.target_scope_id
|
|
210
|
+
|| envelope.activeSlotId;
|
|
211
|
+
const scope = getScopeByEnvelopeId(envelope, targetScopeEnvelopeId);
|
|
212
|
+
if (!scope.promotable) {
|
|
213
|
+
throw new Error(`checkpoint synthesis target scope is not promotable: ${targetScopeEnvelopeId}`);
|
|
214
|
+
}
|
|
215
|
+
if (!Array.isArray(envelope.allowedScopeKeys) || !envelope.allowedScopeKeys.includes(scope.scopeKey)) {
|
|
216
|
+
throw new Error(`checkpoint synthesis target scope is outside allowed envelope: ${scope.scopeKey}`);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
envelopeId: scope.id,
|
|
220
|
+
scopeKind: scope.scopeKind,
|
|
221
|
+
scopeKey: scope.scopeKey,
|
|
222
|
+
label: scope.label || null,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function buildCheckpointSynthesisInput(input = {}, opts = {}) {
|
|
227
|
+
const view = input.view || opts.view;
|
|
228
|
+
assertOkTranscriptView(view);
|
|
229
|
+
const range = normalizeFinalizationRange(input);
|
|
230
|
+
const scopeEnvelope = normalizeScopeEnvelope(input);
|
|
231
|
+
const targetScope = normalizeTargetScope(scopeEnvelope, input);
|
|
232
|
+
const coverage = buildCheckpointCoverageFromView(view, input);
|
|
233
|
+
const maxTranscriptChars = Math.max(1000, Math.min(120000, input.maxTranscriptChars || opts.maxTranscriptChars || 60000));
|
|
234
|
+
const transcriptText = view.text.length > maxTranscriptChars
|
|
235
|
+
? view.text.slice(Math.max(0, view.text.length - maxTranscriptChars))
|
|
236
|
+
: view.text;
|
|
237
|
+
const base = {
|
|
238
|
+
kind: 'session_checkpoint_synthesis_input_v1',
|
|
239
|
+
policyVersion: input.policyVersion || opts.policyVersion || DEFAULT_POLICY_VERSION,
|
|
240
|
+
sourceOfTruth: input.sourceOfTruth || input.source_of_truth || opts.sourceOfTruth || 'sanitized_transcript_view',
|
|
241
|
+
triggerKind: input.triggerKind || input.trigger_kind || opts.triggerKind || 'manual',
|
|
242
|
+
promotion: {
|
|
243
|
+
default: 'checkpoint_proposal_only',
|
|
244
|
+
requires: 'operator_review_or_explicit_finalize',
|
|
245
|
+
},
|
|
246
|
+
guards: {
|
|
247
|
+
checkpointIsProcessMaterial: true,
|
|
248
|
+
rawToolOutputExcluded: true,
|
|
249
|
+
debugIdsExcluded: true,
|
|
250
|
+
activeMemoryCommitExcluded: true,
|
|
251
|
+
},
|
|
252
|
+
range,
|
|
253
|
+
coverage,
|
|
254
|
+
targetScope,
|
|
255
|
+
scopeEnvelope: {
|
|
256
|
+
policyVersion: scopeEnvelope.policyVersion || 'scope_envelope_v1',
|
|
257
|
+
activeSlotId: scopeEnvelope.activeSlotId,
|
|
258
|
+
activeScopeKey: scopeEnvelope.activeScopeKey,
|
|
259
|
+
allowedScopeKeys: scopeEnvelope.allowedScopeKeys || [],
|
|
260
|
+
slots: (scopeEnvelope.slots || []).map(scope => ({
|
|
261
|
+
id: scope.id,
|
|
262
|
+
slot: scope.slot,
|
|
263
|
+
scopeKind: scope.scopeKind,
|
|
264
|
+
scopeKey: scope.scopeKey,
|
|
265
|
+
label: scope.label || null,
|
|
266
|
+
promotable: Boolean(scope.promotable),
|
|
267
|
+
allowedScopeKeys: scope.allowedScopeKeys || [],
|
|
268
|
+
})),
|
|
269
|
+
},
|
|
270
|
+
transcript: {
|
|
271
|
+
sessionId: view.sessionId || null,
|
|
272
|
+
transcriptHash: view.transcriptHash || null,
|
|
273
|
+
charCount: view.charCount ?? view.text.length,
|
|
274
|
+
approxPromptTokens: view.approxPromptTokens || Math.ceil(view.text.length / 3),
|
|
275
|
+
truncated: transcriptText.length !== view.text.length,
|
|
276
|
+
text: transcriptText,
|
|
277
|
+
},
|
|
278
|
+
currentMemory: compactCurrentMemory(input.currentMemory || opts.currentMemory || null, input),
|
|
279
|
+
previousCheckpoints: compactPreviousCheckpoints(input.previousCheckpoints || input.checkpoints || opts.previousCheckpoints || [], input),
|
|
280
|
+
storage: {
|
|
281
|
+
scopeId: input.storageScopeId || input.storage_scope_id || input.scopeDbId || input.scope_db_id || null,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
...base,
|
|
286
|
+
inputHash: hashSnapshot(base),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function promptSafeSynthesisInput(synthesisInput = {}) {
|
|
291
|
+
const transcript = synthesisInput.transcript && typeof synthesisInput.transcript === 'object'
|
|
292
|
+
? {
|
|
293
|
+
...synthesisInput.transcript,
|
|
294
|
+
transcriptHash: undefined,
|
|
295
|
+
}
|
|
296
|
+
: synthesisInput.transcript;
|
|
297
|
+
const out = {
|
|
298
|
+
...synthesisInput,
|
|
299
|
+
inputHash: undefined,
|
|
300
|
+
storage: undefined,
|
|
301
|
+
transcript,
|
|
302
|
+
};
|
|
303
|
+
return JSON.parse(JSON.stringify(out));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildCheckpointSynthesisPrompt(synthesisInput = {}, opts = {}) {
|
|
307
|
+
if (!synthesisInput || synthesisInput.kind !== 'session_checkpoint_synthesis_input_v1') {
|
|
308
|
+
throw new Error('buildCheckpointSynthesisPrompt requires a checkpoint synthesis input');
|
|
309
|
+
}
|
|
310
|
+
const maxFacts = Math.max(1, Math.min(24, opts.maxFacts || 10));
|
|
311
|
+
const promptInput = promptSafeSynthesisInput(synthesisInput);
|
|
312
|
+
return [
|
|
313
|
+
'You are producing an Aquifer session checkpoint proposal.',
|
|
314
|
+
'Use only the <checkpoint_synthesis_input> block. Do not use hidden tool output, injected context, or debug material.',
|
|
315
|
+
'This checkpoint is producer process material, not active current memory and not final truth.',
|
|
316
|
+
'Choose scope only from scopeEnvelope.slots and keep every item inside targetScope unless the input proves a narrower allowed scope.',
|
|
317
|
+
'Do not include DB ids, raw hashes, secrets, raw tool output, or prompt/debug identifiers in memory candidates.',
|
|
318
|
+
'Return compact JSON with this shape:',
|
|
319
|
+
STRUCTURED_SUMMARY_SHAPE,
|
|
320
|
+
`Keep facts/decisions/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
|
|
321
|
+
'Preserve the coverage object so handoff can skip only the already-covered transcript range.',
|
|
322
|
+
'',
|
|
323
|
+
'<checkpoint_synthesis_input>',
|
|
324
|
+
stableJson(promptInput),
|
|
325
|
+
'</checkpoint_synthesis_input>',
|
|
326
|
+
].join('\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function normalizeCheckpointSynthesisSummary(input = {}) {
|
|
330
|
+
const raw = input && typeof input === 'object'
|
|
331
|
+
? {
|
|
332
|
+
summaryText: input.summaryText || input.summary || '',
|
|
333
|
+
structuredSummary: input.structuredSummary || input.structured_summary || {},
|
|
334
|
+
}
|
|
335
|
+
: {
|
|
336
|
+
summaryText: '',
|
|
337
|
+
structuredSummary: {},
|
|
338
|
+
};
|
|
339
|
+
const sanitized = sanitizeSummaryResult(raw);
|
|
340
|
+
const coverage = input && typeof input === 'object' && input.coverage && typeof input.coverage === 'object'
|
|
341
|
+
? input.coverage
|
|
342
|
+
: null;
|
|
343
|
+
return {
|
|
344
|
+
summary: sanitized.summaryResult || raw,
|
|
345
|
+
coverage,
|
|
346
|
+
safetyGate: sanitized.meta || {},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function buildCheckpointRunInputFromSynthesis(synthesisInput = {}, synthesisSummary = {}, opts = {}) {
|
|
351
|
+
if (!synthesisInput || synthesisInput.kind !== 'session_checkpoint_synthesis_input_v1') {
|
|
352
|
+
throw new Error('checkpoint run input requires a checkpoint synthesis input');
|
|
353
|
+
}
|
|
354
|
+
const { summary, coverage, safetyGate } = normalizeCheckpointSynthesisSummary(synthesisSummary);
|
|
355
|
+
const summaryText = String(summary.summaryText || summary.summary || '').trim();
|
|
356
|
+
const structuredSummary = summary.structuredSummary || {};
|
|
357
|
+
if (!summaryText && Object.keys(structuredSummary).length === 0) {
|
|
358
|
+
throw new Error('checkpoint run input requires summaryText or structuredSummary');
|
|
359
|
+
}
|
|
360
|
+
const range = normalizeFinalizationRange(synthesisInput.range || {});
|
|
361
|
+
const scopeId = requiredPositiveInteger(
|
|
362
|
+
opts.storageScopeId || opts.scopeId || synthesisInput.storage?.scopeId,
|
|
363
|
+
'storageScopeId'
|
|
364
|
+
);
|
|
365
|
+
const status = opts.status || 'processing';
|
|
366
|
+
const targetScope = synthesisInput.targetScope || {};
|
|
367
|
+
const checkpointPayload = {
|
|
368
|
+
kind: 'session_checkpoint_proposal_v1',
|
|
369
|
+
policyVersion: synthesisInput.policyVersion || DEFAULT_POLICY_VERSION,
|
|
370
|
+
inputHash: synthesisInput.inputHash || hashSnapshot(synthesisInput),
|
|
371
|
+
promotionGate: 'operator_required',
|
|
372
|
+
checkpointRole: 'handoff_process_material',
|
|
373
|
+
triggerKind: synthesisInput.triggerKind || 'manual',
|
|
374
|
+
summaryText,
|
|
375
|
+
structuredSummary,
|
|
376
|
+
coverage: coverage || synthesisInput.coverage || {},
|
|
377
|
+
targetScope,
|
|
378
|
+
safetyGate,
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
scopeId,
|
|
382
|
+
checkpointKey: opts.checkpointKey || undefined,
|
|
383
|
+
status,
|
|
384
|
+
fromFinalizationIdExclusive: range.fromFinalizationIdExclusive,
|
|
385
|
+
toFinalizationIdInclusive: range.toFinalizationIdInclusive,
|
|
386
|
+
scopeSnapshot: {
|
|
387
|
+
scopeKind: targetScope.scopeKind || null,
|
|
388
|
+
scopeKey: targetScope.scopeKey || null,
|
|
389
|
+
targetScopeEnvelopeId: targetScope.envelopeId || null,
|
|
390
|
+
policyVersion: synthesisInput.scopeEnvelope?.policyVersion || 'scope_envelope_v1',
|
|
391
|
+
},
|
|
392
|
+
checkpointText: summaryText || null,
|
|
393
|
+
checkpointPayload,
|
|
394
|
+
metadata: {
|
|
395
|
+
source: 'session_checkpoint_producer',
|
|
396
|
+
inputHash: checkpointPayload.inputHash,
|
|
397
|
+
triggerKind: checkpointPayload.triggerKind,
|
|
398
|
+
policyVersion: checkpointPayload.policyVersion,
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = {
|
|
404
|
+
stableJson,
|
|
405
|
+
hashSnapshot,
|
|
406
|
+
buildCheckpointCoverageFromView,
|
|
407
|
+
buildCheckpointSynthesisInput,
|
|
408
|
+
buildCheckpointSynthesisPrompt,
|
|
409
|
+
promptSafeSynthesisInput,
|
|
410
|
+
normalizeCheckpointSynthesisSummary,
|
|
411
|
+
buildCheckpointRunInputFromSynthesis,
|
|
412
|
+
};
|