@sema-agent/core 5.35.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 +115 -0
- package/dist/agents/subagent.d.ts +10 -0
- package/dist/agents/subagent.js +29 -2
- package/dist/core/auto-compaction.d.ts +23 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/checkpoint-store.d.ts +16 -0
- package/dist/core/context-guard.d.ts +41 -0
- package/dist/core/context-guard.js +76 -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 +265 -3
- 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/park-selfcheck.d.ts +5 -0
- package/dist/core/protocol-table.d.ts +4 -4
- package/dist/core/runner/assemble-result.d.ts +8 -0
- package/dist/core/runner/assemble-result.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +219 -0
- package/dist/core/runner/git-status-frame.js +212 -0
- 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 +21 -0
- package/dist/core/runner/prepare-task.js +28 -35
- package/dist/core/runner/runtask.js +270 -5
- package/dist/core/task-registry-agent.d.ts +15 -0
- package/dist/core/task-registry-agent.js +9 -0
- package/dist/core/task-registry.d.ts +3 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/types.d.ts +122 -7
- package/dist/engine/harness/types.d.ts +65 -1
- package/dist/engine/harness/types.js +20 -0
- package/dist/engine/session/import-validate.js +10 -1
- package/dist/engine/session/session.d.ts +37 -1
- package/dist/engine/session/session.js +56 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/internal/harness.d.ts +2 -0
- package/dist/internal/harness.js +2 -0
- package/dist/prompt-assembly/epoch.js +1 -1
- package/dist/prompt-assembly/event-registry.js +1 -0
- package/dist/prompts/default.d.ts +20 -7
- package/dist/prompts/default.js +2 -7
- 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) {
|
|
@@ -152,5 +152,10 @@ export declare function durableParkGapFor(deps: Pick<RunnerDeps, "checkpointStor
|
|
|
152
152
|
* deployment's, deliberately — this module reports, loudly and in a closed vocabulary, and does not
|
|
153
153
|
* legislate. What it will NOT do is report a pass it did not observe: `"round_trip_ok"` is returned only
|
|
154
154
|
* with an empty findings list, and the invariant is asserted below rather than left to reading.
|
|
155
|
+
*
|
|
156
|
+
* This is a DIAGNOSTIC probe, not a health-poll body: every run files (and then fences) one real
|
|
157
|
+
* synthetic row under the reserved scope prefix. Mounted on a high-frequency health endpoint it
|
|
158
|
+
* becomes write amplification against the checkpoint store — run it at startup, on demand, or on a
|
|
159
|
+
* coarse operator cadence, not per request.
|
|
155
160
|
*/
|
|
156
161
|
export declare function probeParkRoundTrip(deps: Pick<RunnerDeps, "checkpointStore" | "sessionStore">, spec?: Pick<TaskSpec, "checkpointStore">): Promise<ParkSelfCheckResult>;
|
|
@@ -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
|
|
@@ -149,6 +154,9 @@ export interface ResultFlags {
|
|
|
149
154
|
budgetAxis?: BudgetAxis;
|
|
150
155
|
blockedReason?: string;
|
|
151
156
|
conflict?: boolean;
|
|
157
|
+
/** env-tail migration F5: the request-build guard threw the irreducible-core terminal this run —
|
|
158
|
+
* the loop folded it into a text-only failure message, so the typed code rides this flag. */
|
|
159
|
+
gitCoreOverBudget?: boolean;
|
|
152
160
|
outputInvalid?: boolean;
|
|
153
161
|
/** design/72 §2.2 (B): a re-suspend was refused because the task already suspended `maxSuspends` times
|
|
154
162
|
* (a resume/restart loop). It aborted the run (no `threw`) but must read as `failed`/`suspend.loop`,
|
|
@@ -141,6 +141,9 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
141
141
|
errorCode = lifted;
|
|
142
142
|
errorMessage = stripErrorCodePrefix(errorMessage);
|
|
143
143
|
}
|
|
144
|
+
else if (flags.gitCoreOverBudget) {
|
|
145
|
+
errorCode = "irreducible_core_over_budget";
|
|
146
|
+
}
|
|
144
147
|
else if (flags.conflict) {
|
|
145
148
|
errorCode = "conflict";
|
|
146
149
|
}
|
|
@@ -159,5 +162,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
159
162
|
void _internalCompaction;
|
|
160
163
|
if (flags.unpricedSpend)
|
|
161
164
|
delete publicStats.costMicroUsd;
|
|
162
|
-
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 };
|
|
163
166
|
}
|