@sema-agent/core 7.30.0 → 7.31.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +274 -0
  2. package/dist/agents/cross-session-judge.d.ts +4 -12
  3. package/dist/agents/cross-session-judge.js +4 -11
  4. package/dist/agents/peer-session-drain.js +0 -5
  5. package/dist/agents/send-message-tool.js +1 -0
  6. package/dist/agents/subagent.d.ts +1 -0
  7. package/dist/agents/subagent.js +1 -0
  8. package/dist/core/ask-origin.d.ts +6 -0
  9. package/dist/core/ask-origin.js +10 -0
  10. package/dist/core/checkpoint-store.d.ts +14 -0
  11. package/dist/core/checkpoint-store.js +1 -0
  12. package/dist/core/engine-notice.d.ts +4 -0
  13. package/dist/core/governance-codes.d.ts +1 -1
  14. package/dist/core/governance-codes.js +3 -0
  15. package/dist/core/memory-engine/data-plane.d.ts +3 -1
  16. package/dist/core/memory-engine/data-plane.js +1 -1
  17. package/dist/core/memory-engine/delegation-settlement.d.ts +44 -3
  18. package/dist/core/memory-engine/delegation-settlement.js +38 -3
  19. package/dist/core/memory-engine/engine.d.ts +123 -26
  20. package/dist/core/memory-engine/engine.js +426 -149
  21. package/dist/core/memory-engine/entry-scan.d.ts +23 -0
  22. package/dist/core/memory-engine/entry-scan.js +55 -0
  23. package/dist/core/memory-engine/file-backend.d.ts +16 -23
  24. package/dist/core/memory-engine/file-backend.js +122 -99
  25. package/dist/core/memory-engine/layer-observations.d.ts +109 -0
  26. package/dist/core/memory-engine/layer-observations.js +148 -0
  27. package/dist/core/memory-engine/layout.d.ts +11 -0
  28. package/dist/core/memory-engine/layout.js +20 -4
  29. package/dist/core/memory-engine/types.d.ts +26 -2
  30. package/dist/core/parent-spec-seat.d.ts +4 -1
  31. package/dist/core/parent-spec-seat.js +2 -0
  32. package/dist/core/park-selfcheck.js +1 -0
  33. package/dist/core/runner/announce-once-ledger.d.ts +26 -0
  34. package/dist/core/runner/announce-once-ledger.js +37 -0
  35. package/dist/core/runner/contracts.d.ts +22 -5
  36. package/dist/core/runner/denial-limit-arms.d.ts +2 -2
  37. package/dist/core/runner/prepare-acquire-reconcile.d.ts +0 -4
  38. package/dist/core/runner/prepare-acquire-reconcile.js +1 -3
  39. package/dist/core/runner/prepare-file-history.d.ts +13 -15
  40. package/dist/core/runner/prepare-file-history.js +57 -43
  41. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  42. package/dist/core/runner/prepare-listings.d.ts +3 -1
  43. package/dist/core/runner/prepare-listings.js +2 -2
  44. package/dist/core/runner/prepare-memory.js +16 -5
  45. package/dist/core/runner/prepare-park-ask.js +27 -16
  46. package/dist/core/runner/prepare-prompt-inputs.d.ts +8 -1
  47. package/dist/core/runner/prepare-prompt-inputs.js +5 -3
  48. package/dist/core/runner/prepare-task.js +7 -5
  49. package/dist/core/runner/prepare-wiring-manifest.d.ts +1 -0
  50. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  51. package/dist/core/runner/run-attachment-seats.js +1 -1
  52. package/dist/core/runner/run-leg.js +61 -52
  53. package/dist/core/runner/run-settle-and-teardown.js +4 -0
  54. package/dist/core/runner/run-turn-boundary.js +6 -6
  55. package/dist/core/runner/turn-attachments.d.ts +6 -6
  56. package/dist/core/runner-deps.d.ts +3 -1
  57. package/dist/core/secret-env.d.ts +11 -0
  58. package/dist/core/secret-env.js +22 -1
  59. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +22 -0
  61. package/dist/core/task-result.d.ts +1 -1
  62. package/dist/core/task-spec.d.ts +17 -8
  63. package/dist/core/tool-spec.d.ts +1 -1
  64. package/dist/core/wiring-manifest.d.ts +10 -0
  65. package/dist/core/wiring-manifest.js +7 -1
  66. package/dist/engine/execution-env/node-execution-env.d.ts +6 -0
  67. package/dist/engine/execution-env/node-execution-env.js +15 -17
  68. package/dist/engine/harness/agent-harness.js +7 -5
  69. package/dist/engine/lsp/node-lsp-manager.d.ts +7 -0
  70. package/dist/engine/lsp/node-lsp-manager.js +11 -3
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.js +1 -1
  73. package/dist/shell/lex.d.ts +8 -3
  74. package/dist/shell/lex.js +2 -2
  75. package/dist/tools/fs/bash-readonly-classifier.d.ts +7 -1
  76. package/dist/tools/fs/bash-readonly-classifier.js +7 -3
  77. package/dist/tools/fs/shell-read-establish.d.ts +1 -1
  78. package/dist/tools/fs/shell-read-establish.js +6 -34
  79. package/dist/tools/fs/shell-read-identity.d.ts +13 -0
  80. package/dist/tools/fs/shell-read-identity.js +39 -0
  81. package/dist/tools/fs/shell-read-judge.d.ts +8 -0
  82. package/dist/tools/fs/shell-read-judge.js +7 -0
  83. package/dist/tools/fs/shell-read-plan.d.ts +5 -1
  84. package/dist/tools/fs/shell-read-plan.js +13 -21
  85. package/dist/tools/fs/shell-read-word.d.ts +16 -2
  86. package/dist/tools/fs/shell-read-word.js +40 -5
  87. package/package.json +1 -1
  88. package/test/export-surface.snapshot.json +7 -1
  89. package/wire-changes/core@7.30.1.json +154 -0
  90. package/wire-changes/core@7.31.0.json +705 -0
@@ -37,6 +37,7 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
37
37
  "memory.restore_withheld",
38
38
  "memory.project_scope_absent",
39
39
  "memory.projection_failed",
40
+ "memory.observation_settlement_unavailable",
40
41
  "memory.consolidation_disabled",
41
42
  "memory.consolidation_gated",
42
43
  "memory.consolidation_busy",
@@ -125,6 +126,7 @@ export const ENGINE_NOTICE_CODES = [
125
126
  "config.durable_gate_unavailable",
126
127
  "config.peer_admission_out_of_range",
127
128
  "config.peer_lane_unmounted",
129
+ "config.secret_env_scrubbed",
128
130
  "peer.inbound_disposition",
129
131
  "peer.held_settled",
130
132
  "peer.idle_subscription",
@@ -217,6 +219,7 @@ const NOTICE_AUDIENCE_TABLE = {
217
219
  "config.tool_face_invalid": "operator",
218
220
  "config.peer_lane_unmounted": "operator",
219
221
  "config.peer_admission_out_of_range": "operator",
222
+ "config.secret_env_scrubbed": "operator",
220
223
  "peer.inbound_disposition": "user",
221
224
  "peer.held_settled": "user",
222
225
  "peer.idle_subscription": "user",
@@ -32,7 +32,9 @@ export interface RemoteMemoryBaseline {
32
32
  readonlyDirs?: string[];
33
33
  }
34
34
  export interface RemoteMaterialization {
35
- /** Ship these into the sandbox memory root (the deployment owns the transport + any chmod). */
35
+ /** Ship these into the sandbox memory root (the deployment owns the transport). `readonly` is the
36
+ * session's policy, enforced by {@link harvestFilesToPatches}' `readonly_layer` rejection; whether
37
+ * to also set permission bits on its own volume is the deployment's call — the engine sets none. */
36
38
  files: Array<RemoteMemoryFile & {
37
39
  readonly: boolean;
38
40
  }>;
@@ -2,7 +2,7 @@ import { uuidv7 } from "../../internal/harness.js";
2
2
  import { firstSentence } from "../memory.js";
3
3
  import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
4
4
  import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
5
- import { MEMORY_INDEX_FILENAME } from "./file-backend.js";
5
+ import { MEMORY_INDEX_FILENAME } from "./entry-scan.js";
6
6
  import { scopeDirName } from "./layout.js";
7
7
  export const REMOTE_HARVEST_PER_FILE_BYTES = 100 * 1024;
8
8
  export const REMOTE_MASS_DELETION_FUSE_RATIO = 0.5;
@@ -153,6 +153,19 @@ export interface UnattributedMember {
153
153
  export interface SessionAccountRow {
154
154
  sessionId: string;
155
155
  openedAt: number;
156
+ /** The scope this session writes (its write home) — the layer it is a live writer of until
157
+ * `terminatedAt` ({@link openLayerWriters}). Absent on rows written before the field. */
158
+ writeScope?: string;
159
+ /** The session's leg ENDED here: its terminal harvest returned (whatever it admitted — a refused,
160
+ * zero-admission or budget-deferred pass ends the leg all the same), or the host valve closed the
161
+ * row. A lifecycle fact, independent of `closedAt` (every full harvest closes the account — a
162
+ * checkpoint harvest too — while the session may keep writing); cleared when a resume re-opens
163
+ * the row. */
164
+ terminatedAt?: number;
165
+ /** The LEG that holds the row now: minted by the materialize that opened (or re-opened) it and
166
+ * carried on that mount's handle. A close or a leg end made for another leg — an earlier leg
167
+ * whose harvest finished after a resume re-opened the row — is not this leg's and is not written. */
168
+ leg?: string;
156
169
  /** The STICKY unattributed set, frozen at materialize (§3.6 归属判据): pre-existing, uncommitted
157
170
  * files seen while a FOREIGN dangling open row stood. Membership never shrinks on later writes
158
171
  * (the sticky law); it clears only when the row is re-opened with a fresh classification (the
@@ -175,6 +188,20 @@ export declare function convertV1SidecarKeys(controlDir: string): void;
175
188
  /** The dangling-trigger read: open rows (and unadjudicated closes) belonging to OTHER sessions.
176
189
  * These are what arm the unattributed residue classification at a session's materialize. */
177
190
  export declare function foreignDanglingSessionAccounts(controlDir: string, selfSessionId: string): SessionAccountRow[];
191
+ /** The live writers of the store's layers, as the account knows them: every row other than the one
192
+ * held by `selfLeg` (the asking mount's own leg — another leg of the same session is another
193
+ * writer) whose session's leg has not ended (no `terminatedAt` — running, or ended without
194
+ * a terminal close, e.g. a crash), by its write scope. A row written before the write scope was
195
+ * recorded (the engine then closed the account at every full harvest, so its close cannot say
196
+ * whether the leg ended) reads as before: live while never closed, counted in `unscoped` — a
197
+ * writer of an unknown layer. Sessions that open no row (no session id, provenance "off", an
198
+ * adoption-restricted mount) are not visible here. */
199
+ export declare function openLayerWriters(controlDir: string, selfLeg: string | undefined): {
200
+ scopes: Set<string>;
201
+ unscoped: number;
202
+ };
203
+ /** The leg that holds `sessionId`'s account row (undefined ⇔ no row, or a row without one). */
204
+ export declare function sessionAccountLeg(controlDir: string, sessionId: string): string | undefined;
178
205
  /** The membership question the harvest asks per file: is `(scope, rel)` in the session's sticky
179
206
  * set? A session carrying members the conversion could not attribute answers yes for every seat. */
180
207
  export interface SessionUnattributedSet {
@@ -189,6 +216,8 @@ export declare function openSessionAccount(controlDir: string, input: {
189
216
  sessionId: string;
190
217
  now: () => number;
191
218
  unattributed: readonly UnattributedMember[];
219
+ writeScope?: string;
220
+ leg?: string;
192
221
  }): void;
193
222
  /** The current session's sticky unattributed set (the harvest's residue-arm input). */
194
223
  export declare function sessionUnattributedSet(controlDir: string, sessionId: string): SessionUnattributedSet;
@@ -203,13 +232,25 @@ export declare function sessionUnattributedSet(controlDir: string, sessionId: st
203
232
  * condition and does not hold the close, but the caller DISCLOSES any standing deferral beside it
204
233
  * (a close over id-less deferred files must not read as a clean full pass).
205
234
  *
206
- * Returns whether a row was actually closed: absent ⇒ `false` (a session that never opened an
235
+ * Returns what happened ({@link SessionAccountWrite}): `"absent"` (a session that never opened an
207
236
  * account — e.g. an adoption-restricted materialize — has no close, and the caller's disclosure
208
- * must not claim one). */
237
+ * must not claim one), or `"stale"` when `leg` names a mount that no longer holds the row. */
209
238
  export declare function closeSessionAccount(controlDir: string, input: {
210
239
  sessionId: string;
211
240
  now: () => number;
212
- }): boolean;
241
+ leg?: string;
242
+ }): SessionAccountWrite;
243
+ /** What a close / leg end did: wrote the row, found none, or found the row held by ANOTHER leg (the
244
+ * caller's leg is over — a resume re-opened the row — so nothing was written). */
245
+ export type SessionAccountWrite = "written" | "absent" | "stale";
246
+ /** Record that the session's leg ended ({@link SessionAccountRow.terminatedAt}). Touches nothing
247
+ * else — `closedAt` stays whatever the harvests made it. `leg` (the caller's mount) must hold the
248
+ * row: a leg that is over does not end the one that re-opened it. */
249
+ export declare function terminateSessionAccount(controlDir: string, input: {
250
+ sessionId: string;
251
+ now: () => number;
252
+ leg?: string;
253
+ }): SessionAccountWrite;
213
254
  /** The host valve for a dangling row (advisory; §3.6 — dangling rows never auto-expire). The
214
255
  * close is `closed-unadjudicated` (r7-3): it stops the row dangling but the residue arm keeps
215
256
  * firing until a full-domain harvest closes normally. `requestId` required (audit, #123). */
@@ -242,6 +242,8 @@ function malformedAccountRow(row) {
242
242
  !Number.isFinite(row.openedAt) ||
243
243
  !Array.isArray(row.unattributed) ||
244
244
  (row.closedAt !== undefined && (typeof row.closedAt !== "number" || !Number.isFinite(row.closedAt))) ||
245
+ (row.terminatedAt !== undefined && (typeof row.terminatedAt !== "number" || !Number.isFinite(row.terminatedAt))) ||
246
+ (row.leg !== undefined && !reqStr(row.leg)) ||
245
247
  (row.unadjudicated !== undefined && row.unadjudicated !== true));
246
248
  }
247
249
  function isUnattributedMember(x) {
@@ -273,6 +275,8 @@ function coerceSessionAccounts(raw) {
273
275
  bad();
274
276
  if (row.unattributable !== undefined && (!Array.isArray(row.unattributable) || row.unattributable.some((x) => typeof x !== "string")))
275
277
  bad();
278
+ if (row.writeScope !== undefined && !reqStr(row.writeScope))
279
+ bad();
276
280
  }
277
281
  }
278
282
  return rec.version === 1 ? { version: 1, rows: rows } : { version: 2, rows: rows };
@@ -320,6 +324,13 @@ export function foreignDanglingSessionAccounts(controlDir, selfSessionId) {
320
324
  const rec = readSessionAccountsConverted(controlDir);
321
325
  return rec.rows.filter((r) => r.sessionId !== selfSessionId && (r.closedAt === undefined || r.unadjudicated === true));
322
326
  }
327
+ export function openLayerWriters(controlDir, selfLeg) {
328
+ const open = readSessionAccountsConverted(controlDir).rows.filter((r) => (selfLeg === undefined || r.leg !== selfLeg) && (r.writeScope !== undefined ? r.terminatedAt === undefined : r.closedAt === undefined));
329
+ return { scopes: new Set(open.flatMap((r) => (r.writeScope !== undefined ? [r.writeScope] : []))), unscoped: open.filter((r) => r.writeScope === undefined).length };
330
+ }
331
+ export function sessionAccountLeg(controlDir, sessionId) {
332
+ return readSessionAccountsConverted(controlDir).rows.find((r) => r.sessionId === sessionId)?.leg;
333
+ }
323
334
  export function emptyUnattributedSet() {
324
335
  return { has: () => false, empty: true };
325
336
  }
@@ -330,8 +341,17 @@ export function openSessionAccount(controlDir, input) {
330
341
  lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (read) => {
331
342
  const { rec } = convertSessionAccounts(read, controlDir);
332
343
  const row = rec.rows.find((r) => r.sessionId === input.sessionId);
344
+ if (row !== undefined && input.writeScope !== undefined)
345
+ row.writeScope = input.writeScope;
346
+ if (row !== undefined) {
347
+ delete row.terminatedAt;
348
+ if (input.leg !== undefined)
349
+ row.leg = input.leg;
350
+ else
351
+ delete row.leg;
352
+ }
333
353
  if (row === undefined) {
334
- rec.rows.push({ sessionId: input.sessionId, openedAt: input.now(), unattributed: [...input.unattributed] });
354
+ rec.rows.push({ sessionId: input.sessionId, openedAt: input.now(), ...(input.writeScope !== undefined ? { writeScope: input.writeScope } : {}), ...(input.leg !== undefined ? { leg: input.leg } : {}), unattributed: [...input.unattributed] });
335
355
  }
336
356
  else if (row.closedAt !== undefined && row.unadjudicated === undefined) {
337
357
  row.openedAt = input.now();
@@ -363,12 +383,26 @@ export function closeSessionAccount(controlDir, input) {
363
383
  const { rec } = convertSessionAccounts(read, controlDir);
364
384
  const row = rec.rows.find((r) => r.sessionId === input.sessionId);
365
385
  if (row === undefined)
366
- return { result: false };
386
+ return { result: "absent" };
387
+ if (input.leg !== undefined && row.leg !== input.leg)
388
+ return { result: "stale" };
367
389
  row.closedAt = input.now();
368
390
  delete row.unadjudicated;
369
391
  for (const r of rec.rows)
370
392
  delete r.unadjudicated;
371
- return { next: rec, result: true };
393
+ return { next: rec, result: "written" };
394
+ });
395
+ }
396
+ export function terminateSessionAccount(controlDir, input) {
397
+ return lockedStrictUpdate(controlDir, SESSION_ACCOUNTS_FILE, "memory session-account ledger", coerceSessionAccounts, (read) => {
398
+ const { rec } = convertSessionAccounts(read, controlDir);
399
+ const row = rec.rows.find((r) => r.sessionId === input.sessionId);
400
+ if (row === undefined)
401
+ return { result: "absent" };
402
+ if (input.leg !== undefined && row.leg !== input.leg)
403
+ return { result: "stale" };
404
+ row.terminatedAt = input.now();
405
+ return { next: rec, result: "written" };
372
406
  });
373
407
  }
374
408
  export function resolveSessionAccountRecord(controlDir, input) {
@@ -387,6 +421,7 @@ export function resolveSessionAccountRecord(controlDir, input) {
387
421
  }
388
422
  row.closedAt = input.now();
389
423
  row.unadjudicated = true;
424
+ row.terminatedAt = row.closedAt;
390
425
  return { next: rec, result: undefined };
391
426
  });
392
427
  }
@@ -1,6 +1,6 @@
1
1
  import type { EngineNotice } from "../types.js";
2
2
  import { type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationPlanFile, type ConsolidationProposal, type ConsolidationRunStopReason, type MemoryConsolidationOptions } from "./consolidation.js";
3
- import { foreignDanglingSessionAccounts, type EffectiveSettlement, type HoldRow } from "./delegation-settlement.js";
3
+ import { foreignDanglingSessionAccounts, type SessionAccountWrite, type EffectiveSettlement, type HoldRow } from "./delegation-settlement.js";
4
4
  import { type OriginClearanceRow } from "./origin-clearance.js";
5
5
  import { type CommittedBinding, type EraseMemoryEntriesInput, type MemoryErasureAttestation, type TransferEvidence } from "./file-backend.js";
6
6
  import { type MemoryExportBundle, type MemoryImportReport } from "./export-bundle.js";
@@ -155,7 +155,8 @@ export declare const MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA = "Any guidance in
155
155
  * about the state itself, and a model asked "remember X" under that silence answers with a success
156
156
  * receipt for a save that never happens (the confabulated-receipt shape). This section states the
157
157
  * state instead. Mounted only where "you cannot save" is provably TRUE: by `inject()` for a
158
- * read-only layering (writeScope null ⇒ chmod'd tree), and by the runner for handsReadOnly (the
158
+ * read-only layering (writeScope null ⇒ the harvest admits nothing and the file-tool gate refuses the
159
+ * whole memory domain), and by the runner for handsReadOnly (the
159
160
  * shell rides the read-only band), for a declared-unavailable session (`memoryPersistenceCapable:
160
161
  * false` — where the runner's write gate also refuses the file channel, keeping the engine-refusal
161
162
  * sentence true), and for a write-less roster the persistence inference cannot vouch for. A merely
@@ -310,7 +311,7 @@ export interface MemoryEngineOptions {
310
311
  /** M7 — max files PROCESSED per harvest (default {@link DEFAULT_HARVEST_FILE_BUDGET}). */
311
312
  harvestFileBudget?: number;
312
313
  /**
313
- * Where this engine's advisory incidents go. Two codes ride it:
314
+ * Where this engine's advisory incidents go. Codes that ride it include:
314
315
  * - `memory.partition_split` / `memory.partition_adopt_failed` — the mount-time partition-key probe,
315
316
  * when this engine derives its OWN control dir (no `controlDir`, backend pins none). Same seat and
316
317
  * same reason as the file backend's option: the probe promises "never silent", and on this
@@ -319,6 +320,9 @@ export interface MemoryEngineOptions {
319
320
  * The queue is the DISCLOSURE lane for out-of-session memory events, and its own failures used to
320
321
  * be swallowed at four call sites: a host whose control plane went unwritable kept seeing healthy
321
322
  * sessions while every gate/external notice was dropped on the floor.
323
+ * - `memory.observation_settlement_unavailable` — once per engine, at a zero-copy materialize over a
324
+ * backend without the committed-snapshot face: changes observed in read-only layers are recorded
325
+ * undecided and cannot settle on it.
322
326
  * Absent ⇒ advisory-silent; neither a mount nor a harvest ever fails over this seat.
323
327
  */
324
328
  onIncident?: MemoryPartitionIncidentSink;
@@ -343,8 +347,8 @@ export interface MemoryInjection {
343
347
  /** CC `# Memory` section (system-authority instruction — §0.3 逐字 surface). Empty for a read-only
344
348
  * layering (no write channel to instruct). */
345
349
  instruction: string;
346
- /** Present exactly when this is a read-only LAYERING (writeScope null — the tree is chmod'd
347
- * read-only, so no tool writes into it whatever the roster): the {@link MEMORY_READONLY_NOTICE}
350
+ /** Present exactly when this is a read-only LAYERING (writeScope null — the harvest admits
351
+ * nothing from this session, whatever the roster writes): the {@link MEMORY_READONLY_NOTICE}
348
352
  * section stating that memory cannot be saved to, so the model declines "remember X" instead of
349
353
  * issuing a confabulated success receipt. NOT set for `writeToolMounted:false` over a writable
350
354
  * scope — there only the instruction-named tool is absent, other roster tools can still write the
@@ -843,6 +847,7 @@ export declare class MemoryEngine {
843
847
  private readonly provenance;
844
848
  /** {@link MemoryEngineOptions.onIncident} — the advisory disclosure seat (partition + announce codes). */
845
849
  private readonly onIncident;
850
+ private unsettleableDisclosed;
846
851
  /** A backend-pinned directory (FileBackend zero-copy detection). */
847
852
  private readonly backendPinnedRoot?;
848
853
  constructor(opts: MemoryEngineOptions);
@@ -858,6 +863,94 @@ export declare class MemoryEngine {
858
863
  get provenanceMode(): "off" | "carry";
859
864
  /** {@link MemoryEngineOptions.consolidation}, screened — undefined ⇔ the protocol is OFF. */
860
865
  private readonly consolidation?;
866
+ /**
867
+ * Record that this mount's leg of `sessionId` ended — its terminal harvest returned or threw, or
868
+ * the run that mounted it will never harvest (a prepare that failed after the mount). Writer
869
+ * liveness (`SessionAccountRow.terminatedAt`) is the session's lifecycle, so this is the one mint
870
+ * of it besides the host valve. Only the leg the handle opened is ended (`"stale"`: a resume
871
+ * re-opened the row, and the later leg is still live). A record that cannot be written leaves the
872
+ * row reading live (restores of its layer withheld) and is disclosed, never silent.
873
+ */
874
+ endSessionLeg(handle: MemorySessionHandle, sessionId: string): SessionAccountWrite | undefined;
875
+ private endLeg;
876
+ /** design/336 §3.6 (slice 2) — the session-account incident channel (`memory.session_account_failed`):
877
+ * a degraded residue classification is DISCLOSED, never quiet (the behavioral fail-closed half —
878
+ * harvest refusing on `handle.sessionAccountFailed` — is enforced at the write boundary). */
879
+ private discloseSessionAccountIncident;
880
+ /** Read-only layer integrity, DETECTION half — run on every harvest path, before any exit. A
881
+ * read-only layer is this session's POLICY (the file-tool gate refuses writes into it; nothing on
882
+ * disk enforces it), so a layer file that differs from what this session was served is a
883
+ * `readonly_layer` rejection whatever the arm — unless its bytes EQUAL the committed copy: then a
884
+ * session that may write this layer committed them while this one ran (the same transactional
885
+ * proof the restricted committed view reads), and there is nothing to reject or put back.
886
+ * Returns what the RESTORE half may put back ({@link restoreReadOnlyLayers}): only a session that
887
+ * may commit ever restores, and only once the harvest's fail-closed preflights (journal recovery
888
+ * included) passed — a session that admits nothing (read-only layering, capture opt-out,
889
+ * persistence declared unavailable, an indeterminate capture state) only detects and reports: it
890
+ * never adjudicates a shared layer on disk, and the boundary audit of that arm names the same
891
+ * divergence on the inbound channel. */
892
+ private detectReadOnlyLayerChanges;
893
+ /** The committed copy of `id` read WITHOUT an adopting sync: a File backend's ordinary `getByIds`
894
+ * adopts pending disk edits across every registered scope, which a session that admits nothing
895
+ * must never trigger — its committed view (`restrictedAdoptionView`, audit off) is read instead.
896
+ * Zero-copy reads the committed shadow already, and a backend without that view has no read-side
897
+ * adoption channel (its plain reads serve committed rows). */
898
+ private committedContentUnadopted;
899
+ /** Read-only layer integrity, RESTORE half (zero-copy hazard: the changed file IS the backend's
900
+ * storage, and left in place a later read-side sync would adopt it as the layer's baseline). It
901
+ * acts on the committed account AS IT STANDS NOW — after the preflights recovered any interrupted
902
+ * transaction — never on what detection read before them: the file and its committed copy are
903
+ * read again, and bytes that now equal the committed copy were a commit (the early rejection was
904
+ * judged against a stale account and is withdrawn; nothing is written). A restore that could not
905
+ * land is a warning, never a silent skip. */
906
+ private restoreReadOnlyLayers;
907
+ /** Read-only layer integrity, OBSERVATION half — run on every harvest path with the detection
908
+ * half. Every file of a layer this session holds read-only whose bytes changed in the window
909
+ * (against the materialize-time census) is a candidate; one that no commit backs at its seat is
910
+ * recorded in the session-observation table, which every door into the committed account
911
+ * consults: the bytes are never adopted, whoever reads next. A file this session was not served
912
+ * (a new file, with or without an entry id) is rejected here; a served entry's change is the
913
+ * detection half's rejection. The commit question is answered only by a committed account that
914
+ * can be read whole — a pending transaction journal (a writer mid-commit, or one that died there)
915
+ * makes it undecidable, never "not committed": those candidates are recorded UNDECIDED (never
916
+ * adopted meanwhile) and returned; a writing session settles every undecided row once its
917
+ * preflight has recovered the journal (the restore half's rule). Zero-copy only (the census is taken there): that is where the
918
+ * observed plane IS the store the read side adopts from. A record that cannot be written is a
919
+ * warning — the next session may then adopt the bytes, which must not happen silently. */
920
+ private observeReadOnlyLayers;
921
+ /** Write an observation batch AS JUDGED (a retry writes the same rows — same baseline, same
922
+ * observation time — never a re-judgment against a later account). Only a landed batch claims
923
+ * anything: its rejections now, its undecided rows' disclosure at the conclusion. A failure is
924
+ * kept, and said only once no retry follows ({@link concludeObservations}). */
925
+ private landObservations;
926
+ /** Where the harvest leaves the observation half for good: a batch that never landed is said (once,
927
+ * with its last failure); a landed batch's undecided rows are named. */
928
+ private concludeObservations;
929
+ /** Candidates the committed account could not answer for are named, never silently dropped. */
930
+ private discloseUndecidedObservations;
931
+ /** The settlement of undecided observation rows — run by a session that may write, right after its
932
+ * preflight recovered any pending journal, each row by its own key: removed when the committed
933
+ * account backs its bytes at its seat (they are committed content), otherwise pinned to the
934
+ * committed rev read here — an ordinary row, retired like any other when that rev moves; a row the
935
+ * account still cannot answer for stays undecided.
936
+ * This session's own undecided candidates that settle
937
+ * as unexpected writes and were not served become its rejections; ones still undecidable are
938
+ * named. */
939
+ private settleUndecidedObservations;
940
+ /** Whether the read doors refuse this content of `entryId` now: the observation table as it stands,
941
+ * against the committed rev read now. Undefined when either cannot be read. */
942
+ private refusedNow;
943
+ /** Once per engine, at a zero-copy materialize: a backend without `committedSnapshotOf` (a wrapper
944
+ * that does not pass it through) records every read-only-layer change undecided, and none settles. */
945
+ private discloseUnsettleableObservations;
946
+ /** The committed row of `id`, read side-effect-free from the store's audit face: rev plus binding,
947
+ * undefined when the account has no row, `"undecidable"` when the account cannot be read whole
948
+ * (a pending journal, a fault, no audit face) — never a guess from a copy that carries no seat. */
949
+ private committedRowOf;
950
+ /** A caught memory-write failure as a report / incident line: its text, plus the remedy when the
951
+ * file system refused it on permissions ({@link withPermissionRemedy} — the one spelling every
952
+ * write seat surfaces, so no seat renders a permission refusal as a bare errno). */
953
+ private writeFailureText;
861
954
  /**
862
955
  * The ONE delivery point for a failed announcement-queue operation (`memory.announce_failed`, ruled
863
956
  * 2026-08-03). Every enqueue/drain call site stays fail-OPEN — a broken queue must never fail a
@@ -869,10 +962,6 @@ export declare class MemoryEngine {
869
962
  * failure produces no second-order disclosure — the alternative (announcing the announce failure)
870
963
  * would recurse through the very lane that just failed.
871
964
  */
872
- /** design/336 §3.6 (slice 2) — the session-account incident channel (`memory.session_account_failed`):
873
- * a degraded residue classification is DISCLOSED, never quiet (the behavioral fail-closed half —
874
- * harvest refusing on `handle.sessionAccountFailed` — is enforced at the write boundary). */
875
- private discloseSessionAccountIncident;
876
965
  private discloseAnnounceFailure;
877
966
  /**
878
967
  * Record retrieval hits (entries a memory tool actually RETURNED to the model) into this engine's
@@ -1089,6 +1178,7 @@ export declare class MemoryEngine {
1089
1178
  * `residuals.indexUncleared`, never silently absorbed into a clean residual set.
1090
1179
  */
1091
1180
  eraseMemoryEntries(input: EraseMemoryEntriesInput): Promise<MemoryErasureAttestation>;
1181
+ private eraseMemoryEntriesCore;
1092
1182
  /**
1093
1183
  * design/178 v2-b (rescan hardening) — the erase-time DERIVED-INDEX sweep, both lanes. MEMORY.md
1094
1184
  * is the engine's model-visible distillation of entry frontmatter (name + description — routinely
@@ -1199,6 +1289,7 @@ export declare class MemoryEngine {
1199
1289
  sessionId?: string;
1200
1290
  shadowNames?: ReadonlyMap<string, string>;
1201
1291
  }): Promise<MemorySessionHandle>;
1292
+ private materializeCore;
1202
1293
  inject(handle: MemorySessionHandle, opts?: {
1203
1294
  writeToolMounted?: boolean;
1204
1295
  reminderMark?: string;
@@ -1228,6 +1319,10 @@ export declare class MemoryEngine {
1228
1319
  * S2-B (O-F7/C-F3): the announcement ENQUEUE rides the harvest tail INSIDE the engine — every
1229
1320
  * caller (checkpoint mint / task terminal) is covered without run-loop wiring, and a refused
1230
1321
  * harvest (incident) announces too. The enqueue is fail-open: a broken queue never fails a harvest.
1322
+ * `opts.phase` is the CALLER's word for where the session stands: `"terminal"` ⇔ its leg ends with
1323
+ * this harvest, which records the leg's end on the session account once the harvest returns or
1324
+ * throws — whatever it admitted (`SessionAccountRow.terminatedAt`); anything else, absent
1325
+ * included, leaves the writer live.
1231
1326
  */
1232
1327
  harvest(handle: MemorySessionHandle, opts?: {
1233
1328
  polluted?: {
@@ -1239,6 +1334,7 @@ export declare class MemoryEngine {
1239
1334
  reason: string;
1240
1335
  cause?: "capture_optout_inherited";
1241
1336
  };
1337
+ phase?: "checkpoint" | "terminal";
1242
1338
  }): Promise<HarvestReport>;
1243
1339
  private harvestCore;
1244
1340
  /**
@@ -1272,7 +1368,8 @@ export declare class MemoryEngine {
1272
1368
  resolveHold(holdId: string, action: "release" | "discard", requestId: string): void;
1273
1369
  /** §3.6 — the session-account valve: close a dangling foreign row WITHOUT adjudication (the
1274
1370
  * residue arm keeps firing until the next full-domain harvest closes normally — a valve close
1275
- * must not launder the window it covers). Audited (requestId required). */
1371
+ * must not launder the window it covers). It also ends the session's leg (no live writer of its
1372
+ * layer any more). Audited (requestId required). */
1276
1373
  resolveSessionAccount(sessionId: string, requestId: string): void;
1277
1374
  /** The engine's control-plane directory (the sidecar home: pollution markers, lineage/challenge
1278
1375
  * ledgers, and the design/336 settlement/hold/session accounts). Exposed so a host (and the
@@ -1687,11 +1784,12 @@ export declare class MemoryEngine {
1687
1784
  /** Persist the canonical projection back to the session file (id minting / frontmatter completion).
1688
1785
  * C-F6 (S2-0): called ONLY after the backend transaction committed — never ahead of the journal
1689
1786
  * commit point. Skips the write when the disk already holds the canonical bytes (zero-copy: the
1690
- * journal's own execute step wrote them, making this an idempotent no-op). Returns whether the
1691
- * disk now holds the canonical bytes — the ordinary completion family stays best-effort on a
1692
- * `false` (the next materialize re-projects), but the id-COMPLETION family's caller reads it to
1693
- * keep the #366 projection-debt row standing (an id-less plane file must re-bind to its
1694
- * committed id at the next harvest, never re-admit as a duplicate). */
1787
+ * journal's own execute step wrote them, making this an idempotent no-op). Returns `undefined`
1788
+ * when the disk now holds the canonical bytes, else the failure's text: the caller reports it
1789
+ * (the ordinary completion family stays recoverable — the backend commit is authoritative and the
1790
+ * next materialize re-projects), and the id-COMPLETION family additionally keeps the #366
1791
+ * projection-debt row standing (an id-less plane file must re-bind to its committed id at the
1792
+ * next harvest, never re-admit as a duplicate). */
1695
1793
  private writeBackProjection;
1696
1794
  /**
1697
1795
  * #366 — repair an id-less-but-committed seat IN PLACE (the failed write-back's retry): write the
@@ -1699,7 +1797,8 @@ export declare class MemoryEngine {
1699
1797
  * already proven the seat carries no unadmitted edit (rev equality against its baseline), so the
1700
1798
  * overwrite is content-preserving — it restores the id line, the completed frontmatter and any
1701
1799
  * carried marker, nothing else. A refused read/write leaves the row standing (retried next pass;
1702
- * the next materialize's projection loop repairs and settles it too).
1800
+ * the next materialize's projection loop repairs and settles it too); a refused WRITE is said on
1801
+ * `warnings`, so a seat that keeps failing is not silently retried forever.
1703
1802
  */
1704
1803
  private repairProjectionSeat;
1705
1804
  /**
@@ -1713,16 +1812,14 @@ export declare class MemoryEngine {
1713
1812
  * `getByIds` there would sync-adopt mid-harvest.
1714
1813
  */
1715
1814
  private debtCommittedProjection;
1716
- /** chmod a scope's entry tree (best-effort). The tree is the scope's own home and nothing else —
1717
- * no other scope's directory can lie under it.
1718
- *
1719
- * `opts.announced` is the CALLER's per-materialize ledger of paths already disclosed (#495 M-1).
1720
- * One materialize sweeps each inherited tree TWICE — restore write perms at the top, re-apply
1721
- * read-only at the bottom — and both passes see the same anomalies, so without it every skipped
1722
- * path was enqueued twice, byte-identically. The chmod WORK still happens twice (it has to: the
1723
- * modes really do change twice); only the disclosure is deduplicated. Absent ⇒ the historic
1724
- * per-call behaviour (each call states everything it skipped). */
1725
- private chmodScopeTree;
1815
+ /** The layer census: one walk of a scope's home per materialize, looking and never changing
1816
+ * anything. The tree is the scope's own home and nothing else — no other scope's directory can
1817
+ * lie under it. What it owes is DISCLOSURE: a link or an unreadable path inside a memory layer is
1818
+ * an anomaly the host should hear about, and a read-only layer has no other channel for it (the
1819
+ * harvest scan, which reports links for the writable scope, does not walk read-only layers at
1820
+ * all). Links are never followed (lstat): a linked directory would carry the walk out of the
1821
+ * tree. */
1822
+ private censusScopeTree;
1726
1823
  }
1727
1824
  /** Render drained announcements as ONE engine-owned system-reminder block (the lead/frame is
1728
1825
  * trusted; item identifiers were neutralized at enqueue — see {@link MemoryAnnouncement.items}). */