@sema-agent/core 5.36.0 → 5.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/dist/agents/subagent.d.ts +10 -0
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +142 -0
- package/dist/core/memory-engine/engine.js +264 -2
- package/dist/core/memory-engine/file-backend.d.ts +490 -16
- package/dist/core/memory-engine/file-backend.js +1099 -36
- package/dist/core/memory-engine/index.d.ts +2 -2
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +42 -2
- package/dist/core/memory-engine/layout.js +76 -12
- package/dist/core/memory-engine/memory-backend-contract.d.ts +13 -0
- package/dist/core/memory-engine/memory-backend-contract.js +89 -0
- package/dist/core/protocol-table.d.ts +4 -4
- package/dist/core/runner/assemble-result.d.ts +5 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +11 -1
- package/dist/core/runner/prepare-memory.js +48 -2
- package/dist/core/runner/prepare-task.d.ts +5 -0
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/runtask.js +4 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +13 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, } from "./engine.js";
|
|
1
|
+
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, } from "./engine.js";
|
|
2
2
|
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, } from "./tools.js";
|
|
3
3
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
4
|
-
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH, type ScannedEntryFile } from "./file-backend.js";
|
|
4
|
+
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH, type ScannedEntryFile, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, } from "./file-backend.js";
|
|
5
5
|
export { ControlPlaneCorruptError, deriveControlPlaneDir, deriveRepoControlPlaneDir, deriveRepoKey, deriveRepoMemoryDir, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, claimRootScope, rootScopeOf, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAppendResult, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, } from "./layout.js";
|
|
6
6
|
export { readV2HeaderHints, type V2HeaderHints } from "./header-hints.js";
|
|
7
7
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, type ParsedEntryFile } from "./frontmatter.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, } from "./engine.js";
|
|
2
2
|
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, } from "./tools.js";
|
|
3
3
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
4
|
-
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH } from "./file-backend.js";
|
|
4
|
+
export { FileMemoryEngineBackend, scanEntryFiles, MEMORY_INDEX_FILENAME, DEFAULT_MAX_ENTRY_DEPTH, erasureSelectHash, } from "./file-backend.js";
|
|
5
5
|
export { ControlPlaneCorruptError, deriveControlPlaneDir, deriveRepoControlPlaneDir, deriveRepoKey, deriveRepoMemoryDir, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, claimRootScope, rootScopeOf, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, } from "./layout.js";
|
|
6
6
|
export { readV2HeaderHints } from "./header-hints.js";
|
|
7
7
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile } from "./frontmatter.js";
|
|
@@ -380,15 +380,55 @@ export declare function reconcileLineage(controlDir: string, now: () => number):
|
|
|
380
380
|
* refuse — "the account is not settled, the content does not go on the table". Throws on a
|
|
381
381
|
* corrupt ledger (integrity unknown ≠ no latch). */
|
|
382
382
|
export declare function lineageLatchedIds(controlDir: string): Set<string>;
|
|
383
|
-
/** Every committed contribution of `sessionId` (the pollution-retroaction sweep input
|
|
384
|
-
* a corrupt ledger.
|
|
383
|
+
/** Every committed contribution of `sessionId` (the pollution-retroaction sweep input, and the
|
|
384
|
+
* erasure lane's sessionId-selector resolver). Throws on a corrupt ledger. Lookups are
|
|
385
|
+
* OWN-property (the twin law {@link lineageAccountOfEntry} states): a prototype-member sessionId
|
|
386
|
+
* such as "constructor" indexes truthy on EVERY plain object, so a bare lookup would attribute
|
|
387
|
+
* the WHOLE committed set to it — behind the erasure selector that phantom is a whole-store
|
|
388
|
+
* delete, the exact shape the selector type forbids. Belt over the null-proto parse rebuild:
|
|
389
|
+
* this guard stays honest even against a map minted elsewhere. */
|
|
385
390
|
export declare function lineageContributionsOfSession(controlDir: string, sessionId: string): Array<{
|
|
386
391
|
entryId: string;
|
|
387
392
|
lastRev: string;
|
|
388
393
|
}>;
|
|
394
|
+
/** design/178 v2-a §1.3 — the BY-ENTRY lineage account, from ONE parse of the ledger (the
|
|
395
|
+
* by-session read's transpose; the committed set is keyed by entryId, so the index is O(1) after
|
|
396
|
+
* the load — the load itself still parses and validates the whole account). BOTH halves come from
|
|
397
|
+
* the same read on purpose (adversarial r3): contributors (committed rows; pending rows are NOT
|
|
398
|
+
* contributions — the account is unsettled) and the dirty-latch fact (any pending row naming this
|
|
399
|
+
* id). Deriving them from two separate reads let a promotion land between them and assemble a
|
|
400
|
+
* scene no instant exhibited — empty ancestry AND no latch. Unknown entry ⇒ empty/false. Throws
|
|
401
|
+
* {@link ControlPlaneCorruptError} on a corrupt ledger — an audit answer is never assembled over
|
|
402
|
+
* an account whose integrity is unknown. Lookups are own-property (a prototype-member entryId
|
|
403
|
+
* such as "constructor" must answer the empty account, never a phantom). */
|
|
404
|
+
export declare function lineageAccountOfEntry(controlDir: string, entryId: string): {
|
|
405
|
+
contributors: Array<{
|
|
406
|
+
sessionId: string;
|
|
407
|
+
lastRev: string;
|
|
408
|
+
lastAt: number;
|
|
409
|
+
}>;
|
|
410
|
+
pendingLatched: boolean;
|
|
411
|
+
};
|
|
389
412
|
/** Drop an entry's lineage rows once the entry is DELETED (an applied tombstone) — space hygiene
|
|
390
413
|
* only, taken at the harvest that saw the applied delete (design/180 §10). */
|
|
391
414
|
export declare function clearLineageForEntries(controlDir: string, entryIds: readonly string[]): void;
|
|
415
|
+
/**
|
|
416
|
+
* design/178 v2-b §7.2-3 — capture-and-clear an erased id set's lineage in ONE strict-lock write:
|
|
417
|
+
* - the COMMITTED contributors of each id are captured (the attestation's testimony) and the rows
|
|
418
|
+
* dropped (the organic tombstone's space hygiene, same terminal state);
|
|
419
|
+
* - every PENDING txn's staged rows naming these ids are STRUCK in the same write — a promotion
|
|
420
|
+
* landing after this lock releases iterates rows the strike already removed, so a late harvest
|
|
421
|
+
* can never write a deleted id's lineage row back (the read and the clear being two separate
|
|
422
|
+
* lock spans was exactly the interleave that both missed a landing contributor and then erased
|
|
423
|
+
* the record of having missed it).
|
|
424
|
+
* Struck pending rows contribute their txn's sessionId to the returned set — EXCEPT rows carrying
|
|
425
|
+
* the structural `kind: "latch-only"` seat (an import protocol's synthetic latch is not a
|
|
426
|
+
* contributing session; the row is still struck). Returns id → contributing session ids
|
|
427
|
+
* (committed ∪ struck). NON-durable by design: a crash between the caller's journal close and this
|
|
428
|
+
* call loses the return set — the caller's replay re-runs it idempotently and its attestation
|
|
429
|
+
* carries the chain's pre-captured set (honest downgrade, never a fabricated rebuild).
|
|
430
|
+
*/
|
|
431
|
+
export declare function captureAndClearLineageForEntries(controlDir: string, entryIds: readonly string[]): Map<string, string[]>;
|
|
392
432
|
/** Full-ledger read (tests / host observability). Throws on corruption. */
|
|
393
433
|
export declare function readLineageRecord(controlDir: string): {
|
|
394
434
|
committed: Record<string, Record<string, LineageContribution>>;
|
|
@@ -375,7 +375,8 @@ export function claimRootScope(controlDir, scope) {
|
|
|
375
375
|
}
|
|
376
376
|
export function registerScope(memoryDir, controlDir, scope, opts) {
|
|
377
377
|
return lockedScopesUpdate(controlDir, (rec) => {
|
|
378
|
-
const
|
|
378
|
+
const registered = rec.scopes !== undefined && Object.prototype.hasOwnProperty.call(rec.scopes, scope) ? rec.scopes[scope] : undefined;
|
|
379
|
+
const dirName = rec.rootScope === scope ? "" : (registered ?? scopeDirName(scope));
|
|
379
380
|
for (const [other, otherDir] of Object.entries(rec.scopes ?? {})) {
|
|
380
381
|
if (other === scope || otherDir === "")
|
|
381
382
|
continue;
|
|
@@ -390,7 +391,7 @@ export function registerScope(memoryDir, controlDir, scope, opts) {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
|
-
if (
|
|
394
|
+
if (registered === undefined) {
|
|
394
395
|
return { next: { ...rec, scopes: { ...rec.scopes, [scope]: dirName } }, result: dirName === "" ? memoryDir : join(memoryDir, dirName) };
|
|
395
396
|
}
|
|
396
397
|
return { result: dirName === "" ? memoryDir : join(memoryDir, dirName) };
|
|
@@ -403,7 +404,7 @@ export function scopeDirFor(memoryDir, controlDir, scope) {
|
|
|
403
404
|
const rec = readScopesRecord(controlDir);
|
|
404
405
|
if (rec.rootScope === scope)
|
|
405
406
|
return memoryDir;
|
|
406
|
-
const registered = rec.scopes
|
|
407
|
+
const registered = rec.scopes !== undefined && Object.prototype.hasOwnProperty.call(rec.scopes, scope) ? rec.scopes[scope] : undefined;
|
|
407
408
|
if (registered !== undefined)
|
|
408
409
|
return registered === "" ? memoryDir : join(memoryDir, registered);
|
|
409
410
|
return join(memoryDir, scopeDirName(scope));
|
|
@@ -769,8 +770,8 @@ export const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
|
|
|
769
770
|
export const USAGE_RETRIEVED_MAX_IDS = 4096;
|
|
770
771
|
function coerceRetrievedAccount(raw) {
|
|
771
772
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
772
|
-
return
|
|
773
|
-
const out =
|
|
773
|
+
return Object.create(null);
|
|
774
|
+
const out = Object.create(null);
|
|
774
775
|
for (const [k, v] of Object.entries(raw)) {
|
|
775
776
|
const row = v;
|
|
776
777
|
if (row && typeof row === "object" && typeof row.count === "number" && Number.isFinite(row.count) && row.count > 0 && typeof row.lastAt === "number" && Number.isFinite(row.lastAt)) {
|
|
@@ -903,8 +904,9 @@ function readStrictSidecar(controlDir, fileName, what) {
|
|
|
903
904
|
return readStrictSidecarRaw(file, what);
|
|
904
905
|
}
|
|
905
906
|
function coerceLineage(raw) {
|
|
906
|
-
if (raw === undefined)
|
|
907
|
-
return { version: 1, committed:
|
|
907
|
+
if (raw === undefined) {
|
|
908
|
+
return { version: 1, committed: Object.create(null), pending: Object.create(null) };
|
|
909
|
+
}
|
|
908
910
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
909
911
|
throw new ControlPlaneCorruptError("memory lineage ledger has the wrong shape");
|
|
910
912
|
const r = raw;
|
|
@@ -942,7 +944,17 @@ function coerceLineage(raw) {
|
|
|
942
944
|
}
|
|
943
945
|
}
|
|
944
946
|
}
|
|
945
|
-
|
|
947
|
+
const committed = Object.create(null);
|
|
948
|
+
for (const [entryId, sessions] of Object.entries(r.committed)) {
|
|
949
|
+
const s = Object.create(null);
|
|
950
|
+
for (const [sid, c] of Object.entries(sessions))
|
|
951
|
+
s[sid] = c;
|
|
952
|
+
committed[entryId] = s;
|
|
953
|
+
}
|
|
954
|
+
const pending = Object.create(null);
|
|
955
|
+
for (const [txnId, t] of Object.entries(r.pending))
|
|
956
|
+
pending[txnId] = t;
|
|
957
|
+
return { version: 1, committed, pending };
|
|
946
958
|
}
|
|
947
959
|
export function stageLineagePending(controlDir, txnId, sessionId, rows, now) {
|
|
948
960
|
lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
|
|
@@ -981,7 +993,7 @@ export function promoteLineagePending(controlDir, txnId, now) {
|
|
|
981
993
|
for (const row of txn.rows) {
|
|
982
994
|
if (!applied.has(row.entryId))
|
|
983
995
|
continue;
|
|
984
|
-
const sessions = (rec.committed[row.entryId] ??=
|
|
996
|
+
const sessions = (rec.committed[row.entryId] ??= Object.create(null));
|
|
985
997
|
sessions[txn.sessionId] = { lastRev: row.rev, lastAt: at };
|
|
986
998
|
promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev });
|
|
987
999
|
}
|
|
@@ -1004,7 +1016,7 @@ export function adjudicateLineagePending(controlDir, txnId, action, now) {
|
|
|
1004
1016
|
for (const row of txn.rows) {
|
|
1005
1017
|
if (applied !== undefined && !applied.has(row.entryId))
|
|
1006
1018
|
continue;
|
|
1007
|
-
const sessions = (rec.committed[row.entryId] ??=
|
|
1019
|
+
const sessions = (rec.committed[row.entryId] ??= Object.create(null));
|
|
1008
1020
|
sessions[txn.sessionId] = { lastRev: row.rev, lastAt: at };
|
|
1009
1021
|
promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev });
|
|
1010
1022
|
}
|
|
@@ -1028,7 +1040,7 @@ export function reconcileLineage(controlDir, now) {
|
|
|
1028
1040
|
if (!applied.has(row.entryId))
|
|
1029
1041
|
continue;
|
|
1030
1042
|
at ??= now();
|
|
1031
|
-
const sessions = (rec.committed[row.entryId] ??=
|
|
1043
|
+
const sessions = (rec.committed[row.entryId] ??= Object.create(null));
|
|
1032
1044
|
sessions[txn.sessionId] = { lastRev: row.rev, lastAt: at };
|
|
1033
1045
|
promoted.push({ entryId: row.entryId, sessionId: txn.sessionId, rev: row.rev });
|
|
1034
1046
|
}
|
|
@@ -1050,12 +1062,25 @@ export function lineageContributionsOfSession(controlDir, sessionId) {
|
|
|
1050
1062
|
const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
|
|
1051
1063
|
const out = [];
|
|
1052
1064
|
for (const [entryId, sessions] of Object.entries(rec.committed)) {
|
|
1053
|
-
const c = sessions[sessionId];
|
|
1065
|
+
const c = Object.prototype.hasOwnProperty.call(sessions, sessionId) ? sessions[sessionId] : undefined;
|
|
1054
1066
|
if (c !== undefined)
|
|
1055
1067
|
out.push({ entryId, lastRev: c.lastRev });
|
|
1056
1068
|
}
|
|
1057
1069
|
return out;
|
|
1058
1070
|
}
|
|
1071
|
+
export function lineageAccountOfEntry(controlDir, entryId) {
|
|
1072
|
+
const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
|
|
1073
|
+
const sessions = Object.prototype.hasOwnProperty.call(rec.committed, entryId) ? rec.committed[entryId] : undefined;
|
|
1074
|
+
const contributors = sessions === undefined ? [] : Object.entries(sessions).map(([sessionId, c]) => ({ sessionId, lastRev: c.lastRev, lastAt: c.lastAt }));
|
|
1075
|
+
let pendingLatched = false;
|
|
1076
|
+
for (const txn of Object.values(rec.pending)) {
|
|
1077
|
+
if (txn.rows.some((r) => r.entryId === entryId)) {
|
|
1078
|
+
pendingLatched = true;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return { contributors, pendingLatched };
|
|
1083
|
+
}
|
|
1059
1084
|
export function clearLineageForEntries(controlDir, entryIds) {
|
|
1060
1085
|
if (entryIds.length === 0)
|
|
1061
1086
|
return;
|
|
@@ -1070,6 +1095,45 @@ export function clearLineageForEntries(controlDir, entryIds) {
|
|
|
1070
1095
|
return { ...(changed ? { next: rec } : {}), result: undefined };
|
|
1071
1096
|
});
|
|
1072
1097
|
}
|
|
1098
|
+
export function captureAndClearLineageForEntries(controlDir, entryIds) {
|
|
1099
|
+
if (entryIds.length === 0)
|
|
1100
|
+
return new Map();
|
|
1101
|
+
return lockedStrictUpdate(controlDir, LINEAGE_FILE, "memory lineage ledger", coerceLineage, (rec) => {
|
|
1102
|
+
const want = new Set(entryIds);
|
|
1103
|
+
const captured = new Map();
|
|
1104
|
+
const add = (id, sessionId) => {
|
|
1105
|
+
const set = captured.get(id) ?? new Set();
|
|
1106
|
+
set.add(sessionId);
|
|
1107
|
+
captured.set(id, set);
|
|
1108
|
+
};
|
|
1109
|
+
let changed = false;
|
|
1110
|
+
for (const id of entryIds) {
|
|
1111
|
+
if (!Object.prototype.hasOwnProperty.call(rec.committed, id))
|
|
1112
|
+
continue;
|
|
1113
|
+
for (const sessionId of Object.keys(rec.committed[id] ?? {}))
|
|
1114
|
+
add(id, sessionId);
|
|
1115
|
+
delete rec.committed[id];
|
|
1116
|
+
changed = true;
|
|
1117
|
+
}
|
|
1118
|
+
for (const txn of Object.values(rec.pending)) {
|
|
1119
|
+
const keep = [];
|
|
1120
|
+
for (const row of txn.rows) {
|
|
1121
|
+
if (!want.has(row.entryId)) {
|
|
1122
|
+
keep.push(row);
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
changed = true;
|
|
1126
|
+
if (row.kind !== "latch-only")
|
|
1127
|
+
add(row.entryId, txn.sessionId);
|
|
1128
|
+
}
|
|
1129
|
+
txn.rows = keep;
|
|
1130
|
+
}
|
|
1131
|
+
const result = new Map();
|
|
1132
|
+
for (const [id, sessions] of captured)
|
|
1133
|
+
result.set(id, [...sessions].sort());
|
|
1134
|
+
return { ...(changed ? { next: rec } : {}), result };
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1073
1137
|
export function readLineageRecord(controlDir) {
|
|
1074
1138
|
const rec = coerceLineage(readStrictSidecar(controlDir, LINEAGE_FILE, "memory lineage ledger"));
|
|
1075
1139
|
return { committed: rec.committed, pending: rec.pending };
|
|
@@ -12,6 +12,19 @@ export interface MemoryBackendContractHooks {
|
|
|
12
12
|
skipCrossInstanceCas?: string;
|
|
13
13
|
/** Wrap each case (vitest `it`, sequential SQL chaining, …). Default: run sequentially. */
|
|
14
14
|
runAssertion?: (name: string, fn: () => Promise<void>) => void | Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* design/178 v2-a §1.3 — the OPTIONAL audit-face clauses' report seat (#196
|
|
17
|
+
* `onOptionalMember` posture: absence is REPORTED, never a silent green — a run without this
|
|
18
|
+
* hook still passes on an absent face, but a certifying harness that wires it can tell
|
|
19
|
+
* "verified" from "the store never offered the face"). The three faces are capability probes on
|
|
20
|
+
* the concrete class (`committedSnapshotOf` / `committedSnapshotsOfScopes` / `custodyOf`); the
|
|
21
|
+
* `MemoryBackend` interface itself is untouched, so absence is legal — the engine's provenance
|
|
22
|
+
* assembly answers it as capability-absent.
|
|
23
|
+
*/
|
|
24
|
+
onOptionalMember?: (report: {
|
|
25
|
+
member: "committedSnapshotOf" | "committedSnapshotsOfScopes" | "custodyOf" | "eraseWithEvidence";
|
|
26
|
+
status: "verified" | "absent";
|
|
27
|
+
}) => void;
|
|
15
28
|
}
|
|
16
29
|
/** Run the full conformance suite. Throws (assert) on the first violated contract clause. */
|
|
17
30
|
export declare function memoryBackendContract(hooks: MemoryBackendContractHooks): Promise<void>;
|
|
@@ -295,6 +295,95 @@ export async function memoryBackendContract(hooks) {
|
|
|
295
295
|
assert.strictEqual(await b.getConsolidationCursor("s1"), "c-2");
|
|
296
296
|
assert.strictEqual(await b.getConsolidationCursor("s2"), "c-other");
|
|
297
297
|
});
|
|
298
|
+
defer("design/178 v2-a optional audit faces: committed snapshot / scope enumeration / custody (absent ⇒ reported)", async () => {
|
|
299
|
+
const b = await hooks.make();
|
|
300
|
+
const seats = [
|
|
301
|
+
["committedSnapshotOf", b.committedSnapshotOf],
|
|
302
|
+
["committedSnapshotsOfScopes", b.committedSnapshotsOfScopes],
|
|
303
|
+
["custodyOf", b.custodyOf],
|
|
304
|
+
];
|
|
305
|
+
for (const [member, seat] of seats) {
|
|
306
|
+
assert.ok(seat === undefined || typeof seat === "function", `${member} must be undefined (absent) or a function — a non-function occupant is a defect, not absence`);
|
|
307
|
+
}
|
|
308
|
+
const snapshotOf = typeof seats[0]?.[1] === "function" ? seats[0][1] : undefined;
|
|
309
|
+
const snapshotsOfScopes = typeof seats[1]?.[1] === "function" ? seats[1][1] : undefined;
|
|
310
|
+
const custodyOf = typeof seats[2]?.[1] === "function" ? seats[2][1] : undefined;
|
|
311
|
+
const e = entry("id-audit-0001", "s1", "audited", "audited body", { name: "Audited" });
|
|
312
|
+
await b.applyPatches([{ op: "add", id: e.id, entry: e }]);
|
|
313
|
+
if (typeof snapshotOf !== "function") {
|
|
314
|
+
hooks.onOptionalMember?.({ member: "committedSnapshotOf", status: "absent" });
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
const ghost = await snapshotOf.call(b, "id-ghost-9999");
|
|
318
|
+
assert.strictEqual(ghost.state, "absent", "an unknown id answers state 'absent' — never a throw, never a guess");
|
|
319
|
+
const snap = await snapshotOf.call(b, e.id);
|
|
320
|
+
assert.strictEqual(snap.state, "row");
|
|
321
|
+
if (snap.state === "row") {
|
|
322
|
+
assert.strictEqual(snap.rev, e.rev, "the snapshot's rev is the committed rev");
|
|
323
|
+
assert.strictEqual(snap.binding.state, "bound", "a committed add answers a bound row");
|
|
324
|
+
if (snap.binding.state === "bound") {
|
|
325
|
+
assert.strictEqual(snap.binding.scope, "s1");
|
|
326
|
+
assert.strictEqual(snap.binding.slug, "audited");
|
|
327
|
+
}
|
|
328
|
+
assert.strictEqual(snap.content.state, "present", "a committed, in-place entry's content is servable");
|
|
329
|
+
if (snap.content.state === "present")
|
|
330
|
+
assert.strictEqual(snap.content.entry.rev, e.rev);
|
|
331
|
+
}
|
|
332
|
+
hooks.onOptionalMember?.({ member: "committedSnapshotOf", status: "verified" });
|
|
333
|
+
}
|
|
334
|
+
if (typeof snapshotsOfScopes !== "function") {
|
|
335
|
+
hooks.onOptionalMember?.({ member: "committedSnapshotsOfScopes", status: "absent" });
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
const enumerated = await snapshotsOfScopes.call(b, ["s1"]);
|
|
339
|
+
if (!enumerated.complete)
|
|
340
|
+
assert.fail("a quiet store enumerates complete");
|
|
341
|
+
assert.deepStrictEqual(enumerated.rows.map((r) => r.id), [e.id], "the account-driven enumeration answers the committed row");
|
|
342
|
+
const foreign = await snapshotsOfScopes.call(b, ["s-elsewhere"]);
|
|
343
|
+
if (!foreign.complete)
|
|
344
|
+
assert.fail("a quiet store enumerates complete (foreign scope)");
|
|
345
|
+
assert.deepStrictEqual(foreign.rows, [], "a foreign scope enumerates empty, not the whole store");
|
|
346
|
+
hooks.onOptionalMember?.({ member: "committedSnapshotsOfScopes", status: "verified" });
|
|
347
|
+
}
|
|
348
|
+
if (typeof custodyOf !== "function") {
|
|
349
|
+
hooks.onOptionalMember?.({ member: "custodyOf", status: "absent" });
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const custody = await custodyOf.call(b, e.id);
|
|
353
|
+
assert.ok(custody.state === "complete" || custody.state === "damaged", "custody state is the two-valued store answer");
|
|
354
|
+
assert.ok(Array.isArray(custody.events));
|
|
355
|
+
for (const ev of custody.events) {
|
|
356
|
+
assert.strictEqual(typeof ev.ev, "string");
|
|
357
|
+
assert.strictEqual(typeof ev.channel, "string");
|
|
358
|
+
assert.strictEqual(typeof ev.at, "number");
|
|
359
|
+
}
|
|
360
|
+
hooks.onOptionalMember?.({ member: "custodyOf", status: "verified" });
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
defer("design/178 v2-b optional erasure face: evidenced delete / replay pinning / selector-swap refusal (absent ⇒ reported)", async () => {
|
|
364
|
+
const b = await hooks.make();
|
|
365
|
+
const seat = b.eraseWithEvidence;
|
|
366
|
+
assert.ok(seat === undefined || typeof seat === "function", "eraseWithEvidence must be undefined (absent) or a function — a non-function occupant is a defect, not absence");
|
|
367
|
+
if (typeof seat !== "function") {
|
|
368
|
+
hooks.onOptionalMember?.({ member: "eraseWithEvidence", status: "absent" });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const erase = seat;
|
|
372
|
+
const e = entry("id-erase-0001", "s1", "erasable", "erasable body", { name: "Erasable" });
|
|
373
|
+
await b.applyPatches([{ op: "add", id: e.id, entry: e }]);
|
|
374
|
+
const att = await erase.call(b, { requestId: "req-contract-1", select: { ids: [e.id, "id-ghost-8888"] } });
|
|
375
|
+
assert.strictEqual(att.v, 1, "the attestation rides the v:1 envelope");
|
|
376
|
+
assert.strictEqual(att.evidenceCapability, "journal", "a face-claiming backend answers the evidenced capability");
|
|
377
|
+
assert.deepStrictEqual(att.erased.map((r) => r.id), [e.id], "the existing id is erased");
|
|
378
|
+
assert.ok(typeof att.erased[0]?.evidenceEv === "string" && att.erased[0].evidenceEv.length > 0, "an evidenced erase names its evidence row");
|
|
379
|
+
assert.deepStrictEqual(att.notFound.map((r) => r.id), ["id-ghost-8888"], "the unknown id answers notFound — never a throw, never a silent drop");
|
|
380
|
+
assert.deepStrictEqual(await b.getByIds([e.id]), [], "an erased id is gone from the read faces");
|
|
381
|
+
const replay = await erase.call(b, { requestId: "req-contract-1", select: { ids: [e.id, "id-ghost-8888"] } });
|
|
382
|
+
assert.deepStrictEqual(replay.resolvedIds, att.resolvedIds, "a replay answers the FIRST resolution's pinned set");
|
|
383
|
+
assert.deepStrictEqual((replay.erasedPreviously ?? []).map((r) => r.id), [e.id], "a replay claims the prior erase as erasedPreviously, never a second erase");
|
|
384
|
+
await assert.rejects(async () => erase.call(b, { requestId: "req-contract-1", select: { ids: ["id-other-7777"] } }), (err) => err.code === "memory.erasure_selector_mismatch", "a reused requestId with a different selector refuses loudly");
|
|
385
|
+
hooks.onOptionalMember?.({ member: "eraseWithEvidence", status: "verified" });
|
|
386
|
+
});
|
|
298
387
|
if (!hooks.makeSibling) {
|
|
299
388
|
const reason = hooks.skipCrossInstanceCas?.trim();
|
|
300
389
|
if (reason) {
|
|
@@ -66,10 +66,10 @@ export declare const MCP_NAMESPACE: ProtocolNamespace<typeof MCP_PREFIX_NAME>;
|
|
|
66
66
|
/**
|
|
67
67
|
* The A2A tool namespace (roadmap 2026-08-03, appended for the A2A client/server work).
|
|
68
68
|
*
|
|
69
|
-
* NAME LAYER ONLY: this entry decides how an A2A tool name is SPELLED and how it decomposes.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* NAME LAYER ONLY: this entry decides how an A2A tool name is SPELLED and how it decomposes. The
|
|
70
|
+
* client leg (`a2a.ts`, `materializeA2aTools`) mints live names in this namespace since v5.3.0; the
|
|
71
|
+
* table entry predates it so that every table-driven site (`protocolOf`, the grouped display key, the
|
|
72
|
+
* `ToolOrigin` union) needed no change when the leg landed — and needs none for the server-side leg.
|
|
73
73
|
*/
|
|
74
74
|
export declare const A2A_NAMESPACE: ProtocolNamespace<typeof A2A_PREFIX_NAME>;
|
|
75
75
|
/** Every protocol namespace the engine knows. APPEND-ONLY: adding a protocol must not change how any
|
|
@@ -128,6 +128,11 @@ export interface ResultFlags {
|
|
|
128
128
|
* spawner folds it stricter-wins into follow-on legs; every other consumer may ignore it. */
|
|
129
129
|
effectiveReadFace?: TaskResult["effectiveReadFace"];
|
|
130
130
|
effectiveReadDenyPatterns?: TaskResult["effectiveReadDenyPatterns"];
|
|
131
|
+
/** design/178 v2 §2.3 (件①) — the memory-visibility observation, echoed on
|
|
132
|
+
* `TaskResult.effectiveMemoryScopes`. Pure pass-through on every terminal, same law as the
|
|
133
|
+
* read-posture seats above: present iff prepare completed (the memory-less states are their own
|
|
134
|
+
* values — absence means only "prepare never ran to completion"). */
|
|
135
|
+
effectiveMemoryScopes?: TaskResult["effectiveMemoryScopes"];
|
|
131
136
|
/** ruled 2026-08-04 — the usage-governance wait hint carried by the platform terminal the run adopted
|
|
132
137
|
* (`undefined` for every other cause: an expiring environment has no return time to give, and a store
|
|
133
138
|
* failure is not a window). Passed as data rather than read back off `threw` so the seat has a typed
|
|
@@ -162,5 +162,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
162
162
|
void _internalCompaction;
|
|
163
163
|
if (flags.unpricedSpend)
|
|
164
164
|
delete publicStats.costMicroUsd;
|
|
165
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), stats: publicStats };
|
|
165
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), stats: publicStats };
|
|
166
166
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BeforeWriteHook, RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
|
|
1
|
+
import type { BeforeWriteHook, EffectiveMemoryScopes, RunnerDeps, TaskSpec, ToolSpec } from "../types.js";
|
|
2
2
|
import type { Prepared } from "./prepare-task.js";
|
|
3
3
|
export interface PrepareMemoryInput {
|
|
4
4
|
spec: TaskSpec;
|
|
@@ -106,5 +106,15 @@ export interface PrepareMemoryResult {
|
|
|
106
106
|
/** The session's own frozen verdict to persist at suspend — present iff this leg ADJUDICATED an
|
|
107
107
|
* org plane (including adjudicated-empty); `undefined` for an org-less task. */
|
|
108
108
|
ownOrgVerdict: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* design/178 v2 §2.3 (件①) — the memory-visibility OBSERVATION this phase minted for
|
|
111
|
+
* `TaskResult.effectiveMemoryScopes` (see {@link EffectiveMemoryScopes} for the state law).
|
|
112
|
+
* ALWAYS present when the phase returns: the memory-less outcomes are their own values
|
|
113
|
+
* (`none` / `memoryless`), never an absent seat — absence on the RESULT means only "prepare
|
|
114
|
+
* never completed" (this phase threw). Minted AFTER the materialize outcome (the fail-open arm
|
|
115
|
+
* sits between the admission verdict and the mount — an earlier stamp would report a mount that
|
|
116
|
+
* never happened); the deliberate-refusal `config.memory_*` throws produce no value at all.
|
|
117
|
+
*/
|
|
118
|
+
effectiveMemoryScopes: EffectiveMemoryScopes;
|
|
109
119
|
}
|
|
110
120
|
export declare function prepareMemory(input: PrepareMemoryInput): Promise<PrepareMemoryResult>;
|
|
@@ -12,12 +12,16 @@ export async function prepareMemory(input) {
|
|
|
12
12
|
const memorySpec = normalizeMemorySpec(spec.memory);
|
|
13
13
|
let admittedOrgScopes = [];
|
|
14
14
|
let ownOrgVerdict;
|
|
15
|
+
const deploymentScopeSet = new Set(deps.deploymentMemoryScopes ?? []);
|
|
16
|
+
const scopeOriginsSnapshot = memorySpec?.scopeOrigins !== undefined ? { ...memorySpec.scopeOrigins } : undefined;
|
|
17
|
+
if (memorySpec && scopeOriginsSnapshot !== undefined)
|
|
18
|
+
memorySpec.scopeOrigins = scopeOriginsSnapshot;
|
|
15
19
|
if (memorySpec && memorySpec.enabled && deps.memoryBackend) {
|
|
16
20
|
const outcome = await admitMemoryScopes({
|
|
17
21
|
memorySpec,
|
|
18
22
|
principal: spec.principal,
|
|
19
23
|
admission: deps.memoryScopeAdmission,
|
|
20
|
-
deploymentScopes:
|
|
24
|
+
deploymentScopes: deploymentScopeSet,
|
|
21
25
|
orgMemoryDenied: admissionCtx.orgMemoryDenied,
|
|
22
26
|
complianceDegraded: admissionCtx.complianceDegraded,
|
|
23
27
|
parentAdmittedOrgScopes: admissionCtx.parentAdmittedOrgScopes,
|
|
@@ -38,6 +42,15 @@ export async function prepareMemory(input) {
|
|
|
38
42
|
const useMemoryEngine = Boolean(memorySpec && memorySpec.enabled && deps.memoryBackend);
|
|
39
43
|
let memoryEngineSession;
|
|
40
44
|
let memoryTools;
|
|
45
|
+
let effectiveMemoryScopes;
|
|
46
|
+
const originOf = (scope) => scopeOriginsSnapshot !== undefined
|
|
47
|
+
? scopeOriginsSnapshot[scope] === "deployment"
|
|
48
|
+
? "deployment"
|
|
49
|
+
: "request"
|
|
50
|
+
: deploymentScopeSet.has(scope)
|
|
51
|
+
? "deployment"
|
|
52
|
+
: "request";
|
|
53
|
+
const materializedResidue = [];
|
|
41
54
|
if (useMemoryEngine && memorySpec) {
|
|
42
55
|
try {
|
|
43
56
|
const backend = deps.memoryBackend;
|
|
@@ -133,6 +146,12 @@ export async function prepareMemory(input) {
|
|
|
133
146
|
b)
|
|
134
147
|
: (b.retrievalView?.() ?? b);
|
|
135
148
|
const planeScopes = (scopes, write) => [...new Set([...scopes, ...(write !== null ? [write] : [])])];
|
|
149
|
+
const mountedScopeRows = (dual
|
|
150
|
+
? [
|
|
151
|
+
...planeScopes(planes.project, planes.writePlane === "project" ? memorySpec.writeScope : null),
|
|
152
|
+
...planeScopes(planes.personal, planes.writePlane === "personal" ? memorySpec.writeScope : null),
|
|
153
|
+
]
|
|
154
|
+
: planeScopes(memorySpec.scopes, memorySpec.writeScope)).map((scope) => ({ scope, origin: originOf(scope) }));
|
|
136
155
|
const pollutedOpts = (engine) => {
|
|
137
156
|
const rec = engine.sessionPollution(sessionId);
|
|
138
157
|
return rec !== undefined ? { polluted: { reason: rec.reason } } : {};
|
|
@@ -179,7 +198,9 @@ export async function prepareMemory(input) {
|
|
|
179
198
|
const personalEngine = personal.engine;
|
|
180
199
|
const p = planes;
|
|
181
200
|
const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted });
|
|
201
|
+
materializedResidue.push(...planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null));
|
|
182
202
|
const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted });
|
|
203
|
+
materializedResidue.push(...planeScopes(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null));
|
|
183
204
|
const writeIsPersonal = p.writePlane === "personal";
|
|
184
205
|
writeEngine = writeIsPersonal ? personalEngine : projectEngine;
|
|
185
206
|
writeHandle = writeIsPersonal ? personalHandle : projectHandle;
|
|
@@ -222,6 +243,7 @@ export async function prepareMemory(input) {
|
|
|
222
243
|
const personal = createPersonalEngine(choosePersonalBackend());
|
|
223
244
|
const personalEngine = personal.engine;
|
|
224
245
|
const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted });
|
|
246
|
+
materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
|
|
225
247
|
writeEngine = personalEngine;
|
|
226
248
|
writeHandle = handle;
|
|
227
249
|
injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted });
|
|
@@ -243,6 +265,7 @@ export async function prepareMemory(input) {
|
|
|
243
265
|
onIncident: onEngineIncident,
|
|
244
266
|
});
|
|
245
267
|
const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted });
|
|
268
|
+
materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
|
|
246
269
|
writeEngine = engine;
|
|
247
270
|
writeHandle = handle;
|
|
248
271
|
injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted });
|
|
@@ -311,6 +334,12 @@ export async function prepareMemory(input) {
|
|
|
311
334
|
execIsExternalContent: memorySpec.execIsExternalContent === true,
|
|
312
335
|
},
|
|
313
336
|
};
|
|
337
|
+
effectiveMemoryScopes = {
|
|
338
|
+
state: "mounted",
|
|
339
|
+
contract: memorySpec.scopeContract === "v2" ? "v2" : "legacy",
|
|
340
|
+
scopes: mountedScopeRows,
|
|
341
|
+
writeScope: memorySpec.writeScope,
|
|
342
|
+
};
|
|
314
343
|
if (input.memorySearchToolsPlanned)
|
|
315
344
|
memoryTools = createMemoryEngineTools({ planes: toolPlanes });
|
|
316
345
|
}
|
|
@@ -322,6 +351,16 @@ export async function prepareMemory(input) {
|
|
|
322
351
|
throw err;
|
|
323
352
|
}
|
|
324
353
|
deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "memory", sessionId });
|
|
354
|
+
if (effectiveMemoryScopes === undefined) {
|
|
355
|
+
effectiveMemoryScopes = {
|
|
356
|
+
state: "memoryless",
|
|
357
|
+
reason: "mount-failed",
|
|
358
|
+
contract: memorySpec.scopeContract === "v2" ? "v2" : "legacy",
|
|
359
|
+
scopes: [],
|
|
360
|
+
writeScope: null,
|
|
361
|
+
...(materializedResidue.length > 0 ? { materializedResidue: [...new Set(materializedResidue)] } : {}),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
325
364
|
}
|
|
326
365
|
}
|
|
327
366
|
let memoryBlock;
|
|
@@ -359,5 +398,12 @@ export async function prepareMemory(input) {
|
|
|
359
398
|
if (memoryBlock !== undefined && injection.indexSeed !== undefined && input.rosterCanPersist)
|
|
360
399
|
seedFiles = [injection.indexSeed];
|
|
361
400
|
}
|
|
362
|
-
|
|
401
|
+
if (effectiveMemoryScopes === undefined) {
|
|
402
|
+
effectiveMemoryScopes = memorySpec
|
|
403
|
+
? memorySpec.enabled
|
|
404
|
+
? { state: "memoryless", reason: "no-backend", contract: memorySpec.scopeContract === "v2" ? "v2" : "legacy", scopes: [], writeScope: null }
|
|
405
|
+
: { state: "none", reason: "disabled", scopes: [], writeScope: null }
|
|
406
|
+
: { state: "none", reason: "no-spec", scopes: [], writeScope: null };
|
|
407
|
+
}
|
|
408
|
+
return { memoryEngineSession, memoryBlock, admittedOrgScopes, ownOrgVerdict, effectiveMemoryScopes, ...(memoryTools !== undefined ? { memoryTools } : {}), ...(seedFiles !== undefined ? { seedFiles } : {}) };
|
|
363
409
|
}
|
|
@@ -205,6 +205,11 @@ export interface Prepared {
|
|
|
205
205
|
* excluded), echoed on `TaskResult.effectiveReadDenyPatterns`. Present iff non-empty; a defensive
|
|
206
206
|
* copy (the wide-scope working array stays the engine's own). */
|
|
207
207
|
effectiveReadDenyPatterns?: readonly import("../../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
|
|
208
|
+
/** design/178 v2 §2.3 (件①) — the memory-visibility observation prepareMemory minted (echoed on
|
|
209
|
+
* `TaskResult.effectiveMemoryScopes`). Always present on a completed prepare (the memory-less
|
|
210
|
+
* states are their own values); the seat is optional only so a Prepared shape without the phase
|
|
211
|
+
* cannot fabricate one. */
|
|
212
|
+
effectiveMemoryScopes?: import("../types.js").EffectiveMemoryScopes;
|
|
208
213
|
/** design/99 §E13 — the per-task logical cwd ref when a real shell is mounted (else undefined). The Runner
|
|
209
214
|
* reads `cwdRef.current` after each tool to detect a `cd` move and emit `workspace_changed`. */
|
|
210
215
|
cwdRef?: CwdRef;
|
|
@@ -1555,7 +1555,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1555
1555
|
const memoryPairExcludedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => (toolFaceSnapshot.exclude ?? []).includes(n));
|
|
1556
1556
|
const memoryPairOccupiedName = MEMORY_ENGINE_TOOL_NAMES.find((n) => memoryPairNameDomain.has(n));
|
|
1557
1557
|
const memorySearchToolsPlanned = memoryPairExcludedName === undefined && memoryPairOccupiedName === undefined;
|
|
1558
|
-
const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, } = await prepareMemory({
|
|
1558
|
+
const { memoryEngineSession, memoryBlock: memoryBlockFromEngine, memoryTools, seedFiles: memorySeedFiles, admittedOrgScopes: memoryAdmittedOrgScopes, ownOrgVerdict: memoryOwnOrgVerdict, effectiveMemoryScopes: memoryEffectiveScopes, } = await prepareMemory({
|
|
1559
1559
|
spec,
|
|
1560
1560
|
deps,
|
|
1561
1561
|
sessionId,
|
|
@@ -4586,7 +4586,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
4586
4586
|
const effectiveReadFaceObserved = carrierReadFace();
|
|
4587
4587
|
const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
|
|
4588
4588
|
const preparedHolder = {};
|
|
4589
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4589
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
4590
4590
|
const prepared = buildPrepared();
|
|
4591
4591
|
preparedHolder.current = prepared;
|
|
4592
4592
|
return prepared;
|
|
@@ -1659,6 +1659,7 @@ export class Runner {
|
|
|
1659
1659
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1660
1660
|
errorCode: code,
|
|
1661
1661
|
...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
|
|
1662
|
+
...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
|
|
1662
1663
|
...(() => {
|
|
1663
1664
|
const hinted = err.retryAfterMs;
|
|
1664
1665
|
return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
|
|
@@ -2029,6 +2030,8 @@ export class Runner {
|
|
|
2029
2030
|
}, this);
|
|
2030
2031
|
notificationHarness = prepared.harness;
|
|
2031
2032
|
notificationSessionId = prepared.sessionId;
|
|
2033
|
+
if (taskIdRef)
|
|
2034
|
+
taskIdRef.effectiveMemoryScopes = prepared.effectiveMemoryScopes;
|
|
2032
2035
|
const runSourceTaskId = spec.taskId ?? prepared.sessionId;
|
|
2033
2036
|
const parentToolCallId = internals?.parentToolCallId;
|
|
2034
2037
|
const ident = () => parentToolCallId !== undefined ? { eventId: uuidv7(), parentToolCallId, sourceTaskId: runSourceTaskId } : { eventId: uuidv7() };
|
|
@@ -3426,6 +3429,7 @@ export class Runner {
|
|
|
3426
3429
|
remoteEnvFailures: prepared.remoteEnvFailures,
|
|
3427
3430
|
effectiveReadFace: prepared.effectiveReadFace,
|
|
3428
3431
|
effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
|
|
3432
|
+
effectiveMemoryScopes: prepared.effectiveMemoryScopes,
|
|
3429
3433
|
retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
|
|
3430
3434
|
abortedForTimeout: timeout.fired,
|
|
3431
3435
|
abortedForTurns: rs.limits.turnsExceeded,
|