@sema-agent/core 5.54.0 → 5.55.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 +94 -0
- package/dist/agents/cumulative-stats.d.ts +26 -0
- package/dist/agents/cumulative-stats.js +56 -0
- package/dist/agents/observer.d.ts +11 -7
- package/dist/agents/observer.js +2 -4
- package/dist/agents/verify.d.ts +27 -3
- package/dist/agents/verify.js +7 -2
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.js +1 -1
- package/dist/core/lsp-diagnostics.d.ts +19 -17
- package/dist/core/lsp-diagnostics.js +11 -5
- package/dist/core/mcp.d.ts +46 -0
- package/dist/core/mcp.js +132 -6
- package/dist/core/memory-engine/consolidation.d.ts +378 -0
- package/dist/core/memory-engine/consolidation.js +342 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +237 -4
- package/dist/core/memory-engine/engine.js +1111 -4
- package/dist/core/memory-engine/export-bundle.js +9 -0
- package/dist/core/memory-engine/file-backend.js +27 -1
- package/dist/core/memory-engine/frontmatter.d.ts +20 -1
- package/dist/core/memory-engine/frontmatter.js +111 -0
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +3 -1
- package/dist/core/memory-engine/memory-backend-contract.js +131 -0
- package/dist/core/memory-engine/sync-client.js +26 -0
- package/dist/core/memory-engine/tools.d.ts +9 -0
- package/dist/core/memory-engine/tools.js +57 -13
- package/dist/core/memory-engine/types.d.ts +99 -0
- package/dist/core/memory-recall.js +4 -3
- package/dist/core/memory.d.ts +33 -3
- package/dist/core/memory.js +6 -4
- package/dist/core/permission-rules.d.ts +22 -0
- package/dist/core/permission-rules.js +60 -6
- package/dist/core/reminder-disclosure.d.ts +29 -4
- package/dist/core/reminder-disclosure.js +60 -12
- package/dist/core/runner/prepare-memory.js +7 -2
- package/dist/core/runner/prepare-task.d.ts +31 -1
- package/dist/core/runner/prepare-task.js +31 -14
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +12 -10
- package/dist/core/runner/session-rule-policy.js +5 -3
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/turn-attachments.d.ts +16 -6
- package/dist/core/runner/turn-attachments.js +34 -20
- package/dist/core/tool-policy.d.ts +18 -0
- package/dist/core/tool-policy.js +19 -8
- package/dist/core/types.d.ts +89 -6
- package/dist/core/untrusted-egress.js +12 -2
- package/dist/core/untrusted-text.d.ts +189 -3
- package/dist/core/untrusted-text.js +416 -6
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prompts/default.d.ts +12 -2
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +28 -1
|
@@ -77,6 +77,15 @@ function entryInvalid(raw, scopes) {
|
|
|
77
77
|
return `entry ${JSON.stringify(e.id)} frontmatter.origin is malformed`;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
if (fm.distilled !== undefined) {
|
|
81
|
+
const d = fm.distilled;
|
|
82
|
+
const inputsOk = (v) => Array.isArray(v) &&
|
|
83
|
+
v.length > 0 &&
|
|
84
|
+
v.every((row) => isRecord(row) && typeof row.id === "string" && row.id.length > 0 && typeof row.rev === "string" && row.rev.length > 0 && (row.superseded === undefined || row.superseded === true));
|
|
85
|
+
if (!isRecord(d) || typeof d.planId !== "string" || d.planId.length === 0 || typeof d.at !== "number" || !Number.isFinite(d.at) || typeof d.carrierRev !== "string" || d.carrierRev.length === 0 || !inputsOk(d.inputs)) {
|
|
86
|
+
return `entry ${JSON.stringify(e.id)} frontmatter.distilled is malformed`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
80
89
|
if (ambiguousOriginRepresentation(fm)) {
|
|
81
90
|
return `entry ${JSON.stringify(e.id)} carries conflicting or duplicated origin representations (ambiguous marker representation)`;
|
|
82
91
|
}
|
|
@@ -4,7 +4,7 @@ import { dirname, join, relative } from "node:path";
|
|
|
4
4
|
import { jaccardDistance, termSet } from "../memory-vector.js";
|
|
5
5
|
import { MAX_MEMORY_BYTES } from "../memory.js";
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
|
-
import { ambiguousOriginRepresentation, committedOriginOf, computeEntryRev, entryFromFile, isValidEntryId, originEquals, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
7
|
+
import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf, computeEntryRev, distilledEquals, entryFromFile, isValidEntryId, originEquals, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
8
|
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, CHALLENGES_FILE, IMPORT_RECEIPTS_DIR, LINEAGE_FILE, SESSION_POLLUTION_DIR, appendChallengeEvents, atomicWriteFileSync, quarantineAndTombstone, captureAndClearLineageForEntries, claimRootScope, adoptCanonicalKeyedControlDir, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, importLatchTxnId, importLineageCommitted, importSessionPollution, isContainedIn, lineageContributionsOfSession, listSessionPollution, readChallengeEvents, readLineageRecord, registerScope, registeredScopes, releaseImportLatch, resolveMemoryEngineRoot, scopeDirFor, scopeDirName, stageImportLatch, writeAllSync, } from "./layout.js";
|
|
9
9
|
import { screenInboundEntries } from "./data-plane.js";
|
|
10
10
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
@@ -2403,6 +2403,15 @@ export class FileMemoryEngineBackend {
|
|
|
2403
2403
|
});
|
|
2404
2404
|
return;
|
|
2405
2405
|
}
|
|
2406
|
+
const committedDistilled = committedDistilledOf(committedFm);
|
|
2407
|
+
if (committedDistilled !== undefined && !distilledEquals(committedDistilled, committedDistilledOf(entry.frontmatter))) {
|
|
2408
|
+
report.conflicts.push({
|
|
2409
|
+
op: "add",
|
|
2410
|
+
id: patch.id,
|
|
2411
|
+
reason: `distilled lineage whitewash refused: the add strips or rewrites the distilled block of a consolidation product (malformed patch refused)`,
|
|
2412
|
+
});
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2406
2415
|
}
|
|
2407
2416
|
if (patch.guard === "absent") {
|
|
2408
2417
|
if (existingAnywhere) {
|
|
@@ -2490,6 +2499,15 @@ export class FileMemoryEngineBackend {
|
|
|
2490
2499
|
});
|
|
2491
2500
|
return;
|
|
2492
2501
|
}
|
|
2502
|
+
const committedDistilled = committedDistilledOf(committedFm);
|
|
2503
|
+
if (committedDistilled !== undefined && !distilledEquals(committedDistilled, committedDistilledOf(patch.entry.frontmatter))) {
|
|
2504
|
+
report.conflicts.push({
|
|
2505
|
+
op: "update",
|
|
2506
|
+
id: patch.id,
|
|
2507
|
+
reason: `distilled lineage whitewash refused: the update strips or rewrites the distilled block of a consolidation product (malformed patch refused)`,
|
|
2508
|
+
});
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2493
2511
|
}
|
|
2494
2512
|
const currentRev = rowsRev(rows, patch.id) ?? found.entry.rev;
|
|
2495
2513
|
if (patch.baseRev !== undefined && patch.baseRev !== currentRev) {
|
|
@@ -3696,6 +3714,14 @@ function headerOf(e, path) {
|
|
|
3696
3714
|
rev: e.rev,
|
|
3697
3715
|
sizeBytes: Buffer.byteLength(serializeEntryFile(e), "utf8"),
|
|
3698
3716
|
...(committedOriginOf(e.frontmatter) !== undefined ? { exposure: "external" } : {}),
|
|
3717
|
+
...(e.frontmatter.distilled !== undefined
|
|
3718
|
+
? {
|
|
3719
|
+
distilled: {
|
|
3720
|
+
carrierRev: e.frontmatter.distilled.carrierRev,
|
|
3721
|
+
supersedes: e.frontmatter.distilled.inputs.filter((i) => i.superseded === true).map((i) => ({ id: i.id, rev: i.rev })),
|
|
3722
|
+
},
|
|
3723
|
+
}
|
|
3724
|
+
: {}),
|
|
3699
3725
|
};
|
|
3700
3726
|
}
|
|
3701
3727
|
function readSafe(path) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryOrigin } from "./types.js";
|
|
1
|
+
import { type MemoryEntry, type MemoryEntryDistilled, type MemoryEntryFrontmatter, type MemoryEntryOrigin } from "./types.js";
|
|
2
2
|
/** A parsed entry file: frontmatter (may be absent — CC allows bare files; harvest mints them) + body. */
|
|
3
3
|
export interface ParsedEntryFile {
|
|
4
4
|
/** The hidden immutable id (`id:` frontmatter line), when present. */
|
|
@@ -92,5 +92,24 @@ export declare function ambiguousOriginRepresentation(fm: Pick<MemoryEntryFrontm
|
|
|
92
92
|
* caller discloses via a report warning).
|
|
93
93
|
*/
|
|
94
94
|
export declare function stripModelWrittenOrigin(fm: MemoryEntryFrontmatter): boolean;
|
|
95
|
+
/** True ⇔ `extra` carries distilled-form bytes (a non-pristine/foreign block preserved verbatim). */
|
|
96
|
+
export declare function hasDistilledFormExtra(fm: Pick<MemoryEntryFrontmatter, "extra">): boolean;
|
|
97
|
+
/** The ONE reading of an entry's committed distilled block: the typed seat or nothing. Extra-form
|
|
98
|
+
* bytes deliberately do NOT answer here (they mint no edge — fewer edges is the safe direction
|
|
99
|
+
* for a suppression carrier). Returns a defensive copy. */
|
|
100
|
+
export declare function committedDistilledOf(fm: Pick<MemoryEntryFrontmatter, "distilled">): MemoryEntryDistilled | undefined;
|
|
101
|
+
/** Deep equality over the distilled block (every member, input order included) — the immutability
|
|
102
|
+
* law's comparator: a rewritten carrierRev, a dropped input row or a flipped superseded flag is as
|
|
103
|
+
* refused as a strip. */
|
|
104
|
+
export declare function distilledEquals(a: MemoryEntryDistilled | undefined, b: MemoryEntryDistilled | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* design/339 §1.4.2-1 — the harvest-side STRIP of model-written distilled bytes (typed field AND
|
|
107
|
+
* distilled-form `extra` lines): the block is minted by the consolidation plan freeze EXCLUSIVELY —
|
|
108
|
+
* whatever a file on the model-writable plane says about its own lineage/supersession is not
|
|
109
|
+
* evidence (a model-writable edge would be the delete-attack channel add-only exists to close).
|
|
110
|
+
* Same seat and same disclosure discipline as {@link stripModelWrittenOrigin}. MUTATES the given
|
|
111
|
+
* (already-copied) frontmatter; returns true ⇔ anything was stripped.
|
|
112
|
+
*/
|
|
113
|
+
export declare function stripModelWrittenDistilled(fm: MemoryEntryFrontmatter): boolean;
|
|
95
114
|
/** Parse a file's text directly into a full MemoryEntry (id must already be present/decided). */
|
|
96
115
|
export declare function entryFromFile(text: string, id: string, slug: string, scope: string): MemoryEntry;
|
|
@@ -37,6 +37,10 @@ export function parseEntryFile(text) {
|
|
|
37
37
|
const originRaw = [];
|
|
38
38
|
let originPristine = true;
|
|
39
39
|
const orig = {};
|
|
40
|
+
let inDistilled = false;
|
|
41
|
+
const distilledRaw = [];
|
|
42
|
+
let distilledPristine = true;
|
|
43
|
+
const dist = { inputs: [] };
|
|
40
44
|
for (let i = 1; i < end; i++) {
|
|
41
45
|
const line = lines[i] ?? "";
|
|
42
46
|
const trimmed = line.trim();
|
|
@@ -47,6 +51,7 @@ export function parseEntryFile(text) {
|
|
|
47
51
|
inMetadata = false;
|
|
48
52
|
inProvenance = false;
|
|
49
53
|
inOrigin = false;
|
|
54
|
+
inDistilled = false;
|
|
50
55
|
}
|
|
51
56
|
if (inMetadata) {
|
|
52
57
|
const m = /^\s+type:\s*(.+?)\s*$/.exec(line);
|
|
@@ -101,6 +106,35 @@ export function parseEntryFile(text) {
|
|
|
101
106
|
}
|
|
102
107
|
continue;
|
|
103
108
|
}
|
|
109
|
+
if (inDistilled) {
|
|
110
|
+
distilledRaw.push(line);
|
|
111
|
+
const m = /^\s+([A-Za-z][A-Za-z0-9_-]*):\s*(.*?)\s*$/.exec(line);
|
|
112
|
+
if (m) {
|
|
113
|
+
const [, k = "", v = ""] = m;
|
|
114
|
+
if (k === "planId" && dist.planId === undefined && /^\S+$/.test(v))
|
|
115
|
+
dist.planId = v;
|
|
116
|
+
else if (k === "at" && dist.at === undefined && ORIGIN_AT_RE.test(v))
|
|
117
|
+
dist.at = Number(v);
|
|
118
|
+
else if (k === "carrierRev" && dist.carrierRev === undefined && /^\S+$/.test(v))
|
|
119
|
+
dist.carrierRev = v;
|
|
120
|
+
else if (k === "input") {
|
|
121
|
+
const parts = v.split(/\s+/);
|
|
122
|
+
const [inId, inRev, flag, ...rest] = parts;
|
|
123
|
+
if (inId !== undefined && isValidEntryId(inId) && inRev !== undefined && /^\S+$/.test(inRev) && rest.length === 0 && (flag === undefined || flag === "superseded")) {
|
|
124
|
+
dist.inputs.push({ id: inId, rev: inRev, ...(flag === "superseded" ? { superseded: true } : {}) });
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
distilledPristine = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
distilledPristine = false;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
distilledPristine = false;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
104
138
|
if (/^provenance:\s*$/.test(trimmed)) {
|
|
105
139
|
inProvenance = true;
|
|
106
140
|
provRaw.push(line);
|
|
@@ -111,6 +145,11 @@ export function parseEntryFile(text) {
|
|
|
111
145
|
originRaw.push(line);
|
|
112
146
|
continue;
|
|
113
147
|
}
|
|
148
|
+
if (/^distilled:\s*$/.test(trimmed) && distilledRaw.length === 0) {
|
|
149
|
+
inDistilled = true;
|
|
150
|
+
distilledRaw.push(line);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
114
153
|
const kv = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*?)\s*$/.exec(trimmed);
|
|
115
154
|
if (!kv || indented) {
|
|
116
155
|
extra.push(line);
|
|
@@ -172,6 +211,14 @@ export function parseEntryFile(text) {
|
|
|
172
211
|
extra.push(...originRaw);
|
|
173
212
|
}
|
|
174
213
|
}
|
|
214
|
+
if (distilledRaw.length > 0) {
|
|
215
|
+
if (distilledPristine && dist.planId !== undefined && typeof dist.at === "number" && atSerializationStable(dist.at) && dist.carrierRev !== undefined && dist.inputs.length > 0) {
|
|
216
|
+
fm.distilled = { planId: dist.planId, at: dist.at, carrierRev: dist.carrierRev, inputs: dist.inputs };
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
extra.push(...distilledRaw);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
175
222
|
if (extra.length > 0)
|
|
176
223
|
fm.extra = extra;
|
|
177
224
|
let body = lines.slice(end + 1).join("\n");
|
|
@@ -200,6 +247,9 @@ export function serializeEntryFile(entry) {
|
|
|
200
247
|
if (fm.origin !== undefined) {
|
|
201
248
|
lines.push("origin:", ` taint: ${fm.origin.taint}`, ...(fm.origin.cause !== undefined ? [` cause: ${fm.origin.cause}`] : []), ` at: ${fm.origin.at}`);
|
|
202
249
|
}
|
|
250
|
+
if (fm.distilled !== undefined) {
|
|
251
|
+
lines.push("distilled:", ` planId: ${fm.distilled.planId}`, ` at: ${fm.distilled.at}`, ` carrierRev: ${fm.distilled.carrierRev}`, ...fm.distilled.inputs.map((i) => ` input: ${i.id} ${i.rev}${i.superseded === true ? " superseded" : ""}`));
|
|
252
|
+
}
|
|
203
253
|
lines.push(FM_FENCE, "");
|
|
204
254
|
const body = entry.body.replace(/\s+$/, "");
|
|
205
255
|
return `${lines.join("\n")}${body}${body ? "\n" : ""}`;
|
|
@@ -220,6 +270,7 @@ export function computeEntryRev(entry) {
|
|
|
220
270
|
? [fm.trust ?? null, fm.provenance !== undefined ? [fm.provenance.kind, fm.provenance.path, fm.provenance.contentHash, fm.provenance.ingestedAt] : null]
|
|
221
271
|
: []),
|
|
222
272
|
...(canonicalOrigin !== undefined ? [["origin", canonicalOrigin.taint, canonicalOrigin.cause ?? null, canonicalOrigin.at]] : []),
|
|
273
|
+
...(fm.distilled !== undefined ? [["distilled", fm.distilled.planId, fm.distilled.at, fm.distilled.inputs.map((i) => [i.id, i.rev, i.superseded === true ? 1 : 0])]] : []),
|
|
223
274
|
]);
|
|
224
275
|
return createHash("sha256").update(canonical, "utf8").digest("hex").slice(0, 16);
|
|
225
276
|
}
|
|
@@ -325,6 +376,66 @@ export function stripModelWrittenOrigin(fm) {
|
|
|
325
376
|
}
|
|
326
377
|
return stripped;
|
|
327
378
|
}
|
|
379
|
+
const DISTILLED_FORM_LINE_RE = /^\s*distilled\s*:/;
|
|
380
|
+
export function hasDistilledFormExtra(fm) {
|
|
381
|
+
return fm.extra !== undefined && fm.extra.some((line) => DISTILLED_FORM_LINE_RE.test(line));
|
|
382
|
+
}
|
|
383
|
+
export function committedDistilledOf(fm) {
|
|
384
|
+
const d = fm.distilled;
|
|
385
|
+
if (d === undefined)
|
|
386
|
+
return undefined;
|
|
387
|
+
return { planId: d.planId, at: d.at, carrierRev: d.carrierRev, inputs: d.inputs.map((i) => ({ id: i.id, rev: i.rev, ...(i.superseded === true ? { superseded: true } : {}) })) };
|
|
388
|
+
}
|
|
389
|
+
export function distilledEquals(a, b) {
|
|
390
|
+
if (a === undefined || b === undefined)
|
|
391
|
+
return a === b;
|
|
392
|
+
if (a.planId !== b.planId || a.at !== b.at || a.carrierRev !== b.carrierRev || a.inputs.length !== b.inputs.length)
|
|
393
|
+
return false;
|
|
394
|
+
for (let i = 0; i < a.inputs.length; i++) {
|
|
395
|
+
const x = a.inputs[i];
|
|
396
|
+
const y = b.inputs[i];
|
|
397
|
+
if (x.id !== y.id || x.rev !== y.rev || (x.superseded === true) !== (y.superseded === true))
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
function partitionDistilledLines(extra) {
|
|
403
|
+
const kept = [];
|
|
404
|
+
let removedAny = false;
|
|
405
|
+
let inBlock = false;
|
|
406
|
+
for (const line of extra) {
|
|
407
|
+
if (DISTILLED_FORM_LINE_RE.test(line)) {
|
|
408
|
+
inBlock = /^\s*distilled\s*:\s*$/.test(line);
|
|
409
|
+
removedAny = true;
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (inBlock && /^\s/.test(line)) {
|
|
413
|
+
removedAny = true;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
inBlock = false;
|
|
417
|
+
kept.push(line);
|
|
418
|
+
}
|
|
419
|
+
return { kept, removedAny };
|
|
420
|
+
}
|
|
421
|
+
export function stripModelWrittenDistilled(fm) {
|
|
422
|
+
let stripped = false;
|
|
423
|
+
if (fm.distilled !== undefined) {
|
|
424
|
+
delete fm.distilled;
|
|
425
|
+
stripped = true;
|
|
426
|
+
}
|
|
427
|
+
if (fm.extra !== undefined) {
|
|
428
|
+
const { kept, removedAny } = partitionDistilledLines(fm.extra);
|
|
429
|
+
if (removedAny) {
|
|
430
|
+
stripped = true;
|
|
431
|
+
if (kept.length > 0)
|
|
432
|
+
fm.extra = kept;
|
|
433
|
+
else
|
|
434
|
+
delete fm.extra;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return stripped;
|
|
438
|
+
}
|
|
328
439
|
export function entryFromFile(text, id, slug, scope) {
|
|
329
440
|
const parsed = parseEntryFile(text);
|
|
330
441
|
const entry = { id, slug, frontmatter: parsed.frontmatter, body: parsed.body, rev: "", scope };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, 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";
|
|
1
|
+
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, 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, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationRefusedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, } from "./engine.js";
|
|
2
2
|
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, type MemoryGetDetails, } from "./tools.js";
|
|
3
3
|
export { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_RECALL_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow, } from "./provenance-wording.js";
|
|
4
4
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
@@ -7,8 +7,10 @@ export { computeBundleSectionHashes, computeMemoryBundleHash, memoryBundleInvali
|
|
|
7
7
|
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";
|
|
8
8
|
export { readV2HeaderHints, isInstructionEntry, type V2HeaderHints } from "./header-hints.js";
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation, type ParsedEntryFile } from "./frontmatter.js";
|
|
10
|
+
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
|
+
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, } from "./consolidation.js";
|
|
10
12
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
11
|
-
export type { MemoryBackend, MemoryEntry, MemoryEntryFrontmatter, MemoryEntryOrigin, MemoryOriginCause, MemoryEntryHeader, ScoredMemoryEntry, NotePatch, PatchReport, MaterializedFile, MemorySessionHandle, HarvestReport, HarvestRejection, HarvestRejectionCode, MemoryAnnouncement, ScanFinding, } from "./types.js";
|
|
13
|
+
export type { MemoryBackend, MemoryEntry, MemoryEntryFrontmatter, MemoryEntryOrigin, MemoryOriginCause, MemoryEntryDistilled, MemoryEntryDistilledInput, MemoryEntryHeader, ScoredMemoryEntry, NotePatch, PatchReport, MaterializedFile, MemorySessionHandle, HarvestReport, HarvestRejection, HarvestRejectionCode, MemoryAnnouncement, ScanFinding, } from "./types.js";
|
|
12
14
|
export { memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, } from "./memory-backend-contract.js";
|
|
13
15
|
export { SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, } from "./scope-contract.js";
|
|
14
16
|
export { migrateScope, type MigrateScopeReport } from "./migrate.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, 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";
|
|
1
|
+
export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, 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, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationRefusedNotice, } from "./engine.js";
|
|
2
2
|
export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, } from "./tools.js";
|
|
3
3
|
export { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_RECALL_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow, } from "./provenance-wording.js";
|
|
4
4
|
export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
|
|
@@ -7,6 +7,8 @@ export { computeBundleSectionHashes, computeMemoryBundleHash, memoryBundleInvali
|
|
|
7
7
|
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";
|
|
8
8
|
export { readV2HeaderHints, isInstructionEntry } from "./header-hints.js";
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation } from "./frontmatter.js";
|
|
10
|
+
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
|
+
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, } from "./consolidation.js";
|
|
10
12
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
11
13
|
export { memoryBackendContract, assertMemoryBackendSearchEquivalence, } from "./memory-backend-contract.js";
|
|
12
14
|
export { SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, } from "./scope-contract.js";
|
|
@@ -513,6 +513,137 @@ export async function memoryBackendContract(hooks) {
|
|
|
513
513
|
assert.deepStrictEqual((await b.getByIds([m0.id]))[0]?.frontmatter.origin, origin);
|
|
514
514
|
assert.strictEqual((await b.getByIds([p0.id]))[0]?.frontmatter.trust, "untrusted");
|
|
515
515
|
});
|
|
516
|
+
const distilledEntry = (id, scope, slug, body, inputs) => {
|
|
517
|
+
const e = { id, scope, slug, frontmatter: { name: slug, distilled: { planId: "plan-c35-0001", at: 1_700_000_000_000, carrierRev: "", inputs } }, body, rev: "" };
|
|
518
|
+
e.rev = computeEntryRev(e);
|
|
519
|
+
e.frontmatter.distilled.carrierRev = e.rev;
|
|
520
|
+
return e;
|
|
521
|
+
};
|
|
522
|
+
defer("design/339 c35: distilled round-trips deep-equal (typed block, superseded rows); rev is conditional — no-block entries keep their historical rev, carrierRev never participates", async () => {
|
|
523
|
+
const b = await hooks.make();
|
|
524
|
+
const inputs = [
|
|
525
|
+
{ id: "id-input-0001", rev: "aaaaaaaaaaaaaaaa", superseded: true },
|
|
526
|
+
{ id: "id-input-0002", rev: "bbbbbbbbbbbbbbbb" },
|
|
527
|
+
];
|
|
528
|
+
const p = distilledEntry("id-prod-00001", "s1", "product", "the distilled abstraction", inputs);
|
|
529
|
+
const rep = await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
530
|
+
assert.deepStrictEqual(rep.conflicts, []);
|
|
531
|
+
const [got] = await b.getByIds([p.id]);
|
|
532
|
+
assert.deepStrictEqual(got?.frontmatter.distilled, p.frontmatter.distilled, "the distilled block must round-trip deep-equal through storage");
|
|
533
|
+
const bare = { id: p.id, scope: p.scope, slug: p.slug, frontmatter: { name: p.frontmatter.name }, body: p.body, rev: "" };
|
|
534
|
+
bare.rev = computeEntryRev(bare);
|
|
535
|
+
assert.notStrictEqual(bare.rev, p.rev, "the distilled block participates in the rev");
|
|
536
|
+
const otherAnchor = { ...p, frontmatter: { ...p.frontmatter, distilled: { ...p.frontmatter.distilled, carrierRev: "different-anchor" } } };
|
|
537
|
+
assert.strictEqual(computeEntryRev(otherAnchor), p.rev, "carrierRev is outside the rev tuple");
|
|
538
|
+
const plainRow = entry("id-plain-0001", "s1", "plain", "plain body", { name: "Plain" });
|
|
539
|
+
await b.applyPatches([{ op: "add", id: plainRow.id, entry: plainRow }]);
|
|
540
|
+
assert.strictEqual((await b.getByIds([plainRow.id]))[0]?.rev, plainRow.rev);
|
|
541
|
+
});
|
|
542
|
+
defer("design/339 c36: distilled immutability across update strip/member rewrite, plain re-add, guard add and same-batch delete+re-add ⇒ /malformed patch refused/; committed tombstone is the legal exit", async () => {
|
|
543
|
+
const b = await hooks.make();
|
|
544
|
+
const p = distilledEntry("id-prod-00002", "s1", "prod-immutable", "product body", [{ id: "id-input-0003", rev: "cccccccccccccccc", superseded: true }]);
|
|
545
|
+
await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
546
|
+
const expectMalformed = async (patch) => {
|
|
547
|
+
const rep = await b.applyPatches([patch]);
|
|
548
|
+
assert.strictEqual(rep.applied.filter((a) => a.id === p.id && a.op !== "delete").length, 0, "the touching op must not apply");
|
|
549
|
+
const bad = rep.conflicts.find((c) => c.id === p.id);
|
|
550
|
+
assert.match(bad?.reason ?? "", /malformed patch refused/);
|
|
551
|
+
const [committed] = await b.getByIds([p.id]);
|
|
552
|
+
assert.deepStrictEqual(committed?.frontmatter.distilled, p.frontmatter.distilled, "the committed block survives the refused op");
|
|
553
|
+
};
|
|
554
|
+
const stripped = { ...p, frontmatter: { name: p.frontmatter.name }, body: "edited" };
|
|
555
|
+
stripped.rev = computeEntryRev(stripped);
|
|
556
|
+
await expectMalformed({ op: "update", id: p.id, entry: stripped, baseRev: p.rev });
|
|
557
|
+
const flipped = { ...p, frontmatter: { ...p.frontmatter, distilled: { ...p.frontmatter.distilled, inputs: [{ id: "id-input-0003", rev: "cccccccccccccccc" }] } } };
|
|
558
|
+
flipped.rev = computeEntryRev(flipped);
|
|
559
|
+
await expectMalformed({ op: "update", id: p.id, entry: flipped, baseRev: p.rev });
|
|
560
|
+
const reanchored = { ...p, frontmatter: { ...p.frontmatter, distilled: { ...p.frontmatter.distilled, carrierRev: "forged-anchor-0001" } } };
|
|
561
|
+
reanchored.rev = computeEntryRev(reanchored);
|
|
562
|
+
assert.strictEqual(reanchored.rev, p.rev, "precondition: the carrierRev rewrite moves no rev");
|
|
563
|
+
await expectMalformed({ op: "update", id: p.id, entry: reanchored, baseRev: p.rev });
|
|
564
|
+
const bareAdd = { id: p.id, scope: p.scope, slug: p.slug, frontmatter: { name: p.frontmatter.name }, body: p.body, rev: "" };
|
|
565
|
+
bareAdd.rev = computeEntryRev(bareAdd);
|
|
566
|
+
await expectMalformed({ op: "add", id: p.id, entry: bareAdd });
|
|
567
|
+
await expectMalformed({ op: "add", id: p.id, entry: bareAdd, guard: "absent" });
|
|
568
|
+
const rep = await b.applyPatches([
|
|
569
|
+
{ op: "delete", id: p.id, baseRev: p.rev },
|
|
570
|
+
{ op: "add", id: p.id, entry: bareAdd },
|
|
571
|
+
]);
|
|
572
|
+
assert.match(rep.conflicts.find((c) => c.op === "add")?.reason ?? "", /malformed patch refused/);
|
|
573
|
+
const rep2 = await b.applyPatches([{ op: "add", id: p.id, entry: bareAdd }]);
|
|
574
|
+
assert.deepStrictEqual(rep2.conflicts, []);
|
|
575
|
+
assert.strictEqual((await b.getByIds([p.id]))[0]?.frontmatter.distilled, undefined);
|
|
576
|
+
});
|
|
577
|
+
defer("design/339 c37: PRECEDENCE — a distilled strip riding a STALE baseRev still answers the malformed refusal, never the rev-mismatch conflict", async () => {
|
|
578
|
+
const b = await hooks.make();
|
|
579
|
+
const p = distilledEntry("id-prod-00003", "s1", "prod-precedence", "v1", [{ id: "id-input-0004", rev: "dddddddddddddddd", superseded: true }]);
|
|
580
|
+
await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
581
|
+
const v2 = { ...p, body: "v2", frontmatter: { ...p.frontmatter, distilled: { ...p.frontmatter.distilled } } };
|
|
582
|
+
v2.rev = computeEntryRev(v2);
|
|
583
|
+
const repOk = await b.applyPatches([{ op: "update", id: p.id, entry: v2, baseRev: p.rev }]);
|
|
584
|
+
assert.deepStrictEqual(repOk.conflicts, []);
|
|
585
|
+
const stripped = { id: p.id, scope: p.scope, slug: p.slug, frontmatter: { name: p.frontmatter.name }, body: "v3", rev: "" };
|
|
586
|
+
stripped.rev = computeEntryRev(stripped);
|
|
587
|
+
const rep = await b.applyPatches([{ op: "update", id: p.id, entry: stripped, baseRev: p.rev }]);
|
|
588
|
+
assert.strictEqual(rep.applied.length, 0);
|
|
589
|
+
assert.match(rep.conflicts[0]?.reason ?? "", /malformed patch refused/);
|
|
590
|
+
assert.doesNotMatch(rep.conflicts[0]?.reason ?? "", /rev mismatch/);
|
|
591
|
+
});
|
|
592
|
+
defer("design/339 c38: the distilled fact rides BOTH header faces (listHeaders + search) as carrierRev + the superseded-row projection; block-free entries carry none on either face", async () => {
|
|
593
|
+
const b = await hooks.make();
|
|
594
|
+
const p = distilledEntry("id-prod-00004", "s1", "prod-header", "searchable distilled kumquat", [
|
|
595
|
+
{ id: "id-input-0005", rev: "eeeeeeeeeeeeeeee", superseded: true },
|
|
596
|
+
{ id: "id-input-0006", rev: "ffffffffffffffff" },
|
|
597
|
+
]);
|
|
598
|
+
const plain = entry("id-plain-0001", "s1", "plain", "searchable plain kumquat");
|
|
599
|
+
await b.applyPatches([
|
|
600
|
+
{ op: "add", id: p.id, entry: p, guard: "absent" },
|
|
601
|
+
{ op: "add", id: plain.id, entry: plain },
|
|
602
|
+
]);
|
|
603
|
+
const headers = await b.listHeaders(["s1"]);
|
|
604
|
+
const ph = headers.find((h) => h.id === p.id);
|
|
605
|
+
assert.deepStrictEqual(ph?.distilled, { carrierRev: p.rev, supersedes: [{ id: "id-input-0005", rev: "eeeeeeeeeeeeeeee" }] });
|
|
606
|
+
assert.strictEqual(headers.find((h) => h.id === plain.id)?.distilled, undefined);
|
|
607
|
+
const hits = await b.search("kumquat", ["s1"], { limit: 10 });
|
|
608
|
+
const psh = hits.find((h) => h.id === p.id);
|
|
609
|
+
assert.deepStrictEqual(psh?.distilled, { carrierRev: p.rev, supersedes: [{ id: "id-input-0005", rev: "eeeeeeeeeeeeeeee" }] }, "search reports the same committed fact");
|
|
610
|
+
assert.strictEqual(hits.find((h) => h.id === plain.id)?.distilled, undefined);
|
|
611
|
+
});
|
|
612
|
+
defer("design/339 c39: the plan-replay add spelling — a guard-absent re-add of the identical product is idempotent (one row, no overwrite, no conflict); a different-rev occupant answers add_guard_absent_conflict with currentRev", async () => {
|
|
613
|
+
const b = await hooks.make();
|
|
614
|
+
const p = distilledEntry("id-prod-00005", "s1", "prod-replay", "replayed product", [{ id: "id-input-0007", rev: "abababababababab", superseded: true }]);
|
|
615
|
+
await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
616
|
+
const replay = await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
617
|
+
assert.deepStrictEqual(replay.conflicts, [], "the identical replay is a no-op apply, never a conflict");
|
|
618
|
+
const rows = await b.getByIds([p.id]);
|
|
619
|
+
assert.strictEqual(rows.length, 1, "one row — a replay must not mint a duplicate");
|
|
620
|
+
assert.strictEqual(rows[0]?.rev, p.rev);
|
|
621
|
+
assert.strictEqual((await b.listHeaders(["s1"])).filter((h) => h.id === p.id).length, 1, "one projection");
|
|
622
|
+
const foreign = { ...p, body: "foreign occupant", frontmatter: { ...p.frontmatter, distilled: { ...p.frontmatter.distilled } } };
|
|
623
|
+
foreign.rev = computeEntryRev(foreign);
|
|
624
|
+
await b.applyPatches([{ op: "update", id: p.id, entry: foreign, baseRev: p.rev }]);
|
|
625
|
+
const conflicted = await b.applyPatches([{ op: "add", id: p.id, entry: p, guard: "absent" }]);
|
|
626
|
+
assert.strictEqual(conflicted.applied.length, 0);
|
|
627
|
+
assert.match(conflicted.conflicts[0]?.reason ?? "", /add_guard_absent_conflict/);
|
|
628
|
+
assert.strictEqual(conflicted.conflicts[0]?.currentRev, foreign.rev);
|
|
629
|
+
assert.strictEqual((await b.getByIds([p.id]))[0]?.body.replace(/\s+$/, ""), "foreign occupant", "the occupant survives");
|
|
630
|
+
});
|
|
631
|
+
defer("design/339 c40: the directed-arm judgment face — after an update-replay conflict, the reported currentRev and the committed read both answer the planned post-state's rev (already-applied is judgeable), and the judgment read is side-effect-free", async () => {
|
|
632
|
+
const b = await hooks.make();
|
|
633
|
+
const v1 = entry("id-directed-01", "s1", "directed", "v1");
|
|
634
|
+
await b.applyPatches([{ op: "add", id: v1.id, entry: v1 }]);
|
|
635
|
+
const terminal = entry("id-directed-01", "s1", "directed", "the intended terminal state");
|
|
636
|
+
const plannedPostRev = terminal.rev;
|
|
637
|
+
const first = await b.applyPatches([{ op: "update", id: v1.id, entry: terminal, baseRev: v1.rev }]);
|
|
638
|
+
assert.deepStrictEqual(first.conflicts, []);
|
|
639
|
+
const replay = await b.applyPatches([{ op: "update", id: v1.id, entry: terminal, baseRev: v1.rev }]);
|
|
640
|
+
assert.strictEqual(replay.applied.length, 0);
|
|
641
|
+
assert.strictEqual(replay.conflicts[0]?.currentRev, plannedPostRev, "the conflict's currentRev answers the already-applied judgment");
|
|
642
|
+
const read1 = await b.getByIds([v1.id]);
|
|
643
|
+
const read2 = await b.getByIds([v1.id]);
|
|
644
|
+
assert.strictEqual(read1[0]?.rev, plannedPostRev);
|
|
645
|
+
assert.strictEqual(read2[0]?.rev, plannedPostRev, "the judgment read advances no rev");
|
|
646
|
+
});
|
|
516
647
|
defer("consolidation cursor round-trips per scope; unset → undefined", async () => {
|
|
517
648
|
const b = await hooks.make();
|
|
518
649
|
assert.strictEqual(await b.getConsolidationCursor("s1"), undefined);
|
|
@@ -89,6 +89,18 @@ function parseMemorySyncResponse(raw, scope, peer) {
|
|
|
89
89
|
(originWire["cause"] !== undefined && !(typeof originWire["cause"] === "string" && MEMORY_ORIGIN_CAUSES.includes(originWire["cause"]))))) {
|
|
90
90
|
fail('entry.frontmatter.origin must be { taint: "external", cause?, at } with a known cause when present');
|
|
91
91
|
}
|
|
92
|
+
const distilledWire = fm["distilled"];
|
|
93
|
+
if (distilledWire !== undefined &&
|
|
94
|
+
(!isRecord(distilledWire) ||
|
|
95
|
+
!isNonEmptyString(distilledWire["planId"]) ||
|
|
96
|
+
typeof distilledWire["at"] !== "number" ||
|
|
97
|
+
!Number.isFinite(distilledWire["at"]) ||
|
|
98
|
+
!isNonEmptyString(distilledWire["carrierRev"]) ||
|
|
99
|
+
!(Array.isArray(distilledWire["inputs"]) &&
|
|
100
|
+
distilledWire["inputs"].length > 0 &&
|
|
101
|
+
distilledWire["inputs"].every((row) => isRecord(row) && isNonEmptyString(row["id"]) && isNonEmptyString(row["rev"]) && (row["superseded"] === undefined || row["superseded"] === true))))) {
|
|
102
|
+
fail("entry.frontmatter.distilled must be { planId, at, carrierRev, inputs: [{ id, rev, superseded? }, …] } when present");
|
|
103
|
+
}
|
|
92
104
|
const extraRaw = fm["extra"];
|
|
93
105
|
if (Array.isArray(extraRaw) && extraRaw.every((s) => typeof s === "string")) {
|
|
94
106
|
const pickedOrigin = originWire !== undefined
|
|
@@ -133,6 +145,20 @@ function parseMemorySyncResponse(raw, scope, peer) {
|
|
|
133
145
|
},
|
|
134
146
|
}
|
|
135
147
|
: {}),
|
|
148
|
+
...(distilledWire !== undefined
|
|
149
|
+
? {
|
|
150
|
+
distilled: {
|
|
151
|
+
planId: distilledWire["planId"],
|
|
152
|
+
at: distilledWire["at"],
|
|
153
|
+
carrierRev: distilledWire["carrierRev"],
|
|
154
|
+
inputs: distilledWire["inputs"].map((row) => ({
|
|
155
|
+
id: row["id"],
|
|
156
|
+
rev: row["rev"],
|
|
157
|
+
...(row["superseded"] === true ? { superseded: true } : {}),
|
|
158
|
+
})),
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
136
162
|
...(fm["extra"] !== undefined ? { extra: [...fm["extra"]] } : {}),
|
|
137
163
|
},
|
|
138
164
|
});
|
|
@@ -121,6 +121,9 @@ export interface CleanMemorySearchHit {
|
|
|
121
121
|
mtimeMs: number;
|
|
122
122
|
sizeBytes: number;
|
|
123
123
|
exposure?: never;
|
|
124
|
+
/** design/339 §3.4 — present ⇔ this hit is SUPERSEDED (retrieved via `includeSuperseded`; the
|
|
125
|
+
* default face filters such hits out) — the value is the standing carrier entry's id. */
|
|
126
|
+
supersededBy?: string;
|
|
124
127
|
}
|
|
125
128
|
/**
|
|
126
129
|
* design/336 §5.2 — a MARKED hit is an OPAQUE HANDLE: id/scope/score/age/size plus the exposure
|
|
@@ -140,6 +143,8 @@ export interface ExposedMemorySearchHit {
|
|
|
140
143
|
slug?: never;
|
|
141
144
|
name?: never;
|
|
142
145
|
description?: never;
|
|
146
|
+
/** design/339 §3.4 — see {@link CleanMemorySearchHit.supersededBy} (same seat on the handle shape). */
|
|
147
|
+
supersededBy?: string;
|
|
143
148
|
}
|
|
144
149
|
/**
|
|
145
150
|
* The search hit union (design/336 B5 — a DISCRIMINATED union on `exposure`, deliberately not an
|
|
@@ -181,6 +186,10 @@ export interface MemoryGetDetails {
|
|
|
181
186
|
/** design/336 §5.3 — present on an `ok` delivery of a MARKED entry under `provenance: "carry"`
|
|
182
187
|
* (the structured half of the banner). Absent under "off" and for unmarked entries. */
|
|
183
188
|
exposure?: "external";
|
|
189
|
+
/** design/339 §3.4 — present on an `ok` delivery of a SUPERSEDED entry (mode-free, data-driven):
|
|
190
|
+
* the standing carrier entry's id (the disclosure note's structured half). Delivery proceeds —
|
|
191
|
+
* supersession is disclosure/ordering, never withholding. */
|
|
192
|
+
supersededBy?: string;
|
|
184
193
|
}
|
|
185
194
|
/** Cut `text` to at most `maxBytes` UTF-8 bytes on a CODE POINT boundary (a byte-wise slice would
|
|
186
195
|
* strand half a character), reporting how many bytes were dropped. Unchanged text reports 0.
|