@sema-agent/core 5.46.0 → 5.47.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 (35) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/agents/subagent.js +129 -2
  3. package/dist/core/governance-codes.d.ts +13 -0
  4. package/dist/core/governance-codes.js +33 -0
  5. package/dist/core/memory-engine/delegation-settlement.d.ts +318 -0
  6. package/dist/core/memory-engine/delegation-settlement.js +661 -0
  7. package/dist/core/memory-engine/engine.d.ts +159 -1
  8. package/dist/core/memory-engine/engine.js +699 -15
  9. package/dist/core/memory-engine/file-backend.d.ts +1 -0
  10. package/dist/core/memory-engine/file-backend.js +3 -1
  11. package/dist/core/memory-engine/frontmatter.d.ts +46 -19
  12. package/dist/core/memory-engine/frontmatter.js +91 -77
  13. package/dist/core/memory-engine/index.d.ts +4 -3
  14. package/dist/core/memory-engine/index.js +3 -2
  15. package/dist/core/memory-engine/layout.d.ts +14 -0
  16. package/dist/core/memory-engine/layout.js +2 -2
  17. package/dist/core/memory-engine/memory-backend-contract.js +43 -0
  18. package/dist/core/memory-engine/origin-clearance.d.ts +66 -0
  19. package/dist/core/memory-engine/origin-clearance.js +84 -0
  20. package/dist/core/memory-engine/provenance-wording.d.ts +50 -0
  21. package/dist/core/memory-engine/provenance-wording.js +15 -0
  22. package/dist/core/memory-engine/tools.d.ts +61 -7
  23. package/dist/core/memory-engine/tools.js +34 -9
  24. package/dist/core/memory-engine/types.d.ts +70 -2
  25. package/dist/core/runner/prepare-memory.js +50 -15
  26. package/dist/core/runner/prepare-task.d.ts +24 -0
  27. package/dist/core/runner/prepare-task.js +80 -10
  28. package/dist/core/session-reconcile.js +3 -2
  29. package/dist/core/types.d.ts +38 -3
  30. package/dist/core/types.js +3 -0
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +2 -1
  33. package/dist/tools/task-list.d.ts +5 -1
  34. package/package.json +1 -1
  35. package/test/export-surface.snapshot.json +10 -2
@@ -1,12 +1,17 @@
1
- import { closeSync, constants as fsConstants, existsSync, fchmodSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1
+ import { createHash } from "node:crypto";
2
+ import { closeSync, constants as fsConstants, existsSync, fchmodSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
2
3
  import { dirname, join, relative, sep } from "node:path";
3
4
  import { uuidv7 } from "../../internal/harness.js";
4
5
  import { MAX_MEMORY_BYTES, composeMemoryBlock, firstSentence } from "../memory.js";
5
6
  import { inlineUntrusted } from "../untrusted-text.js";
6
7
  import { mintSystemReminder } from "../reminder-mint.js";
7
8
  import { formatMemoryAge } from "../memory-recall.js";
8
- import { committedOriginOf, computeEntryRev, hasOriginFormExtra, originEquals, parseEntryFile, serializeEntryFile, stripModelWrittenOrigin } from "./frontmatter.js";
9
+ import { committedOriginOf, computeEntryRev, hasOriginFormExtra, isValidEntryId, originEquals, parseEntryFile, serializeEntryFile, stripModelWrittenOrigin } from "./frontmatter.js";
10
+ import { MEMORY_PROVENANCE_RECALL_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow } from "./provenance-wording.js";
9
11
  import { isInstructionEntry } from "./header-hints.js";
12
+ import { classifySessionSettlements, closeSessionAccount, disposeHold, effectiveSettlements, expireOverdueSettlements, foreignDanglingSessionAccounts, markHoldReleased, openInstructionHold, openSessionAccount, readHoldCustody, readHolds, reconcileHolds, replayExternalSettlementEffects, resolveHoldRecord, resolveSessionAccountRecord, resolveSettlementRecord, sessionSettlements, sessionUnattributedSet, } from "./delegation-settlement.js";
13
+ import { noReplaceRestore } from "./delegation-settlement.js";
14
+ import { openOriginClearance, readOriginClearances, settleOriginClearance } from "./origin-clearance.js";
10
15
  import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, canonicalJsonStringify, captureErasureInput, erasureRequestInvalid, erasureSelectHash, scanEntryFiles, } from "./file-backend.js";
11
16
  import { assembleMemoryExportBundle, computeMemoryBundleHash, memoryBundleInvalid, } from "./export-bundle.js";
12
17
  import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, markSessionPolluted, readSessionPollution, recordRetrievedAccount, writeFileNoFollow, readRetrievedAccount, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, appendChallengeEvents, appendLineageAudit, rebuildStrictControlPlaneLedger, isStrictControlPlaneLedgerCorrupt, CHALLENGE_LEDGER_MAX_EVENTS, adjudicateLineagePending, challengedEntryIds, clearLineageForEntries, discardLineagePending, lineageAccountOfEntry, lineageContributionsOfSession, lineageLatchedIds, promoteLineagePending, readChallengeEvents, readChallengedHistory, readLineageRecord, recordChallengedHistory, recordLineageCredential, reconcileLineage, resolveChallengeEvent, stageLineagePending, } from "./layout.js";
@@ -36,6 +41,25 @@ export function buildMemoryInstruction(memoryDir, instructionFileName) {
36
41
  return MEMORY_INSTRUCTION_TEMPLATE.replaceAll("{{MEMORY_DIR}}", () => dir).replaceAll("{{INSTRUCTION_FILE}}", () => instructionFileName ?? "CLAUDE.md");
37
42
  }
38
43
  export const MEMORY_RECALL_DISCIPLINE = "Before answering questions about earlier work, decisions, dates, people, or the user's preferences, look them up: `memory_search` finds entries by keyword and `memory_get` reads a full entry — the injected memory index only lists what exists. When a lookup comes up empty, say that you checked memory and found nothing instead of guessing.";
44
+ export function memoryRecallDisciplineSegment(provenance) {
45
+ return provenance === "carry" ? `${MEMORY_RECALL_DISCIPLINE} ${MEMORY_PROVENANCE_RECALL_SENTENCE}` : MEMORY_RECALL_DISCIPLINE;
46
+ }
47
+ export function entryFileHeadCarriesOrigin(absPath, maxBytes = 64 * 1024) {
48
+ try {
49
+ const fd = openSync(absPath, "r");
50
+ try {
51
+ const buf = Buffer.alloc(maxBytes);
52
+ const n = readSync(fd, buf, 0, maxBytes, 0);
53
+ return committedOriginOf(parseEntryFile(buf.subarray(0, n).toString("utf8")).frontmatter) !== undefined;
54
+ }
55
+ finally {
56
+ closeSync(fd);
57
+ }
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
39
63
  export const MEMORY_PREFERENCE_DISCIPLINE = "When the user confirms a stored preference or fact still holds, refresh that entry's `last-confirmed: <YYYY-MM-DD>` frontmatter line (add it when absent). When you save a preference, add an `applies-when: <context>` frontmatter line naming when it applies. Both are plain frontmatter lines — write them yourself; nothing fills them in for you.";
40
64
  export const MEMORY_ANNOUNCEMENT_READONLY_PLANE_CODA = "The notices immediately above concern a READ-ONLY memory store: any guidance in them to record, update, or tombstone an entry cannot be applied to that store this session — surface it to the user instead of claiming it done.";
41
65
  export const MEMORY_ANNOUNCEMENT_READONLY_CODA = "The memory store itself is not writable this session, so any guidance above to record, update, or tombstone a memory entry cannot be applied here — surface it to the user instead of claiming it done.";
@@ -48,6 +72,7 @@ export const STUB_ARCHIVED_LINE = "[body archived — request hydration by listi
48
72
  export const DEFAULT_MAX_MEMORY_FILES = 500;
49
73
  export const DEFAULT_HARVEST_DEADLINE_MS = 5_000;
50
74
  export const DEFAULT_HARVEST_FILE_BUDGET = 2_000;
75
+ export const DEFAULT_HOLD_SETTLE_TIMEOUT_MS = 72 * 60 * 60 * 1000;
51
76
  export const MASS_DELETION_FUSE_RATIO = 0.5;
52
77
  let indexCaptureSeq = 0;
53
78
  export function memorySessionPollutedNotice(input) {
@@ -82,6 +107,14 @@ export function pollutionContainmentCounts(report) {
82
107
  }
83
108
  export function memoryHarvestQuarantinedNotice(input) {
84
109
  const reason = input.reason !== undefined ? inlineUntrusted(input.reason, 200) : undefined;
110
+ if (input.count === 0 && input.indexRolledBack === true) {
111
+ return {
112
+ code: "memory.harvest_quarantined",
113
+ message: `Memory harvest rolled the derived index (MEMORY.md) back to its materialize-time baseline${reason !== undefined ? ` (${reason})` : ""}: ` +
114
+ `this session's index prose additions were captured to control-plane quarantine and not retained. No entry files were withheld.`,
115
+ detail: { count: 0, moved: 0, escalated: 0, indexRolledBack: true, ...(reason !== undefined ? { reason } : {}), ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}) },
116
+ };
117
+ }
85
118
  const lead = input.provenance === "carry"
86
119
  ? `Memory harvest withheld instruction-form files for this externally exposed session${reason !== undefined ? ` (${reason})` : ""} (ordinary entries committed with an external-origin marker): `
87
120
  : `Memory harvest committed nothing for this polluted session${reason !== undefined ? ` (${reason})` : ""}: `;
@@ -103,6 +136,39 @@ export function memoryHarvestQuarantinedNotice(input) {
103
136
  },
104
137
  };
105
138
  }
139
+ export function memoryHoldNotices(report, sessionId) {
140
+ const c = report.containment;
141
+ if (c === undefined)
142
+ return [];
143
+ const caps = (paths) => paths.slice(0, 20).map((p) => inlineUntrusted(p, 160));
144
+ const notices = [];
145
+ if (c.heldInstruction.length > 0) {
146
+ notices.push({
147
+ code: "memory.hold_opened",
148
+ message: `Memory harvest HELD ${c.heldInstruction.length} instruction-form entry file(s): this session's delegation outcome ` +
149
+ `is still pending, so instruction entries wait off the model-visible plane and commit automatically once every ` +
150
+ `delegation settles clean (a dirty or expired settlement disposes them to quarantine, announced; resolveHold is the host valve).`,
151
+ detail: { count: c.heldInstruction.length, paths: caps(c.heldInstruction), ...(sessionId !== undefined ? { sessionId } : {}) },
152
+ });
153
+ }
154
+ if (c.releasedHolds.length > 0) {
155
+ notices.push({
156
+ code: "memory.hold_released",
157
+ message: `Memory harvest RELEASED ${c.releasedHolds.length} held instruction entry file(s) — their writer sessions settled clean (or a host valve released them) and the entries re-walked the full gate set and committed.`,
158
+ detail: { count: c.releasedHolds.length, paths: caps(c.releasedHolds), ...(sessionId !== undefined ? { sessionId } : {}) },
159
+ });
160
+ }
161
+ if (c.disposedHolds.length > 0) {
162
+ notices.push({
163
+ code: "memory.hold_disposed",
164
+ message: `Memory harvest DISPOSED ${c.disposedHolds.length} held instruction entry file(s) to control-plane quarantine ` +
165
+ `(terminals: ${[...new Set(c.disposedHolds.map((d) => d.terminal))].join(", ")}). An "expired" terminal is a TIMEOUT, not a ` +
166
+ `conviction — resolveHold(holdId, "release") commits such an entry with a "static"-cause marker after host review; the bytes are never silently dropped.`,
167
+ detail: { count: c.disposedHolds.length, disposed: c.disposedHolds.slice(0, 20).map((d) => ({ path: inlineUntrusted(d.relPath, 160), terminal: d.terminal })), ...(sessionId !== undefined ? { sessionId } : {}) },
168
+ });
169
+ }
170
+ return notices;
171
+ }
106
172
  export class MemoryEngine {
107
173
  backend;
108
174
  memoryDir;
@@ -114,6 +180,7 @@ export class MemoryEngine {
114
180
  maxDepth;
115
181
  harvestDeadlineMs;
116
182
  harvestFileBudget;
183
+ holdSettleTimeoutMs;
117
184
  provenance;
118
185
  onIncident;
119
186
  backendPinnedRoot;
@@ -142,8 +209,27 @@ export class MemoryEngine {
142
209
  this.maxDepth = opts.maxDepth ?? DEFAULT_MAX_ENTRY_DEPTH;
143
210
  this.harvestDeadlineMs = opts.harvestDeadlineMs ?? DEFAULT_HARVEST_DEADLINE_MS;
144
211
  this.harvestFileBudget = opts.harvestFileBudget ?? DEFAULT_HARVEST_FILE_BUDGET;
212
+ if (opts.holdSettleTimeoutMs !== undefined && (typeof opts.holdSettleTimeoutMs !== "number" || !Number.isFinite(opts.holdSettleTimeoutMs) || opts.holdSettleTimeoutMs <= 0)) {
213
+ const got = typeof opts.holdSettleTimeoutMs === "number" ? String(opts.holdSettleTimeoutMs) : opts.holdSettleTimeoutMs === null ? "null" : typeof opts.holdSettleTimeoutMs;
214
+ const e = new Error(`MemoryEngineOptions.holdSettleTimeoutMs must be a finite positive number of milliseconds when present (got ${got}) — an unevaluable settlement window is refused loudly, never folded to the default.`);
215
+ e.code = "config.memory_hold_timeout";
216
+ throw e;
217
+ }
218
+ this.holdSettleTimeoutMs = opts.holdSettleTimeoutMs ?? DEFAULT_HOLD_SETTLE_TIMEOUT_MS;
145
219
  this.onIncident = opts.onIncident;
146
220
  }
221
+ discloseSessionAccountIncident(what, cause) {
222
+ const sink = this.onIncident;
223
+ if (sink === undefined)
224
+ return;
225
+ try {
226
+ const e = new Error(`memory session account: ${what}${cause !== undefined ? `: ${cause instanceof Error ? cause.message : String(cause)}` : ""}`);
227
+ e.code = "memory.session_account_failed";
228
+ sink(e);
229
+ }
230
+ catch {
231
+ }
232
+ }
147
233
  discloseAnnounceFailure(stage, cause) {
148
234
  const sink = this.onIncident;
149
235
  if (sink === undefined)
@@ -756,6 +842,7 @@ export class MemoryEngine {
756
842
  ensureDirExists(this.memoryDir);
757
843
  ensureDirExists(this.controlDir);
758
844
  const restricted = opts?.adoptionRestricted === true;
845
+ let sessionAccountFailed = false;
759
846
  const readBackend = restricted
760
847
  ? (this.backend.restrictedAdoptionView?.({ audit: true, writeScope }) ?? this.backend)
761
848
  : this.backend;
@@ -768,6 +855,112 @@ export class MemoryEngine {
768
855
  scopeDirs.set(writeScope, registerScope(this.memoryDir, this.controlDir, writeScope));
769
856
  for (const [scope, dir] of scopeDirs)
770
857
  this.chmodScopeTree(dir, 0o755, 0o644, { excludeTopDirs: this.siblingScopeDirNames(dir, scope) });
858
+ if (this.provenance === "carry" && opts?.sessionId !== undefined && writeScope !== null && !restricted) {
859
+ let accountFailed = false;
860
+ try {
861
+ const foreign = foreignDanglingSessionAccounts(this.controlDir, opts.sessionId);
862
+ const unattributed = [];
863
+ if (foreign.length > 0) {
864
+ const wroot = canonicalize(scopeDirFor(this.memoryDir, this.controlDir, writeScope));
865
+ let committedRevs;
866
+ const snapFace = this.backend.committedSnapshotsOfScopes;
867
+ if (snapFace === undefined) {
868
+ this.discloseSessionAccountIncident("the backend exposes no committed-snapshot face — the id-bearing crash-residue arms are degraded to the id-less arm for this materialize", undefined);
869
+ }
870
+ if (snapFace !== undefined) {
871
+ const snap = await snapFace.call(this.backend, [writeScope]);
872
+ if (snap.complete && snap.rows !== undefined) {
873
+ committedRevs = new Map(snap.rows.map((r) => [r.id, r.rev]));
874
+ }
875
+ else {
876
+ this.discloseSessionAccountIncident("the committed snapshot was contended/incomplete — the id-bearing residue arm is degraded to id-less for this materialize", undefined);
877
+ }
878
+ }
879
+ for (const f of scanEntryFiles(wroot, { maxDepth: this.maxDepth })) {
880
+ const canonical = canonicalize(f.path);
881
+ if (canonical !== wroot && !canonical.startsWith(`${wroot}${sep}`))
882
+ continue;
883
+ const text = readSafe(canonical);
884
+ if (text === undefined)
885
+ continue;
886
+ const rel = relative(this.memoryDir, canonical);
887
+ if (rel === MEMORY_INDEX_FILENAME || rel.endsWith(`${sep}${MEMORY_INDEX_FILENAME}`))
888
+ continue;
889
+ const parsed = parseEntryFile(text);
890
+ const uncommitted = parsed.id === undefined || (committedRevs !== undefined && !committedRevs.has(parsed.id));
891
+ if (uncommitted) {
892
+ unattributed.push(rel);
893
+ continue;
894
+ }
895
+ if (parsed.id !== undefined && committedRevs !== undefined) {
896
+ const committedRev = committedRevs.get(parsed.id);
897
+ const diskRev = computeEntryRev({ id: parsed.id, frontmatter: parsed.frontmatter, body: parsed.body });
898
+ if (committedRev !== undefined && diskRev !== committedRev) {
899
+ const committed = await this.committedContentFor(parsed.id);
900
+ if (committed !== undefined && committed !== text) {
901
+ let captured = false;
902
+ const captureName = `${this.now()}-residue-${parsed.id}.md`;
903
+ const capturePath = join(this.controlDir, QUARANTINE_DIR, captureName);
904
+ try {
905
+ ensureDirExists(join(this.controlDir, QUARANTINE_DIR));
906
+ writeFileSync(capturePath, text, { encoding: "utf8", flag: "wx" });
907
+ captured = readFileSync(capturePath, "utf8") === text;
908
+ }
909
+ catch {
910
+ captured = false;
911
+ }
912
+ let restoredCommitted = false;
913
+ if (captured) {
914
+ const stagingPath = `${capturePath}.staging`;
915
+ try {
916
+ renameSync(canonical, stagingPath);
917
+ if (readFileSync(stagingPath, "utf8") === text) {
918
+ writeFileNoFollow(canonical, committed);
919
+ restoredCommitted = true;
920
+ try {
921
+ unlinkSync(stagingPath);
922
+ }
923
+ catch {
924
+ }
925
+ }
926
+ else if (noReplaceRestore(stagingPath, canonical) === "stranded") {
927
+ this.discloseSessionAccountIncident(`a concurrent write to ${rel} landed during residue containment and could not be restored — its bytes are preserved at quarantine/${captureName}.staging`, undefined);
928
+ }
929
+ }
930
+ catch {
931
+ }
932
+ }
933
+ else {
934
+ this.discloseSessionAccountIncident(`the crash-residue edit of ${rel} could not be captured to quarantine — the divergent bytes were left in place (the inbound gate adjudicates them)`, undefined);
935
+ }
936
+ try {
937
+ enqueueMemoryAnnouncement(this.controlDir, {
938
+ kind: "external",
939
+ at: this.now(),
940
+ items: [
941
+ restoredCommitted
942
+ ? `crash residue: an edit of committed memory entry ${quoteId(rel)} pre-existed this session under an unresolved foreign session account — its writer cannot be attributed, so the edit bytes were captured to control-plane quarantine (${captureName}) and the committed content restored (host review adjudicates the edit)`
943
+ : `crash residue: an edit of committed memory entry ${quoteId(rel)} pre-existed this session under an unresolved foreign session account and could NOT be fully contained — the divergent/newer bytes were left in place (or preserved beside the capture) for the ordinary inbound gate`,
944
+ ],
945
+ });
946
+ }
947
+ catch (annErr) {
948
+ this.discloseAnnounceFailure("session-account residue", annErr);
949
+ }
950
+ }
951
+ }
952
+ }
953
+ }
954
+ }
955
+ openSessionAccount(this.controlDir, { sessionId: opts.sessionId, now: this.now, unattributed });
956
+ }
957
+ catch (err) {
958
+ accountFailed = true;
959
+ this.discloseSessionAccountIncident("crash-residue attribution is degraded for this session (the harvest boundary refuses fail-closed while this stands)", err);
960
+ }
961
+ if (accountFailed)
962
+ sessionAccountFailed = true;
963
+ }
771
964
  const allScopes = [...scopeDirs.keys()];
772
965
  const headers = await readBackend.listHeaders(allScopes);
773
966
  const entries = await readBackend.getByIds(headers.map((h) => h.id));
@@ -791,6 +984,7 @@ export class MemoryEngine {
791
984
  indexBaselineLines: 0,
792
985
  indexText: "",
793
986
  ...(restricted ? { adoptionRestricted: true } : {}),
987
+ ...(sessionAccountFailed ? { sessionAccountFailed: true } : {}),
794
988
  };
795
989
  if (writeScope !== null)
796
990
  ensureDirExists(handle.writableRoot);
@@ -955,6 +1149,18 @@ export class MemoryEngine {
955
1149
  const mintOrigin = () => ({ taint: "external", cause: pollutionCause ?? "observed", at: this.now() });
956
1150
  const lineageSessionId = opts?.sessionId;
957
1151
  const startedAt = this.now();
1152
+ let sessionPending = false;
1153
+ let pendingSettleIds = [];
1154
+ let unattributedSet = new Set();
1155
+ const containment = { indexRolledBack: false, quarantinedInstruction: [], heldInstruction: [], releasedHolds: [], disposedHolds: [] };
1156
+ const signalContainment = (fill) => {
1157
+ fill(containment);
1158
+ report.containment = containment;
1159
+ };
1160
+ const pushDistinct = (arr, rel) => {
1161
+ if (!arr.includes(rel))
1162
+ arr.push(rel);
1163
+ };
958
1164
  const report = {
959
1165
  ok: true,
960
1166
  patches: { add: 0, update: 0, delete: 0 },
@@ -981,6 +1187,11 @@ export class MemoryEngine {
981
1187
  report.warnings.push(opts.admitNothing.reason);
982
1188
  return report;
983
1189
  }
1190
+ if (carry && handle.sessionAccountFailed === true) {
1191
+ report.ok = false;
1192
+ report.incident = { kind: "sidecar_corrupt", detail: "memory session account could not be opened at materialize — the crash-residue classification for this session never persisted, so the harvest refuses fail-closed (repair the control plane and re-materialize)" };
1193
+ return report;
1194
+ }
984
1195
  try {
985
1196
  this.backend.checkControlPlane?.();
986
1197
  }
@@ -990,6 +1201,34 @@ export class MemoryEngine {
990
1201
  return report;
991
1202
  }
992
1203
  try {
1204
+ if (carry) {
1205
+ reconcileHolds(this.controlDir, { memoryDir: this.memoryDir, now: this.now });
1206
+ const newlyExpired = expireOverdueSettlements(this.controlDir, { timeoutMs: this.holdSettleTimeoutMs, now: this.now });
1207
+ if (newlyExpired.length > 0) {
1208
+ report.warnings.push(`${newlyExpired.length} delegation settlement row(s) passed the ${this.holdSettleTimeoutMs}ms window without settling and are now EXPIRED — a timeout, not a conviction: the affected sessions' unprovable windows close exposed, their held instruction entries dispose to quarantine (resolveHold can still release one with a "static"-cause marker), and resolveSettlement can overturn the expiry with an audited ruling`);
1209
+ }
1210
+ replayExternalSettlementEffects(this.controlDir, { carry, now: this.now });
1211
+ const settled = await this.settleDueHolds(handle, report);
1212
+ for (const rel of settled.released)
1213
+ signalContainment((c) => pushDistinct(c.releasedHolds, rel));
1214
+ for (const d of settled.disposed)
1215
+ signalContainment((c) => c.disposedHolds.push(d));
1216
+ if (lineageSessionId !== undefined) {
1217
+ const rows = sessionSettlements(this.controlDir, lineageSessionId);
1218
+ const cls = classifySessionSettlements(rows);
1219
+ if (cls.state === "exposed") {
1220
+ if (pollutedReason === undefined) {
1221
+ pollutedReason = cls.reason ?? "the session's delegation settlement account holds an external, unattestable or expired row";
1222
+ pollutionCause = cls.cause;
1223
+ }
1224
+ }
1225
+ else if (cls.state === "pending") {
1226
+ sessionPending = true;
1227
+ pendingSettleIds = rows.filter((r) => r.effective === "pending").map((r) => r.row.settleId);
1228
+ }
1229
+ unattributedSet = sessionUnattributedSet(this.controlDir, lineageSessionId);
1230
+ }
1231
+ }
993
1232
  const rec = reconcileLineage(this.controlDir, this.now);
994
1233
  this.settlePromotions(rec.promoted);
995
1234
  for (const u of rec.undecidable) {
@@ -1056,7 +1295,8 @@ export class MemoryEngine {
1056
1295
  const withheldWhy = carry
1057
1296
  ? "this session is marked externally exposed and this file is instruction-form (type: feedback, or pinned/triggers/applies-when header lines) — instruction-form entries from exposed sessions are withheld for host review"
1058
1297
  : "this session invoked a tool classified as an external content source, so its memory changes were not committed";
1059
- const containPollutedRecord = async (f) => {
1298
+ const containPollutedRecord = async (f, whyOverride) => {
1299
+ const why = whyOverride ?? withheldWhy;
1060
1300
  const rel = f.rel;
1061
1301
  if (f.canonical !== handle.writableRoot && !f.canonical.startsWith(`${handle.writableRoot}${sep}`)) {
1062
1302
  report.rejections.push({ path: rel, code: "outside_root", reason: "file resolves outside the writable memory root (containment gate, fail-closed)" });
@@ -1075,7 +1315,7 @@ export class MemoryEngine {
1075
1315
  report.rejections.push({
1076
1316
  path: rel,
1077
1317
  code: "polluted",
1078
- reason: `memory write withheld: ${withheldWhy} (archived-body stub; ${stubCaptured && sq.removed ? "file moved to quarantine for host review, and the next session re-projects the stub" : "containment incomplete — the edited stub may still be on the model-visible plane"})`,
1318
+ reason: `memory write withheld: ${why} (archived-body stub; ${stubCaptured && sq.removed ? "file moved to quarantine for host review, and the next session re-projects the stub" : "containment incomplete — the edited stub may still be on the model-visible plane"})`,
1079
1319
  });
1080
1320
  if (sq.detail !== undefined || !sq.removed || !stubCaptured) {
1081
1321
  const detail = `${sq.detail ?? (stubCaptured ? "suspect content still on the model-visible plane" : "quarantine capture failed")}${sq.removed ? "" : " — NOT contained"}`;
@@ -1106,7 +1346,7 @@ export class MemoryEngine {
1106
1346
  report.rejections.push({
1107
1347
  path: rel,
1108
1348
  code: "polluted",
1109
- reason: `memory write withheld: ${withheldWhy} (${captured && contained ? "file moved to quarantine for host review" : captured ? "quarantine copy captured; removal from the model-visible plane incomplete" : "quarantine copy FAILED; file removal " + (contained ? "succeeded" : "incomplete")})`,
1349
+ reason: `memory write withheld: ${why} (${captured && contained ? "file moved to quarantine for host review" : captured ? "quarantine copy captured; removal from the model-visible plane incomplete" : "quarantine copy FAILED; file removal " + (contained ? "succeeded" : "incomplete")})`,
1110
1350
  });
1111
1351
  if (q.detail !== undefined || !contained || !captured) {
1112
1352
  const parts = [q.detail, !captured ? "quarantine capture failed" : undefined].filter((x) => x !== undefined);
@@ -1120,11 +1360,11 @@ export class MemoryEngine {
1120
1360
  const canonicalIndex = canonicalize(pollutedIndexPath);
1121
1361
  if (canonicalIndex !== pollutedIndexPath && !canonicalIndex.startsWith(`${handle.writableRoot}${sep}`)) {
1122
1362
  report.warnings.push("memory index NOT restored: its path resolves outside the writable memory root (containment gate, fail-closed)");
1123
- return;
1363
+ return false;
1124
1364
  }
1125
1365
  const indexNow = readSafe(pollutedIndexPath);
1126
1366
  if (indexNow === undefined || indexNow === handle.indexText)
1127
- return;
1367
+ return false;
1128
1368
  let indexCaptureLanded = false;
1129
1369
  try {
1130
1370
  const dest = join(this.controlDir, QUARANTINE_DIR, `${this.now()}-polluted-${MEMORY_INDEX_FILENAME}`);
@@ -1137,15 +1377,18 @@ export class MemoryEngine {
1137
1377
  try {
1138
1378
  writeFileNoFollow(pollutedIndexPath, handle.indexText);
1139
1379
  report.warnings.push(`memory index restored to its pre-session state — this session's index additions were not retained (session polluted; ${indexCaptureLanded ? "the removed text was captured to quarantine" : "the quarantine capture did NOT land — the removed text is gone"})`);
1380
+ return true;
1140
1381
  }
1141
1382
  catch (err) {
1142
1383
  report.warnings.push(`memory index could NOT be restored to its pre-session state: ${err instanceof Error ? err.message : String(err)}`);
1384
+ return false;
1143
1385
  }
1144
1386
  };
1145
1387
  const containPollutedDomain = async () => {
1146
1388
  for (const f of records)
1147
1389
  await containPollutedRecord(f);
1148
- restorePollutedIndex();
1390
+ if (restorePollutedIndex())
1391
+ signalContainment((c) => (c.indexRolledBack = true));
1149
1392
  report.warnings.push(`memory harvest committed nothing this session: ${inlineUntrusted(pollutedReason, 200)}`);
1150
1393
  };
1151
1394
  const containExposedInstructionDomain = async () => {
@@ -1158,10 +1401,13 @@ export class MemoryEngine {
1158
1401
  instruction = committedFm !== undefined && isInstructionEntry(committedFm);
1159
1402
  }
1160
1403
  }
1161
- if (instruction)
1404
+ if (instruction) {
1162
1405
  await containPollutedRecord(f);
1406
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, f.rel));
1407
+ }
1163
1408
  }
1164
- restorePollutedIndex();
1409
+ if (restorePollutedIndex())
1410
+ signalContainment((c) => (c.indexRolledBack = true));
1165
1411
  report.warnings.push(`memory harvest refused by an incident while this session is marked externally exposed — no entries were committed; instruction-form files were withheld and the derived index rolled back: ${inlineUntrusted(pollutedReason, 200)}`);
1166
1412
  };
1167
1413
  for (const m of handle.materialized.filter((x) => x.readonly)) {
@@ -1340,8 +1586,54 @@ export class MemoryEngine {
1340
1586
  report.warnings.push(`${rel}: origin-form frontmatter written on the model-visible plane was not adopted — the external-origin marker is engine-authored (the engine's own record of this session decides it)`);
1341
1587
  }
1342
1588
  }
1343
- if (carry && pollutedReason !== undefined && (isInstructionEntry(fm) || (committedFmHere !== undefined && isInstructionEntry(committedFmHere)))) {
1589
+ const instructionForm = carry && (isInstructionEntry(fm) || (committedFmHere !== undefined && isInstructionEntry(committedFmHere)));
1590
+ if (instructionForm && unattributedSet.has(rel)) {
1591
+ await containPollutedRecord(f, "this file pre-existed the session under an unresolved foreign session account (crash residue) and is instruction-form — its writer cannot be attributed, so it is withheld for host review");
1592
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, rel));
1593
+ continue;
1594
+ }
1595
+ if (instructionForm && pollutedReason !== undefined) {
1344
1596
  await containPollutedRecord(f);
1597
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, rel));
1598
+ continue;
1599
+ }
1600
+ if (instructionForm && sessionPending && lineageSessionId !== undefined) {
1601
+ const held = openInstructionHold(this.controlDir, {
1602
+ sessionId: lineageSessionId,
1603
+ relPath: rel,
1604
+ absPath: f.canonical,
1605
+ content: f.text,
1606
+ ...(committedIdHere !== undefined ? { entryId: committedIdHere } : {}),
1607
+ op: baseId !== undefined ? "update" : "add",
1608
+ ...(baseId !== undefined && revByBasePath.get(f.canonical) !== undefined ? { baseRev: revByBasePath.get(f.canonical) } : {}),
1609
+ slug: f.slug,
1610
+ scope: writeScope,
1611
+ settleIds: pendingSettleIds,
1612
+ now: this.now,
1613
+ });
1614
+ if (held.ok) {
1615
+ report.warnings.push(`${rel}: instruction-form entry HELD — this session's delegation outcome is still pending (attested-only evidence standard); the file was captured off the model-visible plane and commits automatically when every delegation settles clean, disposes to quarantine on a dirty or expired settlement (resolveHold is the host valve)`);
1616
+ if (held.thirdWriterStranded !== undefined) {
1617
+ report.warnings.push(`${rel}: a concurrent writer's bytes could not be restored onto the plane while the hold was captured — they are preserved at hold/${held.thirdWriterStranded} in the memory control plane for host recovery`);
1618
+ }
1619
+ signalContainment((c) => pushDistinct(c.heldInstruction, rel));
1620
+ if (committedIdHere !== undefined) {
1621
+ const committed = await this.committedContentFor(committedIdHere);
1622
+ if (committed !== undefined) {
1623
+ try {
1624
+ writeFileNoFollow(f.canonical, committed);
1625
+ report.restored.push(rel);
1626
+ }
1627
+ catch (err) {
1628
+ report.warnings.push(`${rel}: the committed content could not be restored after the hold captured this session's edit — the entry is unavailable on the plane until release or the next restore: ${err instanceof Error ? err.message : String(err)}`);
1629
+ }
1630
+ }
1631
+ }
1632
+ }
1633
+ else {
1634
+ await containPollutedRecord(f, `this session's delegation outcome is pending and this instruction-form file could not be HELD (${held.reason}) — withheld fail-closed for host review`);
1635
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, rel));
1636
+ }
1345
1637
  continue;
1346
1638
  }
1347
1639
  if (fm.deleted !== true) {
@@ -1352,6 +1644,10 @@ export class MemoryEngine {
1352
1644
  report.warnings.push(`${rel}: the entry's committed external-origin marker was carried in a legacy byte form — normalized into the typed origin field (the marker follows the content)`);
1353
1645
  }
1354
1646
  }
1647
+ else if (carry && unattributedSet.has(rel)) {
1648
+ fm.origin = { taint: "external", cause: "unattributed", at: this.now() };
1649
+ report.warnings.push(`${rel}: this file pre-existed the session under an unresolved foreign session account (crash residue) — committed with an external-origin marker (cause "unattributed"), not under this session's name`);
1650
+ }
1355
1651
  else if (carry && pollutedReason !== undefined) {
1356
1652
  fm.origin = mintOrigin();
1357
1653
  }
@@ -1449,8 +1745,10 @@ export class MemoryEngine {
1449
1745
  const committedFm = await this.committedFrontmatterFor(p.id);
1450
1746
  if (committedFm !== undefined && isInstructionEntry(committedFm)) {
1451
1747
  const rec = records.find((r) => (handle.baseIds.get(r.canonical) ?? r.parsed.id) === p.id && r.parsed.frontmatter.deleted === true);
1452
- if (rec !== undefined)
1748
+ if (rec !== undefined) {
1453
1749
  await containPollutedRecord(rec);
1750
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, rec.rel));
1751
+ }
1454
1752
  continue;
1455
1753
  }
1456
1754
  keptPatches.push(p);
@@ -1464,8 +1762,10 @@ export class MemoryEngine {
1464
1762
  if (isInstructionEntry(p.entry.frontmatter) || (committedFm !== undefined && isInstructionEntry(committedFm))) {
1465
1763
  const projection = pendingProjections.find((pp) => pp.entry.id === p.id);
1466
1764
  const rec = projection !== undefined ? records.find((r) => r.canonical === projection.path) : undefined;
1467
- if (rec !== undefined)
1765
+ if (rec !== undefined) {
1468
1766
  await containPollutedRecord(rec);
1767
+ signalContainment((c) => pushDistinct(c.quarantinedInstruction, rec.rel));
1768
+ }
1469
1769
  droppedIds.add(p.id);
1470
1770
  continue;
1471
1771
  }
@@ -1601,11 +1901,18 @@ export class MemoryEngine {
1601
1901
  if (drained !== undefined && drained.length > 0)
1602
1902
  report.inboundFindings = drained;
1603
1903
  if (pollutedReason !== undefined) {
1604
- restorePollutedIndex();
1904
+ if (restorePollutedIndex())
1905
+ signalContainment((c) => (c.indexRolledBack = true));
1605
1906
  report.warnings.push(carry
1606
1907
  ? `memory entries from this externally exposed session were committed with an external-origin marker; its index prose additions were not retained: ${inlineUntrusted(pollutedReason, 200)}`
1607
1908
  : `memory harvest committed nothing this session: ${inlineUntrusted(pollutedReason, 200)}`);
1608
1909
  }
1910
+ else if (carry && sessionPending) {
1911
+ if (restorePollutedIndex()) {
1912
+ signalContainment((c) => (c.indexRolledBack = true));
1913
+ report.warnings.push("this session's index prose additions were not retained: its delegation outcome is still pending (prose has no entry anchor for the settlement account to govern; committed entries and mechanical index lines are unaffected)");
1914
+ }
1915
+ }
1609
1916
  const indexGate = this.gateDerivedIndex(handle);
1610
1917
  if (indexGate !== undefined) {
1611
1918
  report.rejections.push(indexGate.rejection);
@@ -1615,8 +1922,344 @@ export class MemoryEngine {
1615
1922
  const headers = await this.backend.listHeaders([...new Set([...handle.scopes, writeScope])]);
1616
1923
  handle.indexText = this.rebuildIndex(handle, headers, { write: true, ignoreOnDisk: indexGate !== undefined }, report.warnings);
1617
1924
  await this.rebaseline(handle, new Set(report.degraded?.pending ?? []));
1925
+ if (carry && lineageSessionId !== undefined && report.degraded === undefined) {
1926
+ try {
1927
+ closeSessionAccount(this.controlDir, { sessionId: lineageSessionId, now: this.now });
1928
+ }
1929
+ catch (err) {
1930
+ report.warnings.push(`memory session account could not close (the dangling row keeps future crash residue attributed — over-holding, the safe side): ${err instanceof Error ? err.message : String(err)}`);
1931
+ }
1932
+ }
1618
1933
  return report;
1619
1934
  }
1935
+ async settleDueHolds(handle, report) {
1936
+ const out = { released: [], disposed: [] };
1937
+ const dispose = (row, terminal, why) => {
1938
+ const d = disposeHold(this.controlDir, { holdId: row.holdId, terminal, now: this.now });
1939
+ out.disposed.push({ relPath: row.relPath, terminal });
1940
+ report.warnings.push(`held instruction entry ${row.relPath} DISPOSED (${terminal}): ${why}${d.quarantineName !== undefined ? ` — captured bytes moved to control-plane quarantine (${d.quarantineName})` : ""}`);
1941
+ };
1942
+ for (const row of readHolds(this.controlDir)) {
1943
+ const valveRelease = row.resolved === "release" && (row.status === "held" || (row.status === "disposed" && row.disposition?.terminal === "expired"));
1944
+ if (valveRelease) {
1945
+ const released = await this.releaseHold(handle, report, row, { valve: true });
1946
+ if (released !== undefined)
1947
+ out.released.push(released);
1948
+ continue;
1949
+ }
1950
+ if (row.status !== "held")
1951
+ continue;
1952
+ if (row.resolved === "discard") {
1953
+ dispose(row, "discarded", "the host valve resolved this hold as discard");
1954
+ continue;
1955
+ }
1956
+ const marker = this.sessionPollution(row.sessionId);
1957
+ const rows = sessionSettlements(this.controlDir, row.sessionId);
1958
+ if (marker !== undefined || rows.some((r) => r.effective === "external")) {
1959
+ dispose(row, "dirty", marker !== undefined ? "the writer session is marked externally exposed" : "a delegation of the writer session settled with an observed external attestation");
1960
+ continue;
1961
+ }
1962
+ if (rows.some((r) => r.effective === "unattestable" || r.effective === "expired")) {
1963
+ dispose(row, "expired", "the writer session's delegation window closed UNPROVABLE (a timeout or an unattestable delivery — not a conviction); resolveHold(holdId, \"release\") commits the entry with a \"static\"-cause marker after host review");
1964
+ continue;
1965
+ }
1966
+ if (rows.some((r) => r.effective === "pending"))
1967
+ continue;
1968
+ const released = await this.releaseHold(handle, report, row, { valve: false });
1969
+ if (released !== undefined)
1970
+ out.released.push(released);
1971
+ }
1972
+ return out;
1973
+ }
1974
+ async releaseHold(handle, report, row, opts) {
1975
+ const disposeOut = (terminal, why) => {
1976
+ const d = disposeHold(this.controlDir, { holdId: row.holdId, terminal, now: this.now });
1977
+ report.warnings.push(`held instruction entry ${row.relPath} DISPOSED (${terminal}): ${why}${d.quarantineName !== undefined ? ` — captured bytes moved to control-plane quarantine (${d.quarantineName})` : ""}`);
1978
+ report.containment ??= { indexRolledBack: false, quarantinedInstruction: [], heldInstruction: [], releasedHolds: [], disposedHolds: [] };
1979
+ report.containment.disposedHolds.push({ relPath: row.relPath, terminal });
1980
+ return undefined;
1981
+ };
1982
+ const content = readHoldCustody(this.controlDir, row);
1983
+ if (content === undefined)
1984
+ return disposeOut("capture_lost", "the captured bytes are missing or fail their digest — nothing verifiable to commit");
1985
+ const findings = [];
1986
+ const nameFinding = scanMemoryFileName(`${row.slug}.md`);
1987
+ if (nameFinding !== undefined)
1988
+ findings.push(nameFinding);
1989
+ findings.push(...scanMemoryWrite(content));
1990
+ if (findings.length > 0)
1991
+ return disposeOut("discarded", `the release re-scan refused it (${findings[0].code}: ${findings[0].reason})`);
1992
+ if (Buffer.byteLength(content, "utf8") > this.perFileBytes)
1993
+ return disposeOut("discarded", `the captured file is over the ${this.perFileBytes}-byte cap`);
1994
+ if (this.sessionPollution(row.sessionId) !== undefined)
1995
+ return disposeOut("discarded", "the writer session's pollution marker landed before the release could commit");
1996
+ const parsed = parseEntryFile(content);
1997
+ const fm = { ...parsed.frontmatter };
1998
+ if (stripModelWrittenOrigin(fm)) {
1999
+ report.warnings.push(`${row.relPath}: origin-form frontmatter in the held bytes was not adopted — the external-origin marker is engine-authored`);
2000
+ }
2001
+ const committedFm = row.entryId !== undefined ? await this.committedFrontmatterFor(row.entryId) : undefined;
2002
+ const committedOrigin = committedFm !== undefined ? committedOriginOf(committedFm) : undefined;
2003
+ if (fm.deleted !== true) {
2004
+ if (committedOrigin !== undefined)
2005
+ fm.origin = committedOrigin;
2006
+ else if (opts.valve)
2007
+ fm.origin = { taint: "external", cause: "static", at: this.now() };
2008
+ }
2009
+ if (fm.name === undefined)
2010
+ fm.name = row.slug.split("/").pop();
2011
+ if (fm.description === undefined && fm.deleted !== true)
2012
+ fm.description = firstSentence(parsed.body) || undefined;
2013
+ const id = row.entryId ?? (parsed.id !== undefined && isValidEntryId(parsed.id) ? parsed.id : `h${createHash("sha256").update(row.holdId, "utf8").digest("hex").slice(0, 31)}`);
2014
+ const entry = { id, slug: row.slug, frontmatter: fm, body: parsed.body, rev: "", scope: row.scope };
2015
+ entry.rev = computeEntryRev(entry);
2016
+ const patch = row.op === "update" && row.entryId !== undefined
2017
+ ? { op: "update", id: row.entryId, entry, ...(row.baseRev !== undefined ? { baseRev: row.baseRev } : {}) }
2018
+ : { op: "add", id: entry.id, entry, guard: "absent" };
2019
+ const txnId = uuidv7();
2020
+ try {
2021
+ stageLineagePending(this.controlDir, txnId, row.sessionId, [{ entryId: patch.id, rev: entry.rev, ...(fm.origin !== undefined ? { marked: true } : {}) }], this.now);
2022
+ }
2023
+ catch (err) {
2024
+ report.warnings.push(`held instruction entry ${row.relPath} could not stage its lineage row — kept HELD for the next harvest: ${err instanceof Error ? err.message : String(err)}`);
2025
+ return undefined;
2026
+ }
2027
+ let patchReport;
2028
+ try {
2029
+ patchReport = await this.backend.applyPatches([patch]);
2030
+ }
2031
+ catch (err) {
2032
+ try {
2033
+ discardLineagePending(this.controlDir, txnId);
2034
+ }
2035
+ catch {
2036
+ }
2037
+ report.warnings.push(`held instruction entry ${row.relPath} could not commit (backend refused) — kept HELD for the next harvest: ${err instanceof Error ? err.message : String(err)}`);
2038
+ return undefined;
2039
+ }
2040
+ if (patchReport.conflicts.length > 0) {
2041
+ try {
2042
+ discardLineagePending(this.controlDir, txnId);
2043
+ }
2044
+ catch {
2045
+ }
2046
+ let alreadyApplied = false;
2047
+ const snapFace = this.backend.committedSnapshotOf;
2048
+ if (snapFace !== undefined) {
2049
+ try {
2050
+ const snap = await snapFace.call(this.backend, patch.id);
2051
+ alreadyApplied = snap.state === "row" && snap.rev === entry.rev && snap.binding?.scope === row.scope && (snap.binding?.slug === undefined || snap.binding.slug === row.slug);
2052
+ }
2053
+ catch {
2054
+ alreadyApplied = false;
2055
+ }
2056
+ }
2057
+ if (alreadyApplied) {
2058
+ markHoldReleased(this.controlDir, { holdId: row.holdId, now: this.now });
2059
+ report.warnings.push(`held instruction entry ${row.relPath} was already committed by an earlier release attempt (crash-idempotent retry) — marked released`);
2060
+ return row.relPath;
2061
+ }
2062
+ return disposeOut("conflict", `the entry moved since capture (${patchReport.conflicts[0].reason}) — never blind-written; the captured bytes stay in quarantine for the host to reconcile`);
2063
+ }
2064
+ try {
2065
+ recordLineageCredential(this.controlDir, txnId, patchReport.applied.filter((a) => a.op !== "delete").map((a) => a.id), this.now);
2066
+ const promoted = promoteLineagePending(this.controlDir, txnId, this.now);
2067
+ this.settlePromotions(promoted);
2068
+ }
2069
+ catch (err) {
2070
+ report.warnings.push(`released hold ${row.relPath}: lineage settlement failed after commit — the entry stays latched until a later harvest reconciles: ${err instanceof Error ? err.message : String(err)}`);
2071
+ }
2072
+ markHoldReleased(this.controlDir, { holdId: row.holdId, now: this.now });
2073
+ const abs = join(this.memoryDir, row.relPath);
2074
+ if (existsSync(abs)) {
2075
+ const canonical = canonicalize(abs);
2076
+ const text = readSafe(canonical);
2077
+ if (text !== undefined) {
2078
+ handle.baseIds.set(canonical, patch.id);
2079
+ handle.baseRevs.set(canonical, computeEntryRev({ id: patch.id, frontmatter: parseEntryFile(text).frontmatter, body: parseEntryFile(text).body }));
2080
+ handle.fileToScope.set(canonical, row.scope);
2081
+ if (!handle.materialized.some((m) => m.path === canonical)) {
2082
+ handle.materialized.push({ path: canonical, relPath: row.relPath, scope: row.scope, id: patch.id, slug: row.slug, rev: handle.baseRevs.get(canonical), stub: false, readonly: false });
2083
+ }
2084
+ }
2085
+ }
2086
+ report.warnings.push(`held instruction entry ${row.relPath} RELEASED and committed${opts.valve ? ' with a "static"-cause external-origin marker (a host-valve release is not a cleanliness proof)' : " unmarked (every delegation of its writer session settled clean)"}`);
2087
+ return row.relPath;
2088
+ }
2089
+ resolveSettlement(settleId, to, requestId) {
2090
+ resolveSettlementRecord(this.controlDir, { settleId, to, requestId, now: this.now });
2091
+ }
2092
+ resolveHold(holdId, action, requestId) {
2093
+ resolveHoldRecord(this.controlDir, { holdId, action, requestId, now: this.now });
2094
+ }
2095
+ resolveSessionAccount(sessionId, requestId) {
2096
+ resolveSessionAccountRecord(this.controlDir, { sessionId, requestId, now: this.now });
2097
+ }
2098
+ get controlPlaneDir() {
2099
+ return this.controlDir;
2100
+ }
2101
+ readDelegationSettlements() {
2102
+ return effectiveSettlements(this.controlDir);
2103
+ }
2104
+ readInstructionHolds() {
2105
+ return readHolds(this.controlDir);
2106
+ }
2107
+ readDanglingSessionAccounts(selfSessionId) {
2108
+ return foreignDanglingSessionAccounts(this.controlDir, selfSessionId);
2109
+ }
2110
+ committedAuditFace() {
2111
+ const b = this.backend;
2112
+ return b.restrictedAdoptionView?.({ audit: false }) ?? b.retrievalView?.() ?? this.backend;
2113
+ }
2114
+ static originClearRefusal(code, message) {
2115
+ const e = new Error(message);
2116
+ e.code = code;
2117
+ throw e;
2118
+ }
2119
+ async listExternalOriginEntries(scopes) {
2120
+ const face = this.committedAuditFace();
2121
+ const headers = await face.listHeaders(scopes);
2122
+ const entries = await face.getByIds(headers.filter((h) => h.exposure === "external").map((h) => h.id));
2123
+ const requested = new Set(scopes);
2124
+ const out = [];
2125
+ for (const e of entries) {
2126
+ if (!requested.has(e.scope))
2127
+ continue;
2128
+ const origin = committedOriginOf(e.frontmatter);
2129
+ if (origin === undefined)
2130
+ continue;
2131
+ out.push({ id: e.id, slug: e.slug, scope: e.scope, origin, provenance: await this.provenanceOf(e.id) });
2132
+ }
2133
+ return out;
2134
+ }
2135
+ listOriginClearances() {
2136
+ return readOriginClearances(this.controlDir);
2137
+ }
2138
+ async clearEntryOrigin(entryId, input) {
2139
+ if (typeof input.requestId !== "string" || input.requestId.length === 0) {
2140
+ MemoryEngine.originClearRefusal("memory.origin_clear_unattributed", "clearEntryOrigin requires a non-empty requestId (the audit attribution of who cleared) — refused, never defaulted.");
2141
+ }
2142
+ if (typeof input.reason !== "string" || input.reason.length === 0) {
2143
+ MemoryEngine.originClearRefusal("memory.origin_clear_invalid", "clearEntryOrigin requires a non-empty reason (the host's stated ground rides the audit row) — refused, never defaulted.");
2144
+ }
2145
+ const pending = readOriginClearances(this.controlDir).find((r) => r.entryId === entryId && r.status === "pending");
2146
+ if (pending !== undefined)
2147
+ return await this.completeOriginClearance(pending, input.requestId);
2148
+ const face = this.committedAuditFace();
2149
+ const [committed] = await face.getByIds([entryId]);
2150
+ if (committed === undefined || committed.frontmatter.deleted === true) {
2151
+ MemoryEngine.originClearRefusal("memory.origin_clear_unknown", `clearEntryOrigin: no committed memory entry ${JSON.stringify(entryId.slice(0, 80))}.`);
2152
+ }
2153
+ const origin = committedOriginOf(committed.frontmatter);
2154
+ if (origin === undefined) {
2155
+ MemoryEngine.originClearRefusal("memory.origin_clear_not_marked", `clearEntryOrigin: entry ${JSON.stringify(entryId.slice(0, 80))} carries no external-origin marker — nothing to clear.`);
2156
+ }
2157
+ if (this.readChallengeExclusions().has(entryId)) {
2158
+ MemoryEngine.originClearRefusal("memory.origin_clear_challenged", `clearEntryOrigin: entry ${JSON.stringify(entryId.slice(0, 80))} is challenged/latched — adjudicate the challenge first (the clear valve is not a challenge exit).`);
2159
+ }
2160
+ const fm = { ...committed.frontmatter, ...(committed.frontmatter.extra !== undefined ? { extra: [...committed.frontmatter.extra] } : {}) };
2161
+ stripModelWrittenOrigin(fm);
2162
+ const cleared = { id: committed.id, slug: committed.slug, scope: committed.scope, frontmatter: fm, body: committed.body, rev: "" };
2163
+ cleared.rev = computeEntryRev(cleared);
2164
+ const row = {
2165
+ clearanceId: uuidv7(),
2166
+ entryId,
2167
+ scope: committed.scope,
2168
+ slug: committed.slug,
2169
+ baseRev: committed.rev,
2170
+ origin,
2171
+ requestId: input.requestId,
2172
+ reason: input.reason,
2173
+ at: this.now(),
2174
+ entryText: serializeEntryFile(cleared),
2175
+ };
2176
+ openOriginClearance(this.controlDir, row);
2177
+ return await this.completeOriginClearance({ ...row, status: "pending", events: [] }, input.requestId);
2178
+ }
2179
+ async completeOriginClearance(row, requestId) {
2180
+ const fail = (code, detail, keepPending) => {
2181
+ try {
2182
+ settleOriginClearance(this.controlDir, { clearanceId: row.clearanceId, to: "failed", requestId, eventId: uuidv7(), now: this.now, detail, ...(keepPending ? { keepPending: true } : {}) });
2183
+ }
2184
+ catch {
2185
+ }
2186
+ MemoryEngine.originClearRefusal(code, `clearEntryOrigin ${JSON.stringify(row.entryId.slice(0, 80))}: ${detail}`);
2187
+ };
2188
+ const parsed = parseEntryFile(row.entryText);
2189
+ const cleared = { id: row.entryId, slug: row.slug, scope: row.scope, frontmatter: parsed.frontmatter, body: parsed.body, rev: "" };
2190
+ cleared.rev = computeEntryRev(cleared);
2191
+ const [current] = await this.committedAuditFace().getByIds([row.entryId]);
2192
+ if (current === undefined || committedOriginOf(current.frontmatter) !== undefined) {
2193
+ if (parsed.id !== row.entryId || committedOriginOf(parsed.frontmatter) !== undefined) {
2194
+ const what = parsed.id !== row.entryId ? "the custody bytes carry a different entry id" : "an origin form survived in the custody bytes";
2195
+ fail("memory.origin_clear_failed", `the clearance row's custody text failed its identity/cleared-state validation (${what}) — refusing to act on it. ` +
2196
+ (current === undefined
2197
+ ? "The row stays PENDING (the tombstone has committed and the row's entryText is the recovery seat — repair it, or recover the bytes by hand)."
2198
+ : "Re-judge with a fresh clearEntryOrigin call (this row is terminal; the marked entry stands untouched)."), current === undefined);
2199
+ }
2200
+ }
2201
+ let landedSlug = row.slug;
2202
+ let detail;
2203
+ if (current !== undefined && committedOriginOf(current.frontmatter) !== undefined) {
2204
+ if (current.rev !== row.baseRev) {
2205
+ fail("memory.origin_clear_conflict", `the entry moved since the clearance was judged (rev ${row.baseRev.slice(0, 12)}… → ${current.rev.slice(0, 12)}…) — call clearEntryOrigin again to re-judge (the marked entry stands untouched; this row is terminal).`, false);
2206
+ }
2207
+ if (current.scope !== row.scope || current.slug !== row.slug) {
2208
+ fail("memory.origin_clear_conflict", `the entry's projection moved since the clearance was judged (${JSON.stringify(row.scope)}/${JSON.stringify(row.slug.slice(0, 80))} → ${JSON.stringify(current.scope)}/${JSON.stringify(current.slug.slice(0, 80))}) — call clearEntryOrigin again to re-judge (the marked entry stands untouched; this row is terminal).`, false);
2209
+ }
2210
+ const del = await this.backend.applyPatches([{ op: "delete", id: row.entryId, baseRev: row.baseRev }]);
2211
+ if (del.conflicts.length > 0) {
2212
+ const [reprobe] = await this.committedAuditFace().getByIds([row.entryId]);
2213
+ if (reprobe !== undefined && committedOriginOf(reprobe.frontmatter) !== undefined) {
2214
+ fail("memory.origin_clear_conflict", `the tombstone leg was refused (${(del.conflicts[0]?.reason ?? "conflict").slice(0, 160)}) — call clearEntryOrigin again to re-judge (the marked entry stands untouched; this row is terminal).`, false);
2215
+ }
2216
+ if (reprobe === undefined) {
2217
+ const add = await this.backend.applyPatches([{ op: "add", id: row.entryId, entry: cleared, guard: "absent" }]);
2218
+ if (add.conflicts.length > 0) {
2219
+ fail("memory.origin_clear_failed", `the re-record leg was refused (${(add.conflicts[0]?.reason ?? "conflict").slice(0, 160)}) — the row stays PENDING with the cleared bytes in its custody (entryText); call clearEntryOrigin again to resume.`, true);
2220
+ }
2221
+ landedSlug = add.applied.find((a) => a.id === row.entryId)?.slug ?? row.slug;
2222
+ detail = `cleared and re-recorded at slug ${JSON.stringify(landedSlug)} (tombstone raced by a concurrent resumer)`;
2223
+ }
2224
+ else {
2225
+ landedSlug = reprobe.slug;
2226
+ detail = reprobe.rev === cleared.rev ? "already complete (raced by a concurrent resumer)" : "marker already absent (a later committed generation stands; custody retained on this row)";
2227
+ }
2228
+ }
2229
+ else {
2230
+ const add = await this.backend.applyPatches([{ op: "add", id: row.entryId, entry: cleared, guard: "absent" }]);
2231
+ if (add.conflicts.length > 0) {
2232
+ fail("memory.origin_clear_failed", `the re-record leg was refused (${(add.conflicts[0]?.reason ?? "conflict").slice(0, 160)}) — the row stays PENDING with the cleared bytes in its custody (entryText); call clearEntryOrigin again to resume.`, true);
2233
+ }
2234
+ landedSlug = add.applied.find((a) => a.id === row.entryId)?.slug ?? row.slug;
2235
+ detail = `cleared and re-recorded at slug ${JSON.stringify(landedSlug)}`;
2236
+ }
2237
+ }
2238
+ else if (current === undefined) {
2239
+ const add = await this.backend.applyPatches([{ op: "add", id: row.entryId, entry: cleared, guard: "absent" }]);
2240
+ if (add.conflicts.length > 0) {
2241
+ fail("memory.origin_clear_failed", `the resumed re-record leg was refused (${(add.conflicts[0]?.reason ?? "conflict").slice(0, 160)}) — the row stays PENDING with the cleared bytes in its custody (entryText); call clearEntryOrigin again to resume.`, true);
2242
+ }
2243
+ landedSlug = add.applied.find((a) => a.id === row.entryId)?.slug ?? row.slug;
2244
+ detail = `resumed after interruption: re-recorded at slug ${JSON.stringify(landedSlug)}`;
2245
+ }
2246
+ else {
2247
+ landedSlug = current.slug;
2248
+ detail = current.rev === cleared.rev ? "already complete (terminal event replay)" : "marker already absent (a later committed generation stands; custody retained on this row)";
2249
+ }
2250
+ settleOriginClearance(this.controlDir, { clearanceId: row.clearanceId, to: "done", requestId, eventId: uuidv7(), now: this.now, detail });
2251
+ try {
2252
+ enqueueMemoryAnnouncement(this.controlDir, {
2253
+ kind: "gate",
2254
+ at: this.now(),
2255
+ items: [`memory entry ${inlineUntrusted(row.entryId, 80)} external-origin marker CLEARED by host request ${JSON.stringify(inlineUntrusted(requestId, 80))} — re-recorded unmarked (${inlineUntrusted(detail, 160)})`],
2256
+ });
2257
+ }
2258
+ catch (err) {
2259
+ this.discloseAnnounceFailure("origin-clearance enqueue", err);
2260
+ }
2261
+ return { entryId: row.entryId, clearanceId: row.clearanceId, origin: row.origin, landedSlug };
2262
+ }
1620
2263
  async rebaseline(handle, keepBaseline = new Set()) {
1621
2264
  const writeScope = handle.writeScope;
1622
2265
  if (writeScope === null)
@@ -1697,6 +2340,9 @@ export class MemoryEngine {
1697
2340
  const nextIndexRevs = {};
1698
2341
  const expected = new Map();
1699
2342
  const excludedTargets = new Set();
2343
+ const carryIndex = this.provenance === "carry";
2344
+ const exposedById = new Map();
2345
+ const exposedProjectionTargets = new Set();
1700
2346
  for (const h of headers) {
1701
2347
  const repoTarget = handle.repoIndexTargets?.get(h.id);
1702
2348
  const target = repoTarget ?? relative(handle.writableRoot, canonicalize(join(scopeDirFor(this.memoryDir, this.controlDir, h.scope), `${h.slug}.md`)));
@@ -1704,12 +2350,46 @@ export class MemoryEngine {
1704
2350
  excludedTargets.add(target);
1705
2351
  continue;
1706
2352
  }
2353
+ if (carryIndex && h.exposure === "external") {
2354
+ exposedById.set(h.id, h);
2355
+ exposedProjectionTargets.add(target);
2356
+ continue;
2357
+ }
1707
2358
  expected.set(target, h);
1708
2359
  }
1709
2360
  const covered = new Set();
2361
+ const handleCovered = new Set();
1710
2362
  const keptByTarget = new Map();
1711
2363
  const keptLines = [];
1712
2364
  for (const line of existing.split("\n")) {
2365
+ if (carryIndex) {
2366
+ const handleId = parseMemoryExposureIndexRow(line);
2367
+ if (handleId !== undefined) {
2368
+ if (exposedById.has(handleId) && !handleCovered.has(handleId) && line === memoryExposureIndexRow(handleId)) {
2369
+ handleCovered.add(handleId);
2370
+ keptLines.push(line);
2371
+ }
2372
+ continue;
2373
+ }
2374
+ }
2375
+ if (carryIndex && exposedProjectionTargets.size > 0) {
2376
+ const foldDots = (s) => {
2377
+ let prev = s;
2378
+ for (let i = 0; i < 32; i++) {
2379
+ const next = prev.replace(/\/(?:\.\/)+/g, "/").replace(/(?:^|[/\s(])(?:[^/\s()]+\/\.\.\/)+/g, (m) => (/^[/\s(]/.test(m) ? m[0] : ""));
2380
+ if (next === prev)
2381
+ return next;
2382
+ prev = next;
2383
+ }
2384
+ return prev;
2385
+ };
2386
+ const folded = foldDots(line.toLowerCase());
2387
+ const hit = [...exposedProjectionTargets].find((t) => folded.includes(t.toLowerCase()));
2388
+ if (hit !== undefined) {
2389
+ warnings?.push(`MEMORY.md index line naming ${hit} replaced by its opaque external-origin handle (the entry stays readable via memory_get)`);
2390
+ continue;
2391
+ }
2392
+ }
1713
2393
  const target = indexLineTarget(line);
1714
2394
  if (target === undefined) {
1715
2395
  if (line.trim() !== "" || keptLines[keptLines.length - 1]?.trim() !== "")
@@ -1758,6 +2438,10 @@ export class MemoryEngine {
1758
2438
  keptLines.push(`- [${title}](${target})${hook} (${age})`);
1759
2439
  nextIndexRevs[target] = h.rev;
1760
2440
  }
2441
+ for (const id of exposedById.keys()) {
2442
+ if (!handleCovered.has(id))
2443
+ keptLines.push(memoryExposureIndexRow(id));
2444
+ }
1761
2445
  while (keptLines.length > 0 && keptLines[keptLines.length - 1].trim() === "")
1762
2446
  keptLines.pop();
1763
2447
  const text = keptLines.length > 0 ? `${keptLines.join("\n")}\n` : "";
@@ -2029,7 +2713,7 @@ function indexLineTarget(line) {
2029
2713
  return m?.[1];
2030
2714
  }
2031
2715
  function countIndexLines(text) {
2032
- return text.split("\n").filter((l) => indexLineTarget(l) !== undefined).length;
2716
+ return text.split("\n").filter((l) => indexLineTarget(l) !== undefined || parseMemoryExposureIndexRow(l) !== undefined).length;
2033
2717
  }
2034
2718
  function revOfText(text, id) {
2035
2719
  const parsed = parseEntryFile(text);