@sema-agent/core 5.36.0 → 5.38.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 +124 -0
- package/dist/agents/subagent.d.ts +10 -0
- package/dist/agents/subagent.js +6 -0
- package/dist/agents/teacher.js +3 -0
- package/dist/agents/team.d.ts +7 -1
- package/dist/agents/team.js +11 -9
- package/dist/agents/verify.js +3 -0
- package/dist/core/auto-mode-prompt-assets.d.ts +5 -3
- package/dist/core/auto-mode-prompt-assets.js +1 -1
- package/dist/core/checkpoint-store.d.ts +26 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +129 -2
- package/dist/core/hooks.js +20 -3
- package/dist/core/memory-engine/engine.d.ts +142 -0
- package/dist/core/memory-engine/engine.js +264 -2
- package/dist/core/memory-engine/file-backend.d.ts +490 -16
- package/dist/core/memory-engine/file-backend.js +1099 -36
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +42 -2
- package/dist/core/memory-engine/layout.js +76 -12
- package/dist/core/memory-engine/memory-backend-contract.d.ts +13 -0
- package/dist/core/memory-engine/memory-backend-contract.js +89 -0
- package/dist/core/protocol-table.d.ts +4 -4
- package/dist/core/runner/assemble-result.d.ts +5 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +17 -0
- package/dist/core/runner/prepare-config-doors.js +33 -2
- package/dist/core/runner/prepare-memory.d.ts +11 -1
- package/dist/core/runner/prepare-memory.js +48 -2
- package/dist/core/runner/prepare-task.d.ts +22 -2
- package/dist/core/runner/prepare-task.js +125 -42
- package/dist/core/runner/runtask.js +50 -11
- package/dist/core/tool-model-gate.d.ts +125 -0
- package/dist/core/tool-model-gate.js +303 -0
- package/dist/core/tool-policy.d.ts +1 -1
- package/dist/core/types.d.ts +284 -1
- package/dist/core/types.js +21 -0
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -2
- package/dist/orchestration/builtin-workflows.d.ts +68 -6
- package/dist/orchestration/builtin-workflows.js +26 -9
- package/dist/orchestration/run-workflow-tool.d.ts +10 -1
- package/dist/orchestration/run-workflow-tool.js +70 -27
- package/dist/orchestration/workflow-script-store.d.ts +8 -3
- package/dist/prompts/coordinator.d.ts +4 -1
- package/dist/prompts/coordinator.js +8 -0
- package/dist/prompts/default.d.ts +14 -4
- package/dist/prompts/default.js +2 -1
- package/dist/scenarios/full-body.d.ts +5 -0
- package/dist/scenarios/full-body.js +8 -4
- package/dist/tools/fs/fs-shared.d.ts +3 -2
- package/dist/tools/fs/fs-shared.js +19 -9
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +24 -1
|
@@ -23,6 +23,271 @@ export declare function scanEntryFiles(dir: string, opts?: {
|
|
|
23
23
|
/** Harvest gate hook: called for every path the scan SKIPS, with why (backend scans ignore it). */
|
|
24
24
|
onSkip?: (path: string, kind: "symlink" | "depth" | "nonmd" | "dotfile" | "unreadable") => void;
|
|
25
25
|
}): ScannedEntryFile[];
|
|
26
|
+
/** design/178 v2-b §3.3(b) — the explicit erasure selector (each form resolves to an explicit id
|
|
27
|
+
* list before anything is deleted; there is no whole-store selector). */
|
|
28
|
+
export type ErasureSelect = {
|
|
29
|
+
ids: readonly string[];
|
|
30
|
+
} | {
|
|
31
|
+
scope: string;
|
|
32
|
+
} | {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
};
|
|
35
|
+
/** design/178 v2-b §3.3(c) — one erased row's coordinates (the snapshot's tagged binding law,
|
|
36
|
+
* without the account-trace extras: a wire validator refuses hybrid objects). */
|
|
37
|
+
export type ErasedBinding = {
|
|
38
|
+
state: "bound";
|
|
39
|
+
scope: string;
|
|
40
|
+
slug: string;
|
|
41
|
+
} | {
|
|
42
|
+
state: "unbound";
|
|
43
|
+
};
|
|
44
|
+
/** The input of the erasure lane (engine host API `eraseMemoryEntries` and the File capability face
|
|
45
|
+
* `eraseWithEvidence` share it; the File face ignores `allowUnevidenced` — with the evidence
|
|
46
|
+
* capability present there is nothing to degrade to). */
|
|
47
|
+
export interface EraseMemoryEntriesInput {
|
|
48
|
+
/** Idempotency identity, caller-minted (challenge-family law: the engine never mints one). */
|
|
49
|
+
requestId: string;
|
|
50
|
+
select: ErasureSelect;
|
|
51
|
+
/** Evidence-capability-ABSENT degradation opt-in (engine lane only; default = loud refusal). */
|
|
52
|
+
allowUnevidenced?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* design/178 v2-b §3.3(c) — the erasure attestation (the deliverable a compliance caller archives).
|
|
56
|
+
* Every claim is black-box re-checkable: an `erased` row is verified by `committedSnapshotOf(id)`
|
|
57
|
+
* answering `"absent"`, the chain containing its `evidenceEv` delete row, and `getByIds([id])`
|
|
58
|
+
* answering empty.
|
|
59
|
+
*/
|
|
60
|
+
export interface MemoryErasureAttestation {
|
|
61
|
+
/** Version envelope — a consumer reading `v > 1` must refuse, never reinterpret. */
|
|
62
|
+
v: 1;
|
|
63
|
+
requestId: string;
|
|
64
|
+
at: number;
|
|
65
|
+
/** `"complete"` ⇔ conflicts empty ∧ every pinned id ∈ erased ∪ erasedPreviously ∪ (notFound with
|
|
66
|
+
* `custodyState === "complete"`). Everything else is `"partial"` (replay converges it). */
|
|
67
|
+
status: "complete" | "partial";
|
|
68
|
+
/** `"journal"` = the File evidence path; `"none"` = the explicit `allowUnevidenced` degradation
|
|
69
|
+
* lane (always present — a silent downgrade is the forbidden shape). */
|
|
70
|
+
evidenceCapability: "journal" | "none";
|
|
71
|
+
/** The evidence chain's integrity as read at assembly (v2-a custody tri-state law). `"damaged"`
|
|
72
|
+
* ⇒ every notFound row carries `historyUnknown` and `erasedPreviously` is never produced ("no
|
|
73
|
+
* row" cannot be historically judged over a chain with a known hole). */
|
|
74
|
+
custodyState: "complete" | "damaged" | "capability-absent";
|
|
75
|
+
/** The selector, echoed verbatim. */
|
|
76
|
+
select: ErasureSelect;
|
|
77
|
+
/** sha256 hex of the selector's canonical JSON — equals the anchor row's (`"none"` mode: computed
|
|
78
|
+
* for this call only; there is no anchor to reconcile against). */
|
|
79
|
+
selectHash: string;
|
|
80
|
+
/** The pinned id set (= the anchor row's `ids`; a replay reads it back, never re-resolves). */
|
|
81
|
+
resolvedIds: string[];
|
|
82
|
+
erased: Array<{
|
|
83
|
+
id: string;
|
|
84
|
+
rev: string;
|
|
85
|
+
binding: ErasedBinding;
|
|
86
|
+
/** The delete evidence row's `ev` — absent in `"none"` mode. */
|
|
87
|
+
evidenceEv?: string;
|
|
88
|
+
/** Distinct census-confirmed projection files this transaction physically deleted (the sweep
|
|
89
|
+
* arm can answer >1: stray copies and P≥2 unbound duplicates are all removed). */
|
|
90
|
+
projectionsRemoved: number;
|
|
91
|
+
/** Contributing sessions (pre-capture ∪ the same-lock lineage capture-and-clear; `"none"` mode
|
|
92
|
+
* answers `[]`). NON-durable beyond the chain's pre-capture: a replay after a crash carries
|
|
93
|
+
* the chain's set — honest downgrade, never a fabricated rebuild. */
|
|
94
|
+
sessions: string[];
|
|
95
|
+
}>;
|
|
96
|
+
/** No row at judgment time and not claimable as erased under THIS request. Covers never-existed /
|
|
97
|
+
* organically deleted / deleted by another request — the chain can distinguish them, the
|
|
98
|
+
* attestation does not pretend to. `historyUnknown` ⇔ `custodyState !== "complete"`. */
|
|
99
|
+
notFound: Array<{
|
|
100
|
+
id: string;
|
|
101
|
+
historyUnknown?: true;
|
|
102
|
+
}>;
|
|
103
|
+
/** Chain-read rows: an origin-ABSENT delete row carrying THIS requestId (per-request judgment —
|
|
104
|
+
* another request's delete of the id answers notFound, never a claim). Present only when
|
|
105
|
+
* non-empty; never produced in `"none"` mode or over a damaged chain. */
|
|
106
|
+
erasedPreviously?: Array<{
|
|
107
|
+
id: string;
|
|
108
|
+
ev: string;
|
|
109
|
+
at: number;
|
|
110
|
+
/** The pre-delete binding from the most recent same-request delete row that RECORDED one —
|
|
111
|
+
* carried so a REPLAY can still run the index-line sweep (the ledger row is gone; the evidence
|
|
112
|
+
* row is the only surviving source of the projection path). `ev`/`at` come from the MOST RECENT
|
|
113
|
+
* same-request delete row (which may be a later, binding-less re-erase). Absent in THREE cases:
|
|
114
|
+
* no same-request delete row ever recorded a binding (unbound / pre-v2-form), or the recorded
|
|
115
|
+
* path is OCCUPIED now (a live account row binds that scope+slug, or bytes exist at the path —
|
|
116
|
+
* the binding is known but suppressed: the index line there belongs to the occupant, and a
|
|
117
|
+
* consumer must not read absence as "no residue possible"). */
|
|
118
|
+
from?: {
|
|
119
|
+
scope: string;
|
|
120
|
+
slug: string;
|
|
121
|
+
};
|
|
122
|
+
}>;
|
|
123
|
+
/** Per-id planning-stage faults (io/plan errors under the lock — there is no CAS loser inside a
|
|
124
|
+
* single lock span). Non-fatal; a replay converges them. */
|
|
125
|
+
conflicts: Array<{
|
|
126
|
+
id: string;
|
|
127
|
+
reason: string;
|
|
128
|
+
}>;
|
|
129
|
+
residuals: {
|
|
130
|
+
/** Quarantine files whose frontmatter id is in the pinned set (preserve-not-delete: the
|
|
131
|
+
* attestation enumerates, an explicit second action clears). */
|
|
132
|
+
quarantineHits: string[];
|
|
133
|
+
/** Quarantine files that could not be attributed (fragments / non-entry shapes) — the honest
|
|
134
|
+
* bound of the enumeration. */
|
|
135
|
+
quarantineOpaque: number;
|
|
136
|
+
/** Derived MEMORY.md index files that may STILL carry a line naming an erased entry, because
|
|
137
|
+
* the erase-time index sweep could not read or rewrite them (a non-file at the index path, a
|
|
138
|
+
* non-ENOENT read failure, or a refused write — an entry may carry a parenthesized failure
|
|
139
|
+
* detail). Present only when non-empty. Minted by the ENGINE lane
|
|
140
|
+
* (`MemoryEngine.eraseMemoryEntries` — the derived index is the engine's plane; this backend
|
|
141
|
+
* face answers without the seat). The next successful materialize's rebuild also clears
|
|
142
|
+
* orphaned lines, but a one-off compliance erasure cannot count on one — hence the disclosure
|
|
143
|
+
* instead of a silent clean residual set. */
|
|
144
|
+
indexUncleared?: string[];
|
|
145
|
+
/** Propagation boundary, self-declared always: deletion is a THIS-store fact; remote-peer
|
|
146
|
+
* convergence is the sync deployment's half to prove. */
|
|
147
|
+
propagation: "local-store-only";
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** Canonical JSON (recursively sorted keys, no whitespace) — the erasure protocol's ONE comparison
|
|
151
|
+
* and hash basis: `selectHash` complains bind to it, and evidence rows are written/compared in this
|
|
152
|
+
* form so a re-serialized import (key order changed by a middle layer) still compares equal. */
|
|
153
|
+
export declare function canonicalJsonStringify(value: unknown): string;
|
|
154
|
+
/** sha256 hex over the selector's canonical JSON + LF — the replay-identity judge (§4.6) and the
|
|
155
|
+
* anchor validator's recomputation basis (a 64-hex shape check alone would let a forged anchor swap
|
|
156
|
+
* the selector under a kept hash). */
|
|
157
|
+
export declare function erasureSelectHash(select: ErasureSelect): string;
|
|
158
|
+
/** Erasure input validation, shared by the engine host API and the File capability face (one rule
|
|
159
|
+
* set — the selector the anchor row echoes verbatim must pass the anchor's own validator, so the
|
|
160
|
+
* entry check and the chain check are the same predicate). Returns the violation, or undefined. */
|
|
161
|
+
export declare function erasureRequestInvalid(input: EraseMemoryEntriesInput): string | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Snapshot the erasure input into PLAIN data by reading every accessor exactly once (round-2 #3/#5):
|
|
164
|
+
* validation and use must observe the SAME value, so an accessor-backed (getter/Proxy) selector or
|
|
165
|
+
* requestId cannot pass validation on read #1 and yield a different (unvalidated) set on read #2.
|
|
166
|
+
* The returned object is inert — its `requestId` is a captured primitive and its `select` is a fresh
|
|
167
|
+
* plain object. Malformed shapes (a non-object select, or one with several/no keys) capture as-is so
|
|
168
|
+
* the validator that runs next reports them; the capture never throws.
|
|
169
|
+
*/
|
|
170
|
+
export declare function captureErasureInput(input: EraseMemoryEntriesInput): EraseMemoryEntriesInput;
|
|
171
|
+
/**
|
|
172
|
+
* design/178 v2-a §1.3 — ONE `transfers.jsonl` evidence row on the PUBLIC audit face, as an OPEN
|
|
173
|
+
* discriminated form: the base keys (`ev` — the row's unique identity, `channel` — the event kind,
|
|
174
|
+
* `at` — ms epoch) are the contract; everything else is variant payload surfaced through the index
|
|
175
|
+
* signature. Known channels today (each validated in full by the store's own rule set):
|
|
176
|
+
* - `"adopted-move"` / `"applyPatches-move"`: an ownership transfer of one id — payload
|
|
177
|
+
* `{ id, from: {scope, slug}, to: {scope, slug} }`;
|
|
178
|
+
* - `"migration-bind"`: an unbound row converging to a binding — payload `{ id, to }` (`from`
|
|
179
|
+
* optional: honest absence, an unbound row has no prior coordinates);
|
|
180
|
+
* - `"migration"`: the one-shot v1→v2 baseline summary — payload `{ boundRows, unboundRows }`
|
|
181
|
+
* (store management history, never per-entry custody).
|
|
182
|
+
* `channel` is deliberately `string`, not a closed union: later releases append new variants to the
|
|
183
|
+
* SAME chain (one custody chain, one recovery protocol — never a second evidence file), and a closed
|
|
184
|
+
* union would break every consumer at the source level on each addition. CONSUMER OBLIGATION: tolerate
|
|
185
|
+
* unknown channels (report/skip/display raw — never an exhaustive `switch` that throws); the per-id
|
|
186
|
+
* custody read passes them through so a chain written by a newer engine still answers here.
|
|
187
|
+
*/
|
|
188
|
+
export interface TransferEvidence {
|
|
189
|
+
/** Unique event id (the append-idempotency key of the evidence chain). */
|
|
190
|
+
ev: string;
|
|
191
|
+
/** Event kind — open on purpose; see the known-channel table above. */
|
|
192
|
+
channel: string;
|
|
193
|
+
/** ms epoch of the event. */
|
|
194
|
+
at: number;
|
|
195
|
+
/** Variant payload (known channels documented above; unknown channels' payload rides verbatim). */
|
|
196
|
+
[key: string]: unknown;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* design/178 v2-a §1.3 — the committed-account BINDING half of one ledger row, shared verbatim by
|
|
200
|
+
* {@link CommittedEntrySnapshot} and `EntryProvenanceAccount.binding` (one tagged definition — never
|
|
201
|
+
* a look-alike copy: an untagged `{scope, slug}` beside `{state: "unbound"}` can be satisfied by one
|
|
202
|
+
* hybrid object under both readings). `"unbound"` is a LEGAL transitional state (a v1→v2 migration
|
|
203
|
+
* row whose projection could not be derived), never a refusal.
|
|
204
|
+
*/
|
|
205
|
+
export type CommittedBinding = {
|
|
206
|
+
state: "bound";
|
|
207
|
+
scope: string;
|
|
208
|
+
slug: string;
|
|
209
|
+
at?: number;
|
|
210
|
+
prev?: {
|
|
211
|
+
scope: string;
|
|
212
|
+
slug: string;
|
|
213
|
+
at: number;
|
|
214
|
+
};
|
|
215
|
+
} | {
|
|
216
|
+
state: "unbound";
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* design/178 v2-a §1.3 — the AUDIT SNAPSHOT of one entry's committed account: the account half
|
|
220
|
+
* (`rev` + {@link CommittedBinding}) and the content half are SEPARATE, because the account's
|
|
221
|
+
* authority is the ledger row while the content may be independently unavailable — an auditor asking
|
|
222
|
+
* "where is this entry bound, at which rev" gets the account facts even when no committed carrier
|
|
223
|
+
* can be produced ("cannot read it" must never collapse into "it does not exist").
|
|
224
|
+
* - `state: "absent"` — the account has NO row for this id (never a throw, never a guess);
|
|
225
|
+
* - `content.state: "present"` — the committed entry is servable: the committed bytes (control-plane
|
|
226
|
+
* shadow; for a pre-shadow row, the in-place projection at the bound coordinates carrying the
|
|
227
|
+
* committed rev) with the projection carrier in place at the bound address;
|
|
228
|
+
* - `content.state: "unavailable"` — the row exists (binding/rev answered) but no committed carrier
|
|
229
|
+
* serves it: `"shadowless-legacy"` = no shadow copy exists and the bound projection cannot vouch
|
|
230
|
+
* (missing/divergent/foreign id); `"carrier-missing"` = the projection carrier is not in place at
|
|
231
|
+
* the committed address (deleted or squatted out-of-band) or the shadow cannot carry the
|
|
232
|
+
* committed rev — including every UNBOUND row (no committed address exists to mint
|
|
233
|
+
* `MemoryEntry.scope`/`slug` from; the shadow bytes, where present, stay readable via
|
|
234
|
+
* `readCommittedShadow`).
|
|
235
|
+
*/
|
|
236
|
+
export type CommittedEntrySnapshot = {
|
|
237
|
+
state: "absent";
|
|
238
|
+
id: string;
|
|
239
|
+
} | {
|
|
240
|
+
state: "row";
|
|
241
|
+
id: string;
|
|
242
|
+
rev: string;
|
|
243
|
+
binding: CommittedBinding;
|
|
244
|
+
content: {
|
|
245
|
+
state: "present";
|
|
246
|
+
entry: MemoryEntry;
|
|
247
|
+
} | {
|
|
248
|
+
state: "unavailable";
|
|
249
|
+
reason: "shadowless-legacy" | "carrier-missing";
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* design/178 v2-a §1.3 — the LEDGER-DRIVEN scope enumeration's return form. `rows` carries every
|
|
254
|
+
* BOUND row whose `binding.scope` is in the requested set (the account is the enumeration authority
|
|
255
|
+
* — a shadow-backed row whose projection was deleted out-of-band is still answered; a disk scan
|
|
256
|
+
* would miss it). `unbound` is the STORE-LEVEL appendix of unbound row ids (they have no scope to
|
|
257
|
+
* belong to — the enumerating consumer disposes of them explicitly: an export lists them as
|
|
258
|
+
* residuals, an erasure scope-selector never resolves them). `complete: false` means a stable
|
|
259
|
+
* committed snapshot could not be taken (the journal-quiescence fence stayed contended past its
|
|
260
|
+
* bound) and the caller must FAIL CLOSED — a partial answer is never dressed as a full one; the
|
|
261
|
+
* union is DISCRIMINATED on `complete` so the incomplete arm cannot even carry rows to misread.
|
|
262
|
+
*/
|
|
263
|
+
export type CommittedScopeSnapshots = {
|
|
264
|
+
complete: true;
|
|
265
|
+
rows: Array<Extract<CommittedEntrySnapshot, {
|
|
266
|
+
state: "row";
|
|
267
|
+
}>>;
|
|
268
|
+
unbound: string[];
|
|
269
|
+
} | {
|
|
270
|
+
complete: false;
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* design/178 v2-a §1.3 — the per-id custody read's return form. `"damaged"` covers every chain whose
|
|
274
|
+
* integrity is impeached while a sound reading still exists to answer with: a torn tail (crash
|
|
275
|
+
* mid-append — the sound prefix IS the answer and the file is NOT touched: inspection and repair are
|
|
276
|
+
* separate duties; the recovery leg quarantines the fragment, and an audit read that "helpfully"
|
|
277
|
+
* rewrites the evidence it is auditing has destroyed its own subject), the evidence-loss cross-check
|
|
278
|
+
* (a prev-bearing ledger row beside a chain with zero events for the id), and the ev-identity law
|
|
279
|
+
* (one `ev` under two DIFFERENT payloads — the chain was spliced/corrupted out of band; the store's
|
|
280
|
+
* own write path refuses to operate on it, so custody must never read it back as `"complete"`).
|
|
281
|
+
* `reason` carries the impeachment detail when one is known (today: the ev-identity arm); its
|
|
282
|
+
* absence means the damage form speaks for itself (torn tail / evidence loss).
|
|
283
|
+
* `"capability-absent"` never originates here — it is the ENGINE's honest answer for a backend
|
|
284
|
+
* without this capability face.
|
|
285
|
+
*/
|
|
286
|
+
export interface EntryCustodyReport {
|
|
287
|
+
state: "complete" | "damaged";
|
|
288
|
+
events: TransferEvidence[];
|
|
289
|
+
reason?: string;
|
|
290
|
+
}
|
|
26
291
|
/** Construction options for {@link FileMemoryEngineBackend}. */
|
|
27
292
|
export interface FileMemoryEngineBackendOptions {
|
|
28
293
|
/** ⚠️ 分家坑(service 报告 2026-07-09):不显式传时,控制平面按无参 `resolveMemoryEngineRoot()`
|
|
@@ -104,6 +369,13 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
104
369
|
private adoptionNoticeKeys;
|
|
105
370
|
/** Test seam (§7.4 r4-④ arm): force a post-commit-point transfer-append failure. */
|
|
106
371
|
private transfersAppendFault?;
|
|
372
|
+
/** v2-b §3-4 — the resurrection backstop's chain digest, keyed by the evidence log's stat
|
|
373
|
+
* fingerprint (size + mtimeMs — append-only growth and a torn-tail shrink both move `size`).
|
|
374
|
+
* NEVER a mount-lifetime cache: the fingerprint is re-verified under the txn mutex on every
|
|
375
|
+
* consultation, so a sibling process's erasure invalidates it (r3: a B-process erasure must not
|
|
376
|
+
* leave an A-process adopting off the old deleted-id set). The digest carries its chainState —
|
|
377
|
+
* a damaged read caches as damaged, never as a bare (falsely reassuring) id set. */
|
|
378
|
+
private deletedIdsDigest;
|
|
107
379
|
/** B2 — inbound-gate findings accumulated by read-side syncs; the engine drains them into
|
|
108
380
|
* `HarvestReport.inboundFindings` at harvest. */
|
|
109
381
|
private inboundFindings;
|
|
@@ -182,19 +454,97 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
182
454
|
* is a no-op version check.
|
|
183
455
|
*/
|
|
184
456
|
private migrateLockedIfNeeded;
|
|
185
|
-
/**
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
457
|
+
/** Is the durable chain-degradation marker in place? Presence is ENOENT-only-absent (a probe
|
|
458
|
+
* failure is never read as "not degraded" — fail-closed). */
|
|
459
|
+
private chainDegradedMarkerPresent;
|
|
460
|
+
/** §3-4 r6 — mint the chain-degradation marker DURABLY (wx + fsync + read-back verification).
|
|
461
|
+
* Idempotent on an existing marker. Throws when the marker cannot be proven on disk — the caller
|
|
462
|
+
* (the heal) must then ABORT with the chain untouched: truncating first would reopen the exact
|
|
463
|
+
* window this marker closes ("chain reads complete, no marker" after an out-of-band tear). */
|
|
464
|
+
private mintChainDegradedMarker;
|
|
465
|
+
/**
|
|
466
|
+
* Read the WHOLE evidence chain in order (validated, collision-checked), healing a TORN TAIL (a
|
|
467
|
+
* crash mid-append): the fragment is quarantined for evidence, the sound prefix stands (lines are
|
|
468
|
+
* self-contained — damage never spreads backward). Any NON-tail corruption is fail-closed (B3
|
|
469
|
+
* family). Two heal forms (§3-4 r5/r6):
|
|
470
|
+
* - transaction journal PRESENT beside the tear = the honest crash form — the committed-pending
|
|
471
|
+
* protocol re-appends the lost row from the journal, so the healed chain is genuinely complete;
|
|
472
|
+
* - journal ABSENT = out-of-band tail damage — the lost row is unrecoverable, so the heal mints
|
|
473
|
+
* the durable {@link CHAIN_DEGRADED_FILE} marker BEFORE it truncates (write order is
|
|
474
|
+
* load-bearing: isolate fragment → marker wx + read-back → only then truncate; a marker
|
|
475
|
+
* failure aborts the heal with the chain byte-untouched).
|
|
476
|
+
* Same-`ev` collision law (§4.4, read-time arm): canonical-form-equal duplicates are tolerated
|
|
477
|
+
* (append-idempotency residue); DIFFERENT payloads under one ev are fail-closed — an event id is
|
|
478
|
+
* one identity, and a reader must never pick a side by file order.
|
|
479
|
+
*/
|
|
480
|
+
private readTransferChain;
|
|
481
|
+
/** The chain's ev → canonical-payload map (§4.4 upgraded load shape — the append path compares
|
|
482
|
+
* PAYLOADS, never mere membership). */
|
|
483
|
+
private readTransferEvents;
|
|
484
|
+
/**
|
|
485
|
+
* v2-b §3-4 — the deleted-id digest behind the resurrection backstop: every `delete` evidence
|
|
486
|
+
* row's id (LOCAL AND IMPORTED — an origin filter here would disarm exactly the cross-store
|
|
487
|
+
* anti-resurrection evidence a custody import carries; the origin distinction belongs to the
|
|
488
|
+
* replay/anchor judgments alone), read WITHOUT healing (a backstop that rewrites the chain it
|
|
489
|
+
* consults has destroyed its own evidence; a torn tail answers `"damaged"` with the file
|
|
490
|
+
* byte-untouched). Non-tail corruption stays the fail-closed throw. Unknown channels are
|
|
491
|
+
* tolerated (open-form law). Must be called with the txn mutex held — the stat fingerprint is
|
|
492
|
+
* the cross-process staleness judge, and holding the lock is what makes "fingerprint unchanged
|
|
493
|
+
* ⇒ digest current" sound.
|
|
494
|
+
*/
|
|
495
|
+
private deletedIdsDigestLocked;
|
|
496
|
+
/**
|
|
497
|
+
* v2-b §3-4 — the CHAIN-LEVEL resurrection backstop's verdict for one NEW id-bearing disk file
|
|
498
|
+
* (an id with no committed account row) meeting the adopting read:
|
|
499
|
+
* - `"erased"` — the chain carries a delete evidence row for the id: the bytes are a deleted
|
|
500
|
+
* entry resurfacing (whatever journal shape produced the "account gone, disk copy back"
|
|
501
|
+
* scene), refused + quarantined — restoration is an EXPLICIT write with a new id, never a
|
|
502
|
+
* disk replant;
|
|
503
|
+
* - `"degraded"` — the chain cannot prove the deleted set (durable degradation marker, or a
|
|
504
|
+
* live torn tail): "the id is not on the chain" is no evidence on an incomplete chain, so
|
|
505
|
+
* EVERY new id-bearing file is refused adoption (left on disk, not served) until the explicit
|
|
506
|
+
* rebuild clears the state — the same epistemic rule a custody import's completeness
|
|
507
|
+
* precondition applies;
|
|
508
|
+
* - `"clear"` — adoption proceeds normally.
|
|
509
|
+
* `applyPatches` adds are NOT consulted here (host authority — an explicit add of a previously
|
|
510
|
+
* erased id is a legitimate new commit).
|
|
511
|
+
*/
|
|
512
|
+
private resurrectionBackstopVerdict;
|
|
513
|
+
/** v2-b §3-4 — the LOCK-FREE retrieval face of the deleted-id set (retrievalView / batch-plan
|
|
514
|
+
* locateById). Reuses the fingerprint-cached digest: a deleted id's replanted bytes never reach
|
|
515
|
+
* the retrieval face either. Best-effort by construction — a lock-free reader carries the same
|
|
516
|
+
* cross-process staleness the whole retrieval face does, and a degraded chain filters only its
|
|
517
|
+
* known deleted set (it never refuses-all here — retrieval must stay a plane report). */
|
|
518
|
+
private retrievalDeletedIds;
|
|
519
|
+
/** v2-b §3-4 (round-3 #1) — a PURE, fresh read of the committed ledger rows for the retrieval
|
|
520
|
+
* backstop's account check: bytes → parsed rows, installing no read cache and no schema memo — so
|
|
521
|
+
* the guard's account view is as fresh as its chain digest,
|
|
522
|
+
* never a stale instance cache paired with a fresh deletion read. A v1-form or absent ledger
|
|
523
|
+
* answers empty (a v1 store has no delete rows to police anyway). FAULT DIRECTION, stated
|
|
524
|
+
* truthfully (rescan corrected the original note, which claimed the opposite): a read/parse
|
|
525
|
+
* FAULT also answers empty, and with an empty account the consuming filter WITHHOLDS every id
|
|
526
|
+
* on the delete list — conservative on purpose (a host re-add cannot be distinguished from a
|
|
527
|
+
* replant over an unreadable account, and serving bytes that carry delete evidence during a
|
|
528
|
+
* fault window would reopen exactly the resurrection the backstop exists to close). No erase
|
|
529
|
+
* CLAIM is minted (nothing is attested); entries with no delete evidence keep serving. The
|
|
530
|
+
* fault is DISCLOSED on the announcement lane (坏值响亮度 — the pre-fix swallow was silent),
|
|
531
|
+
* once per fault code per mount. */
|
|
532
|
+
private freshCommittedRowsPure;
|
|
189
533
|
/** §1.1 commit-point honesty: any failure BEFORE the journal write must refuse the whole
|
|
190
|
-
* transaction cleanly — so the append target's writability (and the log's soundness
|
|
191
|
-
* here, never discovered after the point of no return. */
|
|
534
|
+
* transaction cleanly — so the append target's writability (and the log's soundness, ev-collision
|
|
535
|
+
* law included) is probed here, never discovered after the point of no return. */
|
|
192
536
|
private precheckTransfersAppendable;
|
|
193
537
|
/** Append evidence events, idempotent by `ev` (the recovery leg re-runs this for a journal whose
|
|
194
|
-
* append crashed).
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
538
|
+
* append crashed). Rows land in CANONICAL form (sorted keys) so idempotency and collision checks
|
|
539
|
+
* compare one representation across writers and re-serializing middle layers. Three collision
|
|
540
|
+
* checks (§4.4): read-time (inside {@link readTransferChain}), batch-time (one batch minting two
|
|
541
|
+
* identities for one ev is a minting bug — fail-closed), append-time (an ev already on the chain
|
|
542
|
+
* with a DIFFERENT payload is corruption, named; equal = idempotent skip). Behavior narrowed,
|
|
543
|
+
* disclosed: the pre-v2b reader silently skipped/overwrote same-ev rows regardless of payload.
|
|
544
|
+
* The ONLY failure class here is I/O — never capacity: a size threshold would freeze legitimate
|
|
545
|
+
* moves behind a full log, so growth is disclosed, not refused. A failure AFTER the commit point
|
|
546
|
+
* is COMMITTED-PENDING (the caller keeps the journal and throws — calling it "refused" would
|
|
547
|
+
* invite a retry of a transaction the next recovery completes anyway). */
|
|
198
548
|
private appendTransfers;
|
|
199
549
|
/**
|
|
200
550
|
* @param opts.unlocked RB-447 (2026-07-31, hardening review) — set by the two callers that run
|
|
@@ -328,19 +678,33 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
328
678
|
getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
|
|
329
679
|
private getByIdsFrom;
|
|
330
680
|
/**
|
|
331
|
-
* design/178 §3 (ruled 2026-08-08) — a
|
|
332
|
-
*
|
|
681
|
+
* design/178 §3 (ruled 2026-08-08) — a projection of this backend for the session's RETRIEVAL
|
|
682
|
+
* face (`memory_search` / `memory_get`): NO ADOPTION, ever.
|
|
333
683
|
*
|
|
334
684
|
* The ordinary read path carries the inbound-adoption channel: a disk revision that disagrees with
|
|
335
685
|
* the ledger is judged an out-of-session change, gated, and — when it passes — adopted as the new
|
|
336
686
|
* COMMITTED baseline (ledger + shadow rewritten). That is a deliberate lifecycle synchronization
|
|
337
687
|
* point, and it belongs to the engine's own moments (materialize / harvest), not to every model
|
|
338
688
|
* lookup: a retrieval landing between them would otherwise COMMIT the session's own in-flight edits
|
|
339
|
-
* with no harvest gate and no pollution discipline in the way.
|
|
340
|
-
* entries from disk without touching the ledger, the shadow, or the quarantine.
|
|
689
|
+
* with no harvest gate and no pollution discipline in the way.
|
|
341
690
|
*
|
|
342
|
-
* Not running the inbound gate on this path is the point, not a concession: the view reports
|
|
343
|
-
*
|
|
691
|
+
* Not running the inbound gate on this path is the point, not a concession: the view reports the
|
|
692
|
+
* plane, and admitting anything stays the harvest's job. Stated exactly (v2-b trued this doc up —
|
|
693
|
+
* the pre-v2b sentence "resolves entries from disk without touching the ledger" is no longer the
|
|
694
|
+
* whole truth):
|
|
695
|
+
* · the v2-b RESURRECTION GUARD consults the delete-evidence digest (`transfers.jsonl` stat +
|
|
696
|
+
* parse) and — when any delete row exists — a PURE fresh read of the committed ledger, and it
|
|
697
|
+
* WITHHOLDS an on-plane file whose id carries a delete row with no live account row (an erased
|
|
698
|
+
* id's replanted bytes are not served through any read face; a host re-add serves). So the
|
|
699
|
+
* view is "the plane minus proven-erased ids", and it can throw {@link ControlPlaneCorruptError}
|
|
700
|
+
* from a fail-closed corrupt evidence log — states a pre-v2b view could not reach;
|
|
701
|
+
* · the reads do no healing and no quarantine, and the COMMITTED-ROWS read is cache-free; the
|
|
702
|
+
* digest read installs the instance fingerprint cache (a read cache, not a store write). The
|
|
703
|
+
* path's write-capable hooks (rescan r3 — do not present this list as shorter): `readScope`'s
|
|
704
|
+
* pre-existing opportunistic v1→v2 migration retry (v1-form/unbound-row stores only, design/186
|
|
705
|
+
* §3.1), the guard's fault-disclosure enqueue (a ledger read/parse fault mints an announcement
|
|
706
|
+
* row, once per fault code per mount), and `scopeDir`'s registry mint (listing a scope name the
|
|
707
|
+
* registry has never seen writes its row — predates this view and rides every scope read).
|
|
344
708
|
*
|
|
345
709
|
* WRITES ARE REFUSED, loudly — a view handed to a write path is a defect, not a fallback.
|
|
346
710
|
*/
|
|
@@ -492,12 +856,122 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
|
492
856
|
* recovery is now authoritative; committing anyway would overwrite the stealer's transaction. */
|
|
493
857
|
private assertTxnLockOwnership;
|
|
494
858
|
private applyPatchesLocked;
|
|
859
|
+
/** E-3 ② — the journal CLOSE guard: re-read the fixed-path journal and remove it only while its
|
|
860
|
+
* `.txn` names THIS transaction. A holder that stalled past the stale line and was stolen from
|
|
861
|
+
* may reach its close after the stealer already wrote a NEW journal at the same path — a bare rm
|
|
862
|
+
* there deletes the stealer's LIVE redo record (its crash then loses a committed transaction).
|
|
863
|
+
* An absent/foreign/unparseable journal is simply not ours to remove: leave it (the stealer, or
|
|
864
|
+
* the recovery leg, owns its fate). The remaining read→rm window is the same assert→act residue
|
|
865
|
+
* the commit-point assertion accepts, stated there. */
|
|
866
|
+
private closeJournalIfOurs;
|
|
495
867
|
/** Plan one patch: decide CAS/conflicts against CURRENT state, emit journal ops (no entry-file
|
|
496
868
|
* writes here — staging/execution happen in {@link applyPatches}). Every row write is BOUND
|
|
497
869
|
* (§1.1) at the coordinates the batch ACTUALLY lands — a slug collision's `-n` suffix binds the
|
|
498
870
|
* suffixed slug, the same one `applied[].slug` reports (v1's ledger had no landing awareness). */
|
|
499
871
|
private planOne;
|
|
500
872
|
private locateById;
|
|
873
|
+
/**
|
|
874
|
+
* design/178 v2-b §7 — request-driven erasure with evidence (capability face; probe with
|
|
875
|
+
* `typeof backend.eraseWithEvidence === "function"`). Rides the SAME txn mutex, the SAME journal
|
|
876
|
+
* shape, and the SAME recovery leg as `applyPatches` — never a second transaction protocol.
|
|
877
|
+
* Everything runs INSIDE the lock (r1: resolving a selector outside it pins a set the store can
|
|
878
|
+
* move under before the lock lands): recover → migrate → chain replay-judgment → resolution (a
|
|
879
|
+
* scope selector consults the census for membership — the account alone cannot see an
|
|
880
|
+
* unledgered-live inhabitant of the scope) → census → plan → journal (ops + ledger snapshot +
|
|
881
|
+
* evidence rows in ONE commit) → EXECUTE → ledger → evidence append → guarded journal close →
|
|
882
|
+
* lineage capture-and-clear.
|
|
883
|
+
*
|
|
884
|
+
* Replay protocol (§4.6): the first execution pins the resolved id set on an ANCHOR row; the same
|
|
885
|
+
* requestId re-executes against THAT set (never re-resolves), deletes the un-deleted remainder,
|
|
886
|
+
* and answers the cumulative view. A same-requestId call with a DIFFERENT selector is refused
|
|
887
|
+
* (`memory.erasure_selector_mismatch`); two same-req anchors on the chain are a spliced chain —
|
|
888
|
+
* refused for a human, never adjudicated by file order.
|
|
889
|
+
*
|
|
890
|
+
* This lane deletes by the ACCOUNT (ledger row), not by a physical locate: shadow-only rows
|
|
891
|
+
* (projection deleted out of band), carrier-missing rows, and unbound rows (P=0 / P≥2) are all
|
|
892
|
+
* erasable — the census sweep removes EVERY projection carrying the id (erasure needs no
|
|
893
|
+
* adjudication of which copy was real: removing all of them IS the requested outcome). A ledger
|
|
894
|
+
* key failing the entry-id contract takes the row-only arm (`legacyId` evidence; no ops — a
|
|
895
|
+
* shadow op on it would fail this store's own journal validator). The NotePatch delete lane's
|
|
896
|
+
* semantics are untouched (a shadow-only row still answers unknown-id there — pinned).
|
|
897
|
+
*
|
|
898
|
+
* Failure postures: an unreadable census refuses the WHOLE request pre-commit
|
|
899
|
+
* (`memory.erasure_census_incomplete` — "deleted clean" must not be attested over a store that
|
|
900
|
+
* cannot be fully read); per-id planning faults are non-fatal `conflicts` rows (a replay
|
|
901
|
+
* converges them); everything before the journal write refuses cleanly (F-1); every crash window
|
|
902
|
+
* after it converges through the shared recovery leg (F-2..F-5, F-13).
|
|
903
|
+
*/
|
|
904
|
+
eraseWithEvidence(input: EraseMemoryEntriesInput): Promise<MemoryErasureAttestation>;
|
|
905
|
+
/** Bounded journal-quiescence fence (attempt count × retry pause). Constants, not knobs: the
|
|
906
|
+
* contended window is the commit tail of one transaction (milliseconds); a store where it stays
|
|
907
|
+
* contended for the full bound has a crashed/wedged writer, and the honest answer is refusal,
|
|
908
|
+
* not a longer wait. */
|
|
909
|
+
private static readonly AUDIT_FENCE_ATTEMPTS;
|
|
910
|
+
private static readonly AUDIT_FENCE_RETRY_MS;
|
|
911
|
+
/** Is a transaction journal present right now? ENOENT-only absence (§3.1 ⓪ — a probe FAILURE is
|
|
912
|
+
* not "no journal"; it throws the fail-closed corrupt error, never folds into quiescence). */
|
|
913
|
+
private auditJournalPending;
|
|
914
|
+
/** PURE ledger read for the audit faces: raw bytes → parsed rows, touching NOTHING on the
|
|
915
|
+
* instance (no cache install, no schema-version memo, no v1-compat announcement) and never the
|
|
916
|
+
* disk. A v1-form ledger is refused here — the migration belongs to the store's own entries
|
|
917
|
+
* (construction / adopting reads / locked mutations), never to an audit read. */
|
|
918
|
+
private auditReadLedger;
|
|
919
|
+
/** Run `read` over a STABLE committed account: journal absent before and after, ledger bytes
|
|
920
|
+
* identical across the read (closes both tear channels — the journal-first commit window and any
|
|
921
|
+
* ledger-advancing path, the journal-free no-move adoption save included), retried up to the
|
|
922
|
+
* bound. `read` may throw {@link AuditObservationUnstableError} on a mid-transition scene — the
|
|
923
|
+
* loop re-observes, and the FINAL attempt receives `final: true` to answer the steady-state
|
|
924
|
+
* reading instead. Throws {@link AuditSnapshotContendedError} when the bound exhausts.
|
|
925
|
+
*
|
|
926
|
+
* CONSISTENCY CLAIM, stated precisely (adversarial-review wording pass): the answer is a
|
|
927
|
+
* CONSISTENT committed account as observed at the fenced read — never a torn mixture of two
|
|
928
|
+
* transactions' halves. FRESHNESS-AT-RETURN is deliberately not claimed: a lock-free reader
|
|
929
|
+
* cannot exclude a writer whose entire commit lands in the sub-window between the last two
|
|
930
|
+
* probes, and such an answer is simply the immediately-preceding committed state (the same
|
|
931
|
+
* answer the call would have produced had it returned a moment earlier). Holding the store's
|
|
932
|
+
* txn mutex would buy freshness at the cost of the audit read acquiring writer machinery (a
|
|
933
|
+
* write, and a contention seat) — refused: an audit must not perturb the store it audits. */
|
|
934
|
+
private auditStable;
|
|
935
|
+
/** The audit faces' committed-CONTENT resolution for one row (see {@link CommittedEntrySnapshot}
|
|
936
|
+
* for the state law). Reads only; the shadow read is fail-closed (a control-plane read FAILURE
|
|
937
|
+
* is not absence), the projection probe is the shared side-effect-free tri-state probe. */
|
|
938
|
+
private resolveCommittedContent;
|
|
939
|
+
/**
|
|
940
|
+
* design/178 v2-a §1.3 — the per-id AUDIT SNAPSHOT (capability face; probe with
|
|
941
|
+
* `typeof backend.committedSnapshotOf === "function"`). Pure committed read under the double
|
|
942
|
+
* fence; unknown id ⇒ `{ state: "absent" }` (never a throw, never an empty guess); unbound row ⇒
|
|
943
|
+
* the account half answers with `binding: { state: "unbound" }` (legal transitional state);
|
|
944
|
+
* v1-compat store ⇒ loud refusal (migrate first); fence contention past the bound ⇒ loud refusal.
|
|
945
|
+
*/
|
|
946
|
+
committedSnapshotOf(id: string): Promise<CommittedEntrySnapshot>;
|
|
947
|
+
/** One row's public {@link CommittedBinding} (shared tagged form — see the type's own doc). */
|
|
948
|
+
private publicBinding;
|
|
949
|
+
/**
|
|
950
|
+
* design/178 v2-a §1.3 — the LEDGER-DRIVEN scope enumeration (capability face). The account is
|
|
951
|
+
* the enumeration authority: every bound row whose scope is requested is answered — including
|
|
952
|
+
* shadow-only rows a disk scan would miss (`listHeaders` is an ADOPTING read and enumerates the
|
|
953
|
+
* plane; it is not an audit face). Unbound rows land in the store-level `unbound` appendix.
|
|
954
|
+
* Fence contention past the bound ⇒ `{ complete: false }` (NO data members — nothing to misread
|
|
955
|
+
* as a truncated package): the caller fails closed. Corrupt control-plane bytes and the
|
|
956
|
+
* v1-compat refusal still THROW (they are definite states, not contention).
|
|
957
|
+
*/
|
|
958
|
+
committedSnapshotsOfScopes(scopes: readonly string[]): Promise<CommittedScopeSnapshots>;
|
|
959
|
+
/**
|
|
960
|
+
* design/178 v2-a §1.3 — the per-id CUSTODY read (capability face): every evidence row of
|
|
961
|
+
* `transfers.jsonl` naming this id, in file (= time) order, as OPEN {@link TransferEvidence}.
|
|
962
|
+
* PURE INSPECTION, never repair: a torn tail (crash mid-append) answers the sound prefix with
|
|
963
|
+
* `state: "damaged"` and the file is NOT touched — quarantining the fragment is the recovery
|
|
964
|
+
* leg's duty (the adopting read path), and an audit that rewrites its subject has destroyed the
|
|
965
|
+
* evidence it was asked about. Non-tail corruption stays the fail-closed throw. Unknown channels
|
|
966
|
+
* are TOLERATED and passed through (open form: a chain written by a newer engine still answers);
|
|
967
|
+
* known channels are validated by the store's own rule set. Runs under the same journal fence as
|
|
968
|
+
* the snapshot faces — a pending journal may hold committed-but-unappended evidence, and a chain
|
|
969
|
+
* read across it would be completed differently by the very next recovery.
|
|
970
|
+
*/
|
|
971
|
+
custodyOf(id: string): Promise<EntryCustodyReport>;
|
|
972
|
+
/** The pure open-form evidence read behind {@link custodyOf} (no healing, no writes). `raw` is
|
|
973
|
+
* the caller's already-fenced read of the log (undefined = absent). */
|
|
974
|
+
private custodyReadPure;
|
|
501
975
|
getConsolidationCursor(scope: string): Promise<string | undefined>;
|
|
502
976
|
setConsolidationCursor(scope: string, cursor: string): Promise<void>;
|
|
503
977
|
private readCursors;
|