@sema-agent/core 5.53.0 → 5.55.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/CHANGELOG.md +150 -0
- package/dist/agents/cumulative-stats.d.ts +26 -0
- package/dist/agents/cumulative-stats.js +56 -0
- package/dist/agents/observer.d.ts +11 -7
- package/dist/agents/observer.js +2 -4
- package/dist/agents/subagent.d.ts +4 -0
- package/dist/agents/subagent.js +1 -1
- package/dist/agents/verify.d.ts +27 -3
- package/dist/agents/verify.js +7 -2
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp-diagnostics.d.ts +19 -17
- package/dist/core/lsp-diagnostics.js +11 -5
- package/dist/core/mcp.d.ts +214 -5
- package/dist/core/mcp.js +315 -30
- package/dist/core/memory-engine/consolidation.d.ts +378 -0
- package/dist/core/memory-engine/consolidation.js +342 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +237 -4
- package/dist/core/memory-engine/engine.js +1111 -4
- package/dist/core/memory-engine/export-bundle.js +9 -0
- package/dist/core/memory-engine/file-backend.js +27 -1
- package/dist/core/memory-engine/frontmatter.d.ts +20 -1
- package/dist/core/memory-engine/frontmatter.js +111 -0
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +3 -1
- package/dist/core/memory-engine/memory-backend-contract.js +131 -0
- package/dist/core/memory-engine/sync-client.js +26 -0
- package/dist/core/memory-engine/tools.d.ts +9 -0
- package/dist/core/memory-engine/tools.js +57 -13
- package/dist/core/memory-engine/types.d.ts +99 -0
- package/dist/core/memory-recall.js +4 -3
- package/dist/core/memory.d.ts +33 -3
- package/dist/core/memory.js +6 -4
- package/dist/core/permission-rule-model.d.ts +140 -21
- package/dist/core/permission-rule-model.js +76 -17
- package/dist/core/permission-rule-org.d.ts +4 -3
- package/dist/core/permission-rule-org.js +12 -3
- package/dist/core/permission-rules.d.ts +22 -0
- package/dist/core/permission-rules.js +60 -6
- package/dist/core/reminder-disclosure.d.ts +29 -4
- package/dist/core/reminder-disclosure.js +60 -12
- package/dist/core/runner/prepare-memory.js +7 -2
- package/dist/core/runner/prepare-task.d.ts +31 -1
- package/dist/core/runner/prepare-task.js +31 -14
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +12 -10
- package/dist/core/runner/session-rule-policy.js +5 -3
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/turn-attachments.d.ts +16 -6
- package/dist/core/runner/turn-attachments.js +34 -20
- package/dist/core/tool-policy.d.ts +18 -0
- package/dist/core/tool-policy.js +19 -8
- package/dist/core/types.d.ts +89 -6
- package/dist/core/untrusted-egress.js +12 -2
- package/dist/core/untrusted-text.d.ts +189 -3
- package/dist/core/untrusted-text.js +416 -6
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-types.d.ts +16 -6
- package/dist/orchestration/workflow-types.js +10 -4
- package/dist/orchestration/workflow.js +32 -6
- package/dist/prompts/default.d.ts +12 -2
- package/dist/stores/file/background-agent-store.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +6 -2
- package/dist/stores/file/checkpoint-store.js +1 -0
- package/dist/stores/file/fs-atomic.d.ts +151 -10
- package/dist/stores/file/fs-atomic.js +208 -32
- package/dist/stores/file/index.d.ts +26 -3
- package/dist/stores/file/index.js +25 -2
- package/dist/stores/file/shared-ledger.d.ts +40 -5
- package/dist/stores/file/shared-ledger.js +24 -8
- package/dist/stores/file/workflow-run-store.d.ts +8 -1
- package/dist/stores/file/workflow-run-store.js +1 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +71 -0
- package/dist/tools/fs/bash-readonly-classifier.js +58 -47
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +30 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { existsSync, readdirSync, renameSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { atomicWriteFileSync, lockedStrictUpdate, readStrictSidecar, ControlPlaneCorruptError, ensureDirExists } from "./layout.js";
|
|
4
|
+
export function deriveSupersededSet(headers, opts = {}) {
|
|
5
|
+
const byId = new Map();
|
|
6
|
+
for (const h of headers)
|
|
7
|
+
byId.set(h.id, h);
|
|
8
|
+
const out = new Map();
|
|
9
|
+
for (const h of headers) {
|
|
10
|
+
const d = h.distilled;
|
|
11
|
+
if (d === undefined || d.supersedes.length === 0)
|
|
12
|
+
continue;
|
|
13
|
+
if (opts.excluded?.(h.id) === true)
|
|
14
|
+
continue;
|
|
15
|
+
if (h.rev !== d.carrierRev)
|
|
16
|
+
continue;
|
|
17
|
+
for (const edge of d.supersedes) {
|
|
18
|
+
const target = byId.get(edge.id);
|
|
19
|
+
if (target === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
if (target.scope !== h.scope)
|
|
22
|
+
continue;
|
|
23
|
+
if (target.rev !== edge.rev)
|
|
24
|
+
continue;
|
|
25
|
+
if (!out.has(edge.id))
|
|
26
|
+
out.set(edge.id, h.id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
export function memorySupersededNote(carrierId) {
|
|
32
|
+
return `note: a newer consolidated entry supersedes this one — memory entry ${carrierId} carries the current form; this entry remains readable as evidence.`;
|
|
33
|
+
}
|
|
34
|
+
export const MEMORY_SEARCH_SUPERSEDED_TAG = "[superseded]";
|
|
35
|
+
export const CONSOLIDATION_DEFAULTS = {
|
|
36
|
+
supersedeRatioCap: 0.25,
|
|
37
|
+
supersedeAbsoluteFloor: 4,
|
|
38
|
+
maxProductsPerRun: 64,
|
|
39
|
+
maxInputsPerProduct: 32,
|
|
40
|
+
maxDirectedPatchesPerPlan: 32,
|
|
41
|
+
minRunIntervalMs: 24 * 60 * 60 * 1000,
|
|
42
|
+
minSessionsBetweenRuns: 5,
|
|
43
|
+
};
|
|
44
|
+
export const CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS = 60_000;
|
|
45
|
+
export const CONSOLIDATION_SESSION_RING_MAX = 64;
|
|
46
|
+
export const CONSOLIDATION_SEAT_FREEZE_GRACE_MS = 10 * 60_000;
|
|
47
|
+
function coded(message, code) {
|
|
48
|
+
const e = new Error(message);
|
|
49
|
+
e.code = code;
|
|
50
|
+
return e;
|
|
51
|
+
}
|
|
52
|
+
export function screenConsolidationOptions(raw, provenance) {
|
|
53
|
+
if (provenance === "off") {
|
|
54
|
+
throw coded(`MemoryEngineOptions.consolidation cannot be enabled under provenance: "off" — the consolidation fold law must be able to mint origin markers (a marked input's product would otherwise commit unmarked). Enable provenance "carry" or remove the consolidation options.`, "config.memory_consolidation_provenance_off");
|
|
55
|
+
}
|
|
56
|
+
const num = (name, v, check, want) => {
|
|
57
|
+
if (v === undefined)
|
|
58
|
+
return CONSOLIDATION_DEFAULTS[name];
|
|
59
|
+
if (typeof v !== "number" || !Number.isFinite(v) || !check(v)) {
|
|
60
|
+
const got = typeof v === "number" ? String(v) : v === null ? "null" : typeof v;
|
|
61
|
+
throw coded(`MemoryEngineOptions.consolidation.${name} must be ${want} when present (got ${got}) — an unevaluable knob is refused loudly, never folded to the default.`, "config.memory_consolidation");
|
|
62
|
+
}
|
|
63
|
+
return v;
|
|
64
|
+
};
|
|
65
|
+
const screened = {
|
|
66
|
+
supersedeRatioCap: num("supersedeRatioCap", raw.supersedeRatioCap, (n) => n > 0 && n <= 1, "a number in (0, 1]"),
|
|
67
|
+
supersedeAbsoluteFloor: num("supersedeAbsoluteFloor", raw.supersedeAbsoluteFloor, (n) => Number.isInteger(n) && n >= 0, "a non-negative integer"),
|
|
68
|
+
maxProductsPerRun: num("maxProductsPerRun", raw.maxProductsPerRun, (n) => Number.isInteger(n) && n >= 1, "a positive integer"),
|
|
69
|
+
maxInputsPerProduct: num("maxInputsPerProduct", raw.maxInputsPerProduct, (n) => Number.isInteger(n) && n >= 1, "a positive integer"),
|
|
70
|
+
maxDirectedPatchesPerPlan: num("maxDirectedPatchesPerPlan", raw.maxDirectedPatchesPerPlan, (n) => Number.isInteger(n) && n >= 1, "a positive integer"),
|
|
71
|
+
minRunIntervalMs: num("minRunIntervalMs", raw.minRunIntervalMs, (n) => n > 0, "a positive number of milliseconds"),
|
|
72
|
+
minSessionsBetweenRuns: num("minSessionsBetweenRuns", raw.minSessionsBetweenRuns, (n) => Number.isInteger(n) && n >= 0, "a non-negative integer"),
|
|
73
|
+
multiNode: raw.multiNode === true,
|
|
74
|
+
...(raw.lease !== undefined ? { lease: raw.lease } : {}),
|
|
75
|
+
};
|
|
76
|
+
if (raw.multiNode !== undefined && raw.multiNode !== true) {
|
|
77
|
+
throw coded(`MemoryEngineOptions.consolidation.multiNode must be exactly true when present (a posture declaration, not a boolean knob).`, "config.memory_consolidation");
|
|
78
|
+
}
|
|
79
|
+
if (screened.multiNode && screened.lease === undefined) {
|
|
80
|
+
throw coded(`MemoryEngineOptions.consolidation.multiNode declared without a lease seat — a multi-node deployment MUST inject the host's global lease (the local plan-seat CAS does not reach across nodes). Inject consolidation.lease or remove the multiNode declaration.`, "config.memory_consolidation_lease_required");
|
|
81
|
+
}
|
|
82
|
+
if (raw.lease !== undefined && typeof raw.lease.acquire !== "function") {
|
|
83
|
+
throw coded(`MemoryEngineOptions.consolidation.lease.acquire must be a function.`, "config.memory_consolidation");
|
|
84
|
+
}
|
|
85
|
+
return screened;
|
|
86
|
+
}
|
|
87
|
+
export function supersessionFuseCeiling(activeSetSize, opts) {
|
|
88
|
+
if (activeSetSize <= 1)
|
|
89
|
+
return 0;
|
|
90
|
+
return Math.min(activeSetSize - 1, Math.max(opts.supersedeAbsoluteFloor, Math.floor(opts.supersedeRatioCap * activeSetSize)));
|
|
91
|
+
}
|
|
92
|
+
export function consolidationTypeEligible(type) {
|
|
93
|
+
return type !== "episodic" && type !== "procedural";
|
|
94
|
+
}
|
|
95
|
+
export const CONSOLIDATION_GATE_FILE = "consolidation-gate.json";
|
|
96
|
+
export const CONSOLIDATION_PLANS_DIR = "consolidation-plans";
|
|
97
|
+
export const CONSOLIDATION_INTENTS_FILE = "consolidation-intents.json";
|
|
98
|
+
function coerceGateFile(raw) {
|
|
99
|
+
if (raw === undefined)
|
|
100
|
+
return { v: 1, scopes: {} };
|
|
101
|
+
if (typeof raw !== "object" || raw === null || raw.v !== 1 || typeof raw.scopes !== "object" || raw.scopes === null) {
|
|
102
|
+
throw new ControlPlaneCorruptError("memory consolidation gate store has an unrecognized shape (fail-closed sidecar)");
|
|
103
|
+
}
|
|
104
|
+
const scopes = raw.scopes;
|
|
105
|
+
const out = { v: 1, scopes: {} };
|
|
106
|
+
for (const [scope, rowRaw] of Object.entries(scopes)) {
|
|
107
|
+
if (typeof rowRaw !== "object" || rowRaw === null)
|
|
108
|
+
throw new ControlPlaneCorruptError(`memory consolidation gate store row for scope ${JSON.stringify(scope)} is malformed (fail-closed sidecar)`);
|
|
109
|
+
const r = rowRaw;
|
|
110
|
+
const sessions = r["sessions"];
|
|
111
|
+
if (typeof sessions !== "object" ||
|
|
112
|
+
sessions === null ||
|
|
113
|
+
typeof sessions.count !== "number" ||
|
|
114
|
+
!Array.isArray(sessions.ring ?? []) ||
|
|
115
|
+
(sessions.ring !== undefined && !sessions.ring.every((s) => typeof s === "string")) ||
|
|
116
|
+
typeof r["epoch"] !== "number" ||
|
|
117
|
+
!Number.isInteger(r["epoch"])) {
|
|
118
|
+
throw new ControlPlaneCorruptError(`memory consolidation gate store row for scope ${JSON.stringify(scope)} is malformed (fail-closed sidecar)`);
|
|
119
|
+
}
|
|
120
|
+
out.scopes[scope] = rowRaw;
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
function freshRow() {
|
|
125
|
+
return { sessions: { count: 0, ring: [] }, epoch: 0 };
|
|
126
|
+
}
|
|
127
|
+
export function updateConsolidationGate(controlDir, fn) {
|
|
128
|
+
return lockedStrictUpdate(controlDir, CONSOLIDATION_GATE_FILE, "memory consolidation gate store", coerceGateFile, fn);
|
|
129
|
+
}
|
|
130
|
+
export function readConsolidationGateFile(controlDir) {
|
|
131
|
+
return coerceGateFile(readStrictSidecar(controlDir, CONSOLIDATION_GATE_FILE, "memory consolidation gate store"));
|
|
132
|
+
}
|
|
133
|
+
export function consolidationGateFileExists(controlDir) {
|
|
134
|
+
return existsSync(join(controlDir, CONSOLIDATION_GATE_FILE));
|
|
135
|
+
}
|
|
136
|
+
export function recordConsolidationSession(controlDir, scope, sessionId, opts, now) {
|
|
137
|
+
return updateConsolidationGate(controlDir, (file) => {
|
|
138
|
+
const row = file.scopes[scope] ?? freshRow();
|
|
139
|
+
if (row.sessions.ring.includes(sessionId)) {
|
|
140
|
+
return { result: { recommended: false, sessionsSince: row.sessions.count } };
|
|
141
|
+
}
|
|
142
|
+
row.sessions.count += 1;
|
|
143
|
+
row.sessions.ring.push(sessionId);
|
|
144
|
+
if (row.sessions.ring.length > CONSOLIDATION_SESSION_RING_MAX)
|
|
145
|
+
row.sessions.ring.splice(0, row.sessions.ring.length - CONSOLIDATION_SESSION_RING_MAX);
|
|
146
|
+
const at = now();
|
|
147
|
+
const timeOpen = row.lastRunAt === undefined || at - row.lastRunAt >= opts.minRunIntervalMs;
|
|
148
|
+
const sessionsOpen = row.sessions.count >= opts.minSessionsBetweenRuns;
|
|
149
|
+
const edgeArmed = row.lastRecommendedAt === undefined || (row.lastRunAt !== undefined && row.lastRecommendedAt <= row.lastRunAt);
|
|
150
|
+
let recommended = false;
|
|
151
|
+
if (timeOpen && sessionsOpen && edgeArmed) {
|
|
152
|
+
recommended = true;
|
|
153
|
+
row.lastRecommendedAt = at;
|
|
154
|
+
}
|
|
155
|
+
file.scopes[scope] = row;
|
|
156
|
+
return { next: file, result: { recommended, sessionsSince: row.sessions.count } };
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
export function recordActiveSetBaseline(controlDir, scope, size, ratioCap, _now) {
|
|
160
|
+
return updateConsolidationGate(controlDir, (file) => {
|
|
161
|
+
const row = file.scopes[scope] ?? freshRow();
|
|
162
|
+
const prev = row.activeSetBaseline;
|
|
163
|
+
row.activeSetBaseline = size;
|
|
164
|
+
file.scopes[scope] = row;
|
|
165
|
+
const collapsed = prev !== undefined && prev > 0 && size < prev && (prev - size) / prev > ratioCap ? { from: prev, to: size } : undefined;
|
|
166
|
+
return { next: file, result: collapsed !== undefined ? { collapsed } : {} };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
export function isValidConsolidationPlanId(value) {
|
|
170
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{7,63}$/.test(value);
|
|
171
|
+
}
|
|
172
|
+
function planPath(controlDir, planId) {
|
|
173
|
+
if (!isValidConsolidationPlanId(planId)) {
|
|
174
|
+
throw new ControlPlaneCorruptError(`consolidation plan id ${JSON.stringify(planId.slice(0, 80))} fails the id grammar — a plan id doubles as a control-plane file name and must never be joined as a path`);
|
|
175
|
+
}
|
|
176
|
+
return join(controlDir, CONSOLIDATION_PLANS_DIR, `${planId}.json`);
|
|
177
|
+
}
|
|
178
|
+
export function writeConsolidationPlan(controlDir, plan) {
|
|
179
|
+
ensureDirExists(join(controlDir, CONSOLIDATION_PLANS_DIR));
|
|
180
|
+
atomicWriteFileSync(planPath(controlDir, plan.planId), `${JSON.stringify(plan, null, 2)}\n`);
|
|
181
|
+
}
|
|
182
|
+
const CONSOLIDATION_PLAN_STATES = new Set(["open", "applying", "completed", "conflict", "discarded"]);
|
|
183
|
+
const CONSOLIDATION_DIRECTED_STATES = new Set(["pending", "applied", "satisfied", "conflict"]);
|
|
184
|
+
const CONSOLIDATION_INTENT_STATES = new Set(["pending", "settled", "abandoned"]);
|
|
185
|
+
function planShapeInvalid(raw, planId) {
|
|
186
|
+
if (typeof raw !== "object" || raw === null)
|
|
187
|
+
return "not an object";
|
|
188
|
+
const p = raw;
|
|
189
|
+
if (p["v"] !== 1)
|
|
190
|
+
return "unrecognized version";
|
|
191
|
+
if (p["planId"] !== planId)
|
|
192
|
+
return "planId mismatch";
|
|
193
|
+
if (typeof p["scope"] !== "string" || p["scope"].length === 0)
|
|
194
|
+
return "missing scope";
|
|
195
|
+
if (typeof p["requestId"] !== "string")
|
|
196
|
+
return "missing requestId";
|
|
197
|
+
if (typeof p["createdAt"] !== "number")
|
|
198
|
+
return "missing createdAt";
|
|
199
|
+
if (typeof p["visibleMarked"] !== "boolean")
|
|
200
|
+
return "missing visibleMarked";
|
|
201
|
+
if (typeof p["state"] !== "string" || !CONSOLIDATION_PLAN_STATES.has(p["state"]))
|
|
202
|
+
return "unrecognized state";
|
|
203
|
+
if (!Array.isArray(p["products"]) || !p["products"].every((e) => typeof e === "object" && e !== null && typeof e.id === "string"))
|
|
204
|
+
return "malformed products";
|
|
205
|
+
const productIds = p["products"].map((e) => e.id);
|
|
206
|
+
const states = p["productStates"];
|
|
207
|
+
if (typeof states !== "object" || states === null)
|
|
208
|
+
return "malformed productStates";
|
|
209
|
+
const stateKeys = Object.keys(states);
|
|
210
|
+
if (stateKeys.length !== productIds.length || !productIds.every((id) => states[id] === "pending" || states[id] === "applied" || states[id] === "conflict")) {
|
|
211
|
+
return "productStates do not cover the product set one-to-one";
|
|
212
|
+
}
|
|
213
|
+
if (!Array.isArray(p["directed"]) ||
|
|
214
|
+
!p["directed"].every((d0) => {
|
|
215
|
+
if (typeof d0 !== "object" || d0 === null)
|
|
216
|
+
return false;
|
|
217
|
+
const d = d0;
|
|
218
|
+
if (typeof d["id"] !== "string" || typeof d["intentRequestId"] !== "string" || typeof d["baseRev"] !== "string")
|
|
219
|
+
return false;
|
|
220
|
+
if (d["op"] !== "update" && d["op"] !== "delete")
|
|
221
|
+
return false;
|
|
222
|
+
if (typeof d["state"] !== "string" || !CONSOLIDATION_DIRECTED_STATES.has(d["state"]))
|
|
223
|
+
return false;
|
|
224
|
+
if (d["op"] === "update") {
|
|
225
|
+
if (typeof d["plannedPostRev"] !== "string")
|
|
226
|
+
return false;
|
|
227
|
+
if (d["state"] === "pending" && (typeof d["entry"] !== "object" || d["entry"] === null))
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
else if (d["entry"] !== undefined)
|
|
231
|
+
return false;
|
|
232
|
+
return true;
|
|
233
|
+
})) {
|
|
234
|
+
return "malformed directed set";
|
|
235
|
+
}
|
|
236
|
+
const intentIds = new Set();
|
|
237
|
+
if (!Array.isArray(p["intents"]) ||
|
|
238
|
+
!p["intents"].every((i0) => {
|
|
239
|
+
if (typeof i0 !== "object" || i0 === null)
|
|
240
|
+
return false;
|
|
241
|
+
const i = i0;
|
|
242
|
+
if (typeof i["requestId"] !== "string")
|
|
243
|
+
return false;
|
|
244
|
+
intentIds.add(i["requestId"]);
|
|
245
|
+
return typeof i["state"] === "string" && CONSOLIDATION_INTENT_STATES.has(i["state"]);
|
|
246
|
+
})) {
|
|
247
|
+
return "malformed intents";
|
|
248
|
+
}
|
|
249
|
+
if (!p["directed"].every((d) => intentIds.has(d.intentRequestId)))
|
|
250
|
+
return "directed patch references an undeclared intent";
|
|
251
|
+
if (!Array.isArray(p["audit"]))
|
|
252
|
+
return "malformed audit trail";
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
export function readConsolidationPlan(controlDir, planId) {
|
|
256
|
+
if (!isValidConsolidationPlanId(planId))
|
|
257
|
+
return { state: "absent" };
|
|
258
|
+
const path = planPath(controlDir, planId);
|
|
259
|
+
if (!existsSync(path))
|
|
260
|
+
return { state: "absent" };
|
|
261
|
+
try {
|
|
262
|
+
const raw = readStrictSidecar(join(controlDir, CONSOLIDATION_PLANS_DIR), `${planId}.json`, "memory consolidation plan");
|
|
263
|
+
const bad = planShapeInvalid(raw, planId);
|
|
264
|
+
if (bad !== undefined)
|
|
265
|
+
return { state: "corrupt", detail: bad };
|
|
266
|
+
return { state: "ok", plan: raw };
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
return { state: "corrupt", detail: err instanceof Error ? err.message : String(err) };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
export function listConsolidationPlanIds(controlDir) {
|
|
273
|
+
const dir = join(controlDir, CONSOLIDATION_PLANS_DIR);
|
|
274
|
+
if (!existsSync(dir))
|
|
275
|
+
return [];
|
|
276
|
+
try {
|
|
277
|
+
return readdirSync(dir)
|
|
278
|
+
.filter((f) => f.endsWith(".json") && !f.endsWith(".corrupt.json"))
|
|
279
|
+
.map((f) => f.slice(0, -".json".length))
|
|
280
|
+
.filter((id) => isValidConsolidationPlanId(id));
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
export function quarantineCorruptPlan(controlDir, planId) {
|
|
287
|
+
if (!isValidConsolidationPlanId(planId))
|
|
288
|
+
return undefined;
|
|
289
|
+
const from = planPath(controlDir, planId);
|
|
290
|
+
if (!existsSync(from))
|
|
291
|
+
return undefined;
|
|
292
|
+
const name = `${planId}.corrupt.json`;
|
|
293
|
+
try {
|
|
294
|
+
renameSync(from, join(controlDir, CONSOLIDATION_PLANS_DIR, name));
|
|
295
|
+
return name;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function coerceIntentFile(raw) {
|
|
302
|
+
if (raw === undefined)
|
|
303
|
+
return { v: 1, rows: {} };
|
|
304
|
+
if (typeof raw !== "object" || raw === null || raw.v !== 1 || typeof raw.rows !== "object" || raw.rows === null) {
|
|
305
|
+
throw new ControlPlaneCorruptError("memory consolidation intent-credential account has an unrecognized shape (fail-closed sidecar)");
|
|
306
|
+
}
|
|
307
|
+
return raw;
|
|
308
|
+
}
|
|
309
|
+
export function recordIntentCredentials(controlDir, rows) {
|
|
310
|
+
if (rows.length === 0)
|
|
311
|
+
return [];
|
|
312
|
+
return lockedStrictUpdate(controlDir, CONSOLIDATION_INTENTS_FILE, "memory consolidation intent-credential account", coerceIntentFile, (file) => {
|
|
313
|
+
const written = [];
|
|
314
|
+
for (const row of rows) {
|
|
315
|
+
if (file.rows[row.requestId] !== undefined)
|
|
316
|
+
continue;
|
|
317
|
+
file.rows[row.requestId] = row;
|
|
318
|
+
written.push(row.requestId);
|
|
319
|
+
}
|
|
320
|
+
return written.length > 0 ? { next: file, result: written } : { result: written };
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
export function readIntentCredentials(controlDir) {
|
|
324
|
+
const raw = readStrictSidecar(controlDir, CONSOLIDATION_INTENTS_FILE, "memory consolidation intent-credential account");
|
|
325
|
+
return coerceIntentFile(raw).rows;
|
|
326
|
+
}
|
|
327
|
+
export function deriveProductSlug(name, fallback) {
|
|
328
|
+
const base = (name ?? "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
|
|
329
|
+
return base.length > 0 ? base : fallback;
|
|
330
|
+
}
|
|
331
|
+
export class ConsolidationRefusedError extends Error {
|
|
332
|
+
code;
|
|
333
|
+
reasons;
|
|
334
|
+
constructor(code, message, reasons = []) {
|
|
335
|
+
super(reasons.length > 0 ? `${message}\n${reasons.map((r) => ` - ${r}`).join("\n")}` : message);
|
|
336
|
+
this.code = code;
|
|
337
|
+
this.reasons = reasons;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export function productAddPatch(product) {
|
|
341
|
+
return { op: "add", id: product.id, entry: product, guard: "absent" };
|
|
342
|
+
}
|
|
@@ -75,6 +75,9 @@ export function mergeHarvestReports(a, b) {
|
|
|
75
75
|
const qf = [...(a.quarantineFailures ?? []), ...(b.quarantineFailures ?? [])];
|
|
76
76
|
if (qf.length > 0)
|
|
77
77
|
merged.quarantineFailures = qf;
|
|
78
|
+
const rec = a.consolidationRecommended ?? b.consolidationRecommended;
|
|
79
|
+
if (rec !== undefined)
|
|
80
|
+
merged.consolidationRecommended = rec;
|
|
78
81
|
const ac = a.containment;
|
|
79
82
|
const bc = b.containment;
|
|
80
83
|
if (ac !== undefined || bc !== undefined) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { EngineNotice } from "../types.js";
|
|
2
|
+
import { type ConsolidationGateRow, type ConsolidationPlanFile, type ConsolidationProposal, type MemoryConsolidationOptions } from "./consolidation.js";
|
|
2
3
|
import { foreignDanglingSessionAccounts, type EffectiveSettlement, type HoldRow } from "./delegation-settlement.js";
|
|
3
4
|
import { type OriginClearanceRow } from "./origin-clearance.js";
|
|
4
5
|
import { type CommittedBinding, type EraseMemoryEntriesInput, type MemoryErasureAttestation, type TransferEvidence } from "./file-backend.js";
|
|
5
6
|
import { type MemoryExportBundle, type MemoryImportReport } from "./export-bundle.js";
|
|
6
7
|
import { type ChallengeAssignment, type ChallengeEvent, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionMarkOutcome, type SessionPollutionRecord } from "./layout.js";
|
|
7
|
-
import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemoryEntryOrigin, MemorySessionHandle, ScanFinding } from "./types.js";
|
|
8
|
+
import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemoryEntry, MemoryEntryOrigin, MemorySessionHandle, ScanFinding } from "./types.js";
|
|
8
9
|
/**
|
|
9
10
|
* The CC `# Memory` instruction section — VERBATIM from the live capture
|
|
10
11
|
* (bench/cc-parity-toolsuite/reminders/live-capture-main-session-2026-07-08.md §12), with the
|
|
@@ -100,10 +101,12 @@ export declare const MEMORY_ANNOUNCEMENT_READONLY_CODA = "The memory store itsel
|
|
|
100
101
|
* false` — where the runner's write gate also refuses the file channel, keeping the engine-refusal
|
|
101
102
|
* sentence true), and for a write-less roster the persistence inference cannot vouch for. A merely
|
|
102
103
|
* Write-less roster does NOT qualify — other tools can still write the root. Name-free by
|
|
103
|
-
* construction (#181 class — it names no tool)
|
|
104
|
-
* read-only memory
|
|
104
|
+
* construction (#181 class — it names no tool). The closing sentence is CC-verbatim (2.1.223
|
|
105
|
+
* read-only memory arm — the same sentence CC uses for both its whole-memory and team-memory
|
|
106
|
+
* read-only states); the state sentence above it is sema-authored context (CC's surrounding prose
|
|
107
|
+
* describes its own file layout, which does not transfer here).
|
|
105
108
|
*/
|
|
106
|
-
export declare const MEMORY_READONLY_NOTICE = "# Memory\n\nYou have READ-ONLY access to persistent memory in this session: stored notes are available below, but this session has no memory write channel \u2014 the engine will not accept writes into the memory store. If the user asks you to remember something
|
|
109
|
+
export declare const MEMORY_READONLY_NOTICE = "# Memory\n\nYou have READ-ONLY access to persistent memory in this session: stored notes are available below, but this session has no memory write channel \u2014 the engine will not accept writes into the memory store. If the user asks you to remember something, explain that memory is read-only in this session.";
|
|
107
110
|
/** CC index-injection parameters: MEMORY.md's first 200 lines / 25KB enter the prompt. */
|
|
108
111
|
export declare const MEMORY_INDEX_MAX_LINES = 200;
|
|
109
112
|
export declare const MEMORY_INDEX_MAX_BYTES: number;
|
|
@@ -201,6 +204,20 @@ export interface MemoryEngineOptions {
|
|
|
201
204
|
* Absent ⇒ advisory-silent; neither a mount nor a harvest ever fails over this seat.
|
|
202
205
|
*/
|
|
203
206
|
onIncident?: MemoryPartitionIncidentSink;
|
|
207
|
+
/**
|
|
208
|
+
* design/339 §6.1 — the v3 consolidation write protocol's master switch. ABSENT = OFF (the
|
|
209
|
+
* shipped default: continuous automatic consolidation degrading useful memory is the研究-backed
|
|
210
|
+
* failure mode, so the protocol only arms on an explicit host decision): the four consolidation
|
|
211
|
+
* verbs refuse loudly (`memory.consolidation_disabled`), the gate store writes nothing (no file
|
|
212
|
+
* is ever created), the recommendation seat never mints, and the control plane stays
|
|
213
|
+
* byte-identical to a pre-339 deployment (G1). PRESENT = enabled; every knob screens at
|
|
214
|
+
* construction (#123 bad-value loudness — see {@link import("./consolidation.js").screenConsolidationOptions}),
|
|
215
|
+
* `provenance: "off"` beside it refuses at construction (D-12a: the fold law must be able to
|
|
216
|
+
* mint), and `multiNode: true` without an injected lease refuses (D-13a). Note the READ-side
|
|
217
|
+
* active-set treatment (index rows / search filter / get disclosure) is NOT behind this switch —
|
|
218
|
+
* supersession edges are store-level data facts and take effect wherever they exist (D-4a).
|
|
219
|
+
*/
|
|
220
|
+
consolidation?: MemoryConsolidationOptions;
|
|
204
221
|
}
|
|
205
222
|
/** What {@link MemoryEngine.inject} produces: the trusted CC-verbatim instruction section + the
|
|
206
223
|
* FENCED (untrusted-downgraded) derived index, and the combined block for the memory prompt slot. */
|
|
@@ -426,6 +443,103 @@ export declare function memoryHarvestQuarantinedNotice(input: {
|
|
|
426
443
|
* identifiers, neutralized and length-capped before they ride the message or the detail.
|
|
427
444
|
*/
|
|
428
445
|
export declare function memoryHoldNotices(report: HarvestReport, sessionId?: string): EngineNotice[];
|
|
446
|
+
/** §6.3 B-4 — verb ②'s receipt. */
|
|
447
|
+
export interface ConsolidationCommitReceipt {
|
|
448
|
+
planId: string;
|
|
449
|
+
state: "completed" | "conflict";
|
|
450
|
+
products: {
|
|
451
|
+
frozen: number;
|
|
452
|
+
applied: number;
|
|
453
|
+
conflicts: number;
|
|
454
|
+
refused: Array<{
|
|
455
|
+
index: number;
|
|
456
|
+
reason: string;
|
|
457
|
+
}>;
|
|
458
|
+
};
|
|
459
|
+
directed: {
|
|
460
|
+
frozen: number;
|
|
461
|
+
applied: number;
|
|
462
|
+
satisfied: number;
|
|
463
|
+
conflicts: number;
|
|
464
|
+
};
|
|
465
|
+
intents: {
|
|
466
|
+
settled: string[];
|
|
467
|
+
pending: string[];
|
|
468
|
+
};
|
|
469
|
+
foldedOrigin: boolean;
|
|
470
|
+
notices: EngineNotice[];
|
|
471
|
+
}
|
|
472
|
+
export interface ConsolidationReconcileReport {
|
|
473
|
+
replayed?: {
|
|
474
|
+
planId: string;
|
|
475
|
+
state: "completed" | "conflict";
|
|
476
|
+
};
|
|
477
|
+
releasedOrphanSeat?: string;
|
|
478
|
+
/** codex r2 — a seat whose plan file is absent but whose claim is YOUNGER than the freeze grace:
|
|
479
|
+
* a live commit is (very likely) mid-freeze; reconcile leaves it alone and reports it here. */
|
|
480
|
+
seatInFlight?: string;
|
|
481
|
+
corruptPlan?: {
|
|
482
|
+
planId: string;
|
|
483
|
+
detail: string;
|
|
484
|
+
};
|
|
485
|
+
propagatedChallenges: string[];
|
|
486
|
+
sweepError?: string;
|
|
487
|
+
}
|
|
488
|
+
export interface ConsolidationResolveReceipt {
|
|
489
|
+
planId: string;
|
|
490
|
+
action: "discard" | "retry";
|
|
491
|
+
state: "discarded" | "completed" | "conflict";
|
|
492
|
+
custody?: string;
|
|
493
|
+
abandonedIntents?: string[];
|
|
494
|
+
retryPlanId?: string;
|
|
495
|
+
retryReceipt?: ConsolidationCommitReceipt;
|
|
496
|
+
}
|
|
497
|
+
export interface ConsolidationPlanSummary {
|
|
498
|
+
planId: string;
|
|
499
|
+
corrupt?: true;
|
|
500
|
+
scope?: string;
|
|
501
|
+
state?: ConsolidationPlanFile["state"];
|
|
502
|
+
createdAt?: number;
|
|
503
|
+
products?: number;
|
|
504
|
+
directed?: number;
|
|
505
|
+
intents?: number;
|
|
506
|
+
}
|
|
507
|
+
/** §2.2 — the recommendation notice, minted off the harvest report's advisory member (the runner's
|
|
508
|
+
* hold-notice consumption point is its natural home). Undefined ⇔ this harvest crossed no edge. */
|
|
509
|
+
export declare function memoryConsolidationRecommendedNotice(report: HarvestReport, sessionId?: string): EngineNotice | undefined;
|
|
510
|
+
export declare function memoryConsolidationCommittedNotice(input: {
|
|
511
|
+
planId: string;
|
|
512
|
+
scope: string;
|
|
513
|
+
products: number;
|
|
514
|
+
superseded: number;
|
|
515
|
+
intents: number;
|
|
516
|
+
}): EngineNotice;
|
|
517
|
+
/** The CONFLICT parking notice. Two things it must not overstate:
|
|
518
|
+
* - the CAUSE. A park is reached three ways: a concurrent write takes a target to a third state
|
|
519
|
+
* (the CAS arm), the store declines a patch outright (an immutability guard, a malformed-patch
|
|
520
|
+
* refusal), or the engine's own re-judgment parks a row before any patch is offered at all (a
|
|
521
|
+
* retry whose target moved scope, vanished, or has no replayable terminal). A fixed label naming
|
|
522
|
+
* only the first would report a race that never happened, and naming only the first two would
|
|
523
|
+
* claim a store answer that was never asked for. The per-target reason is verbatim in the plan
|
|
524
|
+
* file's audit rows for ALL THREE — the re-judgment writes its own rows precisely so this
|
|
525
|
+
* sentence stays true.
|
|
526
|
+
* - the EXTENT. A park is plan-wide but application is per-row: a batch answers applied and
|
|
527
|
+
* conflicted siblings together, and the product batch runs before the directed one, so a parked
|
|
528
|
+
* plan may well have committed some of its patches. "Nothing was overwritten" is true only of
|
|
529
|
+
* the REFUSED targets, and `discard` rolls nothing back — an operator reading otherwise would
|
|
530
|
+
* abandon a plan believing it had no effects. The counts live on the receipt (products/directed
|
|
531
|
+
* applied vs conflicts); this notice stays ids-only and points at them. */
|
|
532
|
+
export declare function memoryConsolidationConflictNotice(input: {
|
|
533
|
+
planId: string;
|
|
534
|
+
scope: string;
|
|
535
|
+
}): EngineNotice;
|
|
536
|
+
/** The refusal's notice dialect (the verb itself throws the coded {@link ConsolidationRefusedError};
|
|
537
|
+
* a host converting the throw into its notice stream uses this one spelling). */
|
|
538
|
+
export declare function memoryConsolidationRefusedNotice(input: {
|
|
539
|
+
code: string;
|
|
540
|
+
scope?: string;
|
|
541
|
+
reason?: string;
|
|
542
|
+
}): EngineNotice;
|
|
429
543
|
export declare class MemoryEngine {
|
|
430
544
|
private readonly backend;
|
|
431
545
|
private readonly memoryDir;
|
|
@@ -446,6 +560,8 @@ export declare class MemoryEngine {
|
|
|
446
560
|
/** A backend-pinned directory (FileBackend zero-copy detection). */
|
|
447
561
|
private readonly backendPinnedRoot?;
|
|
448
562
|
constructor(opts: MemoryEngineOptions);
|
|
563
|
+
/** {@link MemoryEngineOptions.consolidation}, screened — undefined ⇔ the protocol is OFF. */
|
|
564
|
+
private readonly consolidation?;
|
|
449
565
|
/**
|
|
450
566
|
* The ONE delivery point for a failed announcement-queue operation (`memory.announce_failed`, ruled
|
|
451
567
|
* 2026-08-03). Every enqueue/drain call site stays fail-OPEN — a broken queue must never fail a
|
|
@@ -763,6 +879,123 @@ export declare class MemoryEngine {
|
|
|
763
879
|
/** Read face: the dangling foreign session-account rows a given session would classify residue
|
|
764
880
|
* against (host audit for the §3.6 valve). */
|
|
765
881
|
readDanglingSessionAccounts(selfSessionId: string): ReturnType<typeof foreignDanglingSessionAccounts>;
|
|
882
|
+
/** The coded off-switch refusal every verb answers first (G1). */
|
|
883
|
+
private requireConsolidation;
|
|
884
|
+
/** Gate-store read with the strict fail-closed translation (F5: corrupt ⇒ verbs refuse coded;
|
|
885
|
+
* read faces and harvests are untouched — they never come through here). */
|
|
886
|
+
private readConsolidationGateOrRefuse;
|
|
887
|
+
/** §1.7 — the lease check run before EVERY apply batch (fencing-token re-validation; the token
|
|
888
|
+
* narrows a stale holder's overlap to a single batch — §1.2-4 names the in-batch residue).
|
|
889
|
+
* Absent seat ⇒ single-machine deployment, the plan-seat CAS is the mutex. */
|
|
890
|
+
/** §1.7 — best-effort lease RELEASE at every verb exit (an outside pass's finding, adopted:
|
|
891
|
+
* acquire without a paired release starved every later worker on explicit-release lease
|
|
892
|
+
* implementations until expiry). Releasing beside an UNFINISHED durable plan is safe — the
|
|
893
|
+
* plan replays under whichever holder next acquires, and the fencing re-check before each
|
|
894
|
+
* batch keeps a stale holder to one batch. Failures die here (the lease is cost suppression,
|
|
895
|
+
* never safety — a failed release converges at the lease's own expiry). */
|
|
896
|
+
private releaseConsolidationLease;
|
|
897
|
+
private consolidationLeaseCurrent;
|
|
898
|
+
/** §3.1 — the candidate-eligibility context, read once per snapshot/freeze pass off the
|
|
899
|
+
* committed faces and governance accounts (write and read sides share this predicate). */
|
|
900
|
+
private consolidationEligibility;
|
|
901
|
+
/**
|
|
902
|
+
* §1.2-1 — verb ①: snapshot the candidate set (committed, side-effect-free read face), gate-
|
|
903
|
+
* checked (§2.2 案 c: mutex and the same-scope throttle floor are HARD; the time and session
|
|
904
|
+
* gates yield to `force` with a requestId audit). Increments are CHANGE-DRIVEN: the candidate
|
|
905
|
+
* set is the eligible entries that are new to — or moved rev against — the last completed run's
|
|
906
|
+
* fingerprint; a lost/absent fingerprint answers the full set (conservative). `full: true`
|
|
907
|
+
* requests the full set explicitly (a host's re-distillation choice).
|
|
908
|
+
*/
|
|
909
|
+
snapshotConsolidationCandidates(scope: string, opts?: {
|
|
910
|
+
force?: {
|
|
911
|
+
requestId: string;
|
|
912
|
+
};
|
|
913
|
+
full?: boolean;
|
|
914
|
+
}): Promise<{
|
|
915
|
+
cycleToken: string;
|
|
916
|
+
watermark: {
|
|
917
|
+
epoch: number;
|
|
918
|
+
at: number;
|
|
919
|
+
};
|
|
920
|
+
candidates: Array<{
|
|
921
|
+
entry: MemoryEntry;
|
|
922
|
+
marked: boolean;
|
|
923
|
+
}>;
|
|
924
|
+
}>;
|
|
925
|
+
/**
|
|
926
|
+
* §1.2-2/§1.2-3 — verb ②: mechanical acceptance + plan FREEZE + apply-to-terminal. Structural
|
|
927
|
+
* violations (scan findings, out-of-snapshot attribution, caps, the instruction hard gate, the
|
|
928
|
+
* supersession fuse) refuse the WHOLE plan — zero partial freeze; per-product eligibility drift
|
|
929
|
+
* (an input's rev moved / an input got challenged between snapshot and freeze) refuses THAT
|
|
930
|
+
* product with a disclosed reason while the rest freeze (§1.2-2 冻结时资格复验).
|
|
931
|
+
*/
|
|
932
|
+
commitConsolidationPlan(cycleToken: string, proposal: ConsolidationProposal, opts: {
|
|
933
|
+
requestId: string;
|
|
934
|
+
}): Promise<ConsolidationCommitReceipt>;
|
|
935
|
+
/** §1.2-2/§1.2-3 — the freeze: mechanical acceptance over the committed NOW + assembly of the
|
|
936
|
+
* immutable plan workbook (engine-authored frontmatter: minted ids, folded origin, distilled
|
|
937
|
+
* blocks with the carrierRev anchor; per-product rev pre-computed). */
|
|
938
|
+
private freezeConsolidationPlan;
|
|
939
|
+
/**
|
|
940
|
+
* §1.3/§1.5-2 — the apply-to-terminal leg (first run AND every replay: the plan is the ONLY
|
|
941
|
+
* input — no distillation ever re-runs). Idempotent by construction: product adds ride
|
|
942
|
+
* `guard: "absent"` over plan-frozen ids (an identical replay is a no-op), directed patches
|
|
943
|
+
* judge conflicts TERMINAL-STATE-wise (update: committed rev == plannedPostRev ⇒ satisfied;
|
|
944
|
+
* delete: absence ⇒ satisfied — this arm has no re-record leg, so acknowledging absence can
|
|
945
|
+
* revive nothing; attribution degrades to "unattributed" across the crash micro-window while
|
|
946
|
+
* the judgment never degrades, r2-4/G25).
|
|
947
|
+
*/
|
|
948
|
+
private applyConsolidationPlan;
|
|
949
|
+
/** Terminal settlement: per-item terminals → the plan terminal, the credential write (§1.5-3:
|
|
950
|
+
* ALL-satisfied only), the gate watermark/fingerprint/seat update and the announcements. */
|
|
951
|
+
private settleConsolidationPlan;
|
|
952
|
+
private consolidationReceipt;
|
|
953
|
+
/**
|
|
954
|
+
* §1.7/§1.8 — verb ③: idempotent replay/convergence. Replays the seated plan to terminal (a
|
|
955
|
+
* crash at ANY point converges — the plan is the only input), releases an orphan seat (a crash
|
|
956
|
+
* between seat and freeze left nothing to replay: the plan never returned to its caller), and
|
|
957
|
+
* runs the RETROACTIVE challenge propagation sweep (D-6a: committed products whose
|
|
958
|
+
* `distilled.inputs` name a currently-challenged id are challenged mechanically — the lineage
|
|
959
|
+
* travels with the entry, so products that arrived by sync/bundle propagate too, r1-4/G19).
|
|
960
|
+
*/
|
|
961
|
+
reconcileConsolidation(scope: string): Promise<ConsolidationReconcileReport>;
|
|
962
|
+
/** The stale-claim orphan-seat release (extracted for the in-flight split above): crash between
|
|
963
|
+
* seat and plan-file write — nothing frozen, nothing to replay — release the seat with an audit
|
|
964
|
+
* line (the commit call never returned a planId to its caller). */
|
|
965
|
+
private releaseOrphanConsolidationSeat;
|
|
966
|
+
/** ---- D-6a retroactive propagation sweep (reconcile's second half) ---- */
|
|
967
|
+
private runConsolidationPropagationSweep;
|
|
968
|
+
/** The gate half of a terminal plan's settlement (idempotent, seat-guarded — reconcile's repair
|
|
969
|
+
* arm for a crash between the plan-state write and the gate update). */
|
|
970
|
+
private settleGateForTerminalPlan;
|
|
971
|
+
/** The settlement fingerprint's FRESH eligibility predicate: (id, rev) stays in the map only
|
|
972
|
+
* while it is committed at that rev, unchallenged and unsuperseded RIGHT NOW. Undefined ⇔ the
|
|
973
|
+
* read failed — the caller drops the fingerprint (full candidates next round). */
|
|
974
|
+
private settleFingerprintFilter;
|
|
975
|
+
/**
|
|
976
|
+
* §1.5-3 — verb ④: the host valve over a parked/corrupt plan. `discard` = the intents settle
|
|
977
|
+
* ABANDONED explicitly (audited; never a silent drop — unfulfilled and given-up stay two
|
|
978
|
+
* distinguishable account facts, G24) and the seat releases; `retry` = the plan's unfulfilled
|
|
979
|
+
* intents re-judge NOW into a NEW plan (fresh freeze, fresh baseRevs — the ONE authorized
|
|
980
|
+
* re-judgment point) which applies immediately. Both audited by requestId.
|
|
981
|
+
*/
|
|
982
|
+
resolveConsolidationPlan(planId: string, action: "discard" | "retry", opts: {
|
|
983
|
+
requestId: string;
|
|
984
|
+
}): Promise<ConsolidationResolveReceipt>;
|
|
985
|
+
/** Rebuild the directed patches for a retry: the ORIGINAL terminal intent (recovered from the
|
|
986
|
+
* conflicted plan's frozen patch) re-judged against the committed now.
|
|
987
|
+
*
|
|
988
|
+
* Some of those judgments settle a row as CONFLICT here, without ever offering a patch to the
|
|
989
|
+
* store — the target moved out of the plan's scope, it vanished, or the frozen entry is not
|
|
990
|
+
* replayable. Those rows get no backend answer, so they would otherwise reach the parked plan
|
|
991
|
+
* with no per-target reason anywhere: `reasons` carries them out to the retry plan's audit, which
|
|
992
|
+
* is where the conflict notice tells a host to look. */
|
|
993
|
+
private reconstructIntentsForRetry;
|
|
994
|
+
/** §6.2 — host read face: every plan's summary (audit enumeration, zero model surface). */
|
|
995
|
+
listConsolidationPlans(): ConsolidationPlanSummary[];
|
|
996
|
+
/** §6.2 — host read face: one scope's gate row (undefined when the store has no row — reading
|
|
997
|
+
* never creates the file, so the OFF deployment stays byte-identical, G1). */
|
|
998
|
+
readConsolidationGate(scope: string): ConsolidationGateRow | undefined;
|
|
766
999
|
/** The committed, side-effect-free audit read: the adoption-restricted committed view when the
|
|
767
1000
|
* backend offers one (zero-copy File — ledger+shadow, no disk adoption), else the non-adopting
|
|
768
1001
|
* retrieval view (copy-out File), else the backend itself (Pg/TiDB shapes — naturally committed
|