@sema-agent/core 7.25.0 → 7.26.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 +81 -0
- package/dist/core/arg-summary.d.ts +0 -7
- package/dist/core/arg-summary.js +2 -1
- package/dist/core/ask-origin.d.ts +84 -0
- package/dist/core/ask-origin.js +31 -3
- package/dist/core/auto-compaction.d.ts +5 -0
- package/dist/core/auto-compaction.js +7 -0
- package/dist/core/auto-mode-directive.d.ts +45 -0
- package/dist/core/auto-mode-directive.js +47 -0
- package/dist/core/checkpoint-store.d.ts +10 -1
- package/dist/core/engine-notice.d.ts +10 -0
- package/dist/core/gate-lanes.js +18 -10
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/mcp-probe.d.ts +122 -0
- package/dist/core/mcp-probe.js +132 -0
- package/dist/core/memory-engine/engine.d.ts +6 -1
- package/dist/core/memory-engine/engine.js +30 -2
- package/dist/core/memory-engine/index.d.ts +1 -0
- package/dist/core/memory-engine/index.js +1 -0
- package/dist/core/memory-engine/shadowing.d.ts +58 -0
- package/dist/core/memory-engine/shadowing.js +40 -0
- package/dist/core/memory-engine/tools.d.ts +33 -1
- package/dist/core/memory-engine/tools.js +136 -31
- package/dist/core/memory-engine/types.d.ts +10 -0
- package/dist/core/memory.d.ts +60 -0
- package/dist/core/memory.js +47 -2
- package/dist/core/permission-rule-store.d.ts +51 -8
- package/dist/core/permission-rule-store.js +112 -65
- package/dist/core/reminder-mint.d.ts +41 -0
- package/dist/core/reminder-mint.js +55 -0
- package/dist/core/runner/compaction-restate.d.ts +9 -0
- package/dist/core/runner/compaction-restate.js +20 -0
- package/dist/core/runner/contracts.d.ts +18 -1
- package/dist/core/runner/denial-limit-arms.js +1 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +5 -17
- package/dist/core/runner/permission-rule-lanes.js +5 -23
- package/dist/core/runner/prepare-ask-lane.js +2 -0
- package/dist/core/runner/prepare-context-lane.d.ts +4 -0
- package/dist/core/runner/prepare-context-lane.js +7 -2
- package/dist/core/runner/prepare-inherited-gate.d.ts +5 -1
- package/dist/core/runner/prepare-inherited-gate.js +4 -2
- package/dist/core/runner/prepare-memory.js +47 -6
- package/dist/core/runner/prepare-park-ask.js +1 -0
- package/dist/core/runner/prepare-policy-chain.js +8 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +2 -0
- package/dist/core/runner/prepare-suspend-saga.js +3 -1
- package/dist/core/runner/prepare-task.js +7 -4
- package/dist/core/runner/run-leg.js +9 -1
- package/dist/core/runner/run-recovery-lanes.js +2 -2
- package/dist/core/runner/run-terminal-adoption.js +2 -2
- package/dist/core/runner/run-turn-boundary.js +2 -2
- package/dist/core/store-contracts/permission-rule-write-contract.d.ts +20 -0
- package/dist/core/store-contracts/permission-rule-write-contract.js +77 -0
- package/dist/core/task-result.d.ts +11 -0
- package/dist/core/task-spec.d.ts +31 -0
- package/dist/core/tool-catalog-entries.js +3 -3
- package/dist/core/tool-policy.d.ts +38 -0
- package/dist/core/tool-policy.js +35 -24
- package/dist/core/types.d.ts +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/stores/file/permission-rule-store.js +48 -29
- package/dist/tools/fs/bash-lexer.d.ts +13 -0
- package/dist/tools/fs/bash-lexer.js +61 -0
- package/dist/tools/fs/bash-program-position.d.ts +32 -3
- package/dist/tools/fs/bash-program-position.js +33 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +6 -4
- package/dist/tools/fs/bash-readonly-classifier.js +55 -18
- package/dist/tools/fs/shell-read-plan.js +9 -11
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +29 -1
- package/wire-changes/core@7.25.1.json +64 -0
- package/wire-changes/core@7.26.0.json +695 -0
|
@@ -6,6 +6,7 @@ import { formatMemoryAge } from "../memory-recall.js";
|
|
|
6
6
|
import { committedOriginOf } from "./frontmatter.js";
|
|
7
7
|
import { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_INDEX_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, MEMORY_SEARCH_BAND_ORDER_HEADER } from "./provenance-wording.js";
|
|
8
8
|
import { deriveSupersededSet, memorySupersededNote, MEMORY_SEARCH_SUPERSEDED_TAG } from "./consolidation.js";
|
|
9
|
+
import { deriveShadowedSet, memoryShadowedNote, shadowNamesOf, MEMORY_SEARCH_SHADOWED_TAG } from "./shadowing.js";
|
|
9
10
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
10
11
|
export const MEMORY_SEARCH_TOOL_NAME = "memory_search";
|
|
11
12
|
export const MEMORY_GET_TOOL_NAME = "memory_get";
|
|
@@ -69,9 +70,10 @@ const INDEX_DESCRIPTION = [
|
|
|
69
70
|
"",
|
|
70
71
|
`When to use it: when ${MEMORY_SEARCH_TOOL_NAME} comes up empty but the fact should exist, when the ` +
|
|
71
72
|
`injected memory index says it was truncated, or to survey what is stored at all. The listing is ` +
|
|
72
|
-
`complete for the ACTIVE set: an entry absent across all pages is either not mounted
|
|
73
|
-
`by a newer consolidated entry
|
|
74
|
-
`
|
|
73
|
+
`complete for the ACTIVE set: an entry absent across all pages is either not mounted, superseded ` +
|
|
74
|
+
`by a newer consolidated entry, or shadowed by a same-named entry in this session's write home ` +
|
|
75
|
+
`(both are hidden by default, both counts are always disclosed, and includeSuperseded / ` +
|
|
76
|
+
`includeShadowed list them too). The order is structural (scope, then name), NOT ` +
|
|
75
77
|
`by relevance — when checking whether something exists, page through to the last page before ` +
|
|
76
78
|
`concluding it does not; the row you need may sit at the end, and early pages full of routine ` +
|
|
77
79
|
`entries say nothing about the later ones. Pages read the live store: on a multi-page pass, send ` +
|
|
@@ -173,12 +175,56 @@ export function createMemoryEngineTools(opts) {
|
|
|
173
175
|
const scopeSet = new Set(plane.scopes);
|
|
174
176
|
return (await plane.backend.getByIds(ids)).filter((e) => scopeSet.has(e.scope) && e.frontmatter.deleted !== true);
|
|
175
177
|
};
|
|
176
|
-
const
|
|
178
|
+
const servedHeaders = async (plane) => {
|
|
177
179
|
if (plane.scopes.length === 0)
|
|
178
|
-
return
|
|
180
|
+
return [];
|
|
179
181
|
const face = plane.backend.retrievalView?.() ?? plane.backend;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
return face.listHeaders(plane.scopes);
|
|
183
|
+
};
|
|
184
|
+
const supersededOf = async (plane, exclusions) => deriveSupersededSet(await servedHeaders(plane), { excluded: (id) => exclusions?.has(id) === true });
|
|
185
|
+
const planeFaceOf = async (plane, excluded) => {
|
|
186
|
+
const headers = await servedHeaders(plane);
|
|
187
|
+
return { headers, superseded: deriveSupersededSet(headers, { excluded: (id) => excluded?.has(id) === true }) };
|
|
188
|
+
};
|
|
189
|
+
const shadowNamesAcross = async (exclusions, faces = []) => {
|
|
190
|
+
const out = new Map();
|
|
191
|
+
const writeScope = opts.writeScope;
|
|
192
|
+
if (writeScope === undefined || writeScope === null)
|
|
193
|
+
return out;
|
|
194
|
+
for (let i = 0; i < planes.length; i++) {
|
|
195
|
+
const plane = planes[i];
|
|
196
|
+
if (!plane.scopes.includes(writeScope))
|
|
197
|
+
continue;
|
|
198
|
+
const committed = await plane.committedHeaders?.();
|
|
199
|
+
if (committed === undefined)
|
|
200
|
+
continue;
|
|
201
|
+
const face = faces[i] ?? (await planeFaceOf(plane, exclusions[i]));
|
|
202
|
+
const excluded = exclusions[i];
|
|
203
|
+
const standing = committed.filter((h) => excluded?.has(h.id) !== true && !face.superseded.has(h.id));
|
|
204
|
+
for (const [name, id] of shadowNamesOf(standing, writeScope)) {
|
|
205
|
+
const prior = out.get(name);
|
|
206
|
+
if (prior === undefined || id < prior)
|
|
207
|
+
out.set(name, id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
};
|
|
212
|
+
const shadowedOf = (headers, names) => names.size === 0 ? new Map() : deriveShadowedSet(headers, opts.writeScope ?? null, { shadowNames: names });
|
|
213
|
+
const pruneUndeliverableCarriers = async (maps) => {
|
|
214
|
+
const carriers = new Set();
|
|
215
|
+
for (const m of maps)
|
|
216
|
+
for (const id of m.values())
|
|
217
|
+
carriers.add(id);
|
|
218
|
+
if (carriers.size === 0)
|
|
219
|
+
return [...maps];
|
|
220
|
+
const ids = [...carriers];
|
|
221
|
+
const live = new Set();
|
|
222
|
+
for (const plane of planes)
|
|
223
|
+
for (const e of await getWithinScopes(plane, ids))
|
|
224
|
+
live.add(e.id);
|
|
225
|
+
if (live.size === carriers.size)
|
|
226
|
+
return [...maps];
|
|
227
|
+
return maps.map((m) => new Map([...m].filter(([, carrier]) => live.has(carrier))));
|
|
182
228
|
};
|
|
183
229
|
const searchTool = {
|
|
184
230
|
name: MEMORY_SEARCH_TOOL_NAME,
|
|
@@ -190,9 +236,12 @@ export function createMemoryEngineTools(opts) {
|
|
|
190
236
|
includeSuperseded: Type.Optional(Type.Boolean({
|
|
191
237
|
description: "Also return entries a newer consolidated entry has superseded (kept as evidence; hidden from results by default). Superseded hits are tagged, with the superseding entry's id.",
|
|
192
238
|
})),
|
|
239
|
+
includeShadowed: Type.Optional(Type.Boolean({
|
|
240
|
+
description: "Also return entries a same-named entry in this session's write home shadows (kept as evidence; hidden from results by default). Shadowed hits are tagged, with the shadowing entry's id.",
|
|
241
|
+
})),
|
|
193
242
|
}, { additionalProperties: false }),
|
|
194
243
|
execute: async (args, ctx) => {
|
|
195
|
-
const { query, limit: rawLimit, includeSuperseded } = args;
|
|
244
|
+
const { query, limit: rawLimit, includeSuperseded, includeShadowed } = args;
|
|
196
245
|
const signal = ctx.signal;
|
|
197
246
|
if (query.trim() === "") {
|
|
198
247
|
return refusedSearch("empty_query", "query must be non-empty — pass the concrete keywords a past memory entry would contain.");
|
|
@@ -208,11 +257,15 @@ export function createMemoryEngineTools(opts) {
|
|
|
208
257
|
}
|
|
209
258
|
}
|
|
210
259
|
const supersededMaps = [];
|
|
260
|
+
const shadowedMaps = [];
|
|
211
261
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
262
|
+
const faces = [];
|
|
263
|
+
for (let i = 0; i < planes.length; i++)
|
|
264
|
+
faces.push(await planeFaceOf(planes[i], exclusions[i]));
|
|
265
|
+
for (const face of faces)
|
|
266
|
+
supersededMaps.push(face.superseded);
|
|
267
|
+
const names = await shadowNamesAcross(exclusions, faces);
|
|
268
|
+
shadowedMaps.push(...(await pruneUndeliverableCarriers(faces.map((f) => shadowedOf(f.headers, names)))));
|
|
216
269
|
}
|
|
217
270
|
catch (err) {
|
|
218
271
|
if (signal?.aborted === true)
|
|
@@ -226,13 +279,16 @@ export function createMemoryEngineTools(opts) {
|
|
|
226
279
|
if (plane.scopes.length === 0)
|
|
227
280
|
continue;
|
|
228
281
|
const supersededSlots = includeSuperseded === true ? 0 : supersededMaps[i]?.size ?? 0;
|
|
229
|
-
const
|
|
282
|
+
const shadowedSlots = includeShadowed === true ? 0 : shadowedMaps[i]?.size ?? 0;
|
|
283
|
+
const askFor = (carry ? limit + (exclusions[i]?.size ?? 0) : limit) + supersededSlots + shadowedSlots;
|
|
230
284
|
const hits = await plane.backend.search(query, plane.scopes, { limit: askFor, ...(carry ? { exposureBands: true } : {}) });
|
|
231
285
|
for (const h of hits) {
|
|
232
286
|
if (exclusions[i]?.has(h.id))
|
|
233
287
|
continue;
|
|
234
288
|
if (includeSuperseded !== true && supersededMaps[i]?.has(h.id))
|
|
235
289
|
continue;
|
|
290
|
+
if (includeShadowed !== true && shadowedMaps[i]?.has(h.id))
|
|
291
|
+
continue;
|
|
236
292
|
merged.push({ ...h, planeIndex: i });
|
|
237
293
|
}
|
|
238
294
|
}
|
|
@@ -292,11 +348,13 @@ export function createMemoryEngineTools(opts) {
|
|
|
292
348
|
const h = live[i];
|
|
293
349
|
const exposed = exposedFlags[i];
|
|
294
350
|
const supersededBy = supersededMaps[h.planeIndex]?.get(h.id);
|
|
295
|
-
const
|
|
351
|
+
const shadowedBy = shadowedMaps[h.planeIndex]?.get(h.id);
|
|
352
|
+
const tags = `${supersededBy !== undefined ? ` ${MEMORY_SEARCH_SUPERSEDED_TAG}` : ""}${shadowedBy !== undefined ? ` ${MEMORY_SEARCH_SHADOWED_TAG}` : ""}`;
|
|
353
|
+
const offFace = { ...(supersededBy !== undefined ? { supersededBy } : {}), ...(shadowedBy !== undefined ? { shadowedBy } : {}) };
|
|
296
354
|
if (exposed) {
|
|
297
|
-
hits.push({ exposure: "external", id: h.id, scope: h.scope, score: h.score, mtimeMs: h.mtimeMs, sizeBytes: h.sizeBytes, ...
|
|
355
|
+
hits.push({ exposure: "external", id: h.id, scope: h.scope, score: h.score, mtimeMs: h.mtimeMs, sizeBytes: h.sizeBytes, ...offFace });
|
|
298
356
|
lines.push("");
|
|
299
|
-
lines.push(`${i + 1}. [mem:${h.id}] ${MEMORY_EXPOSURE_HANDLE_TAG}${
|
|
357
|
+
lines.push(`${i + 1}. [mem:${h.id}] ${MEMORY_EXPOSURE_HANDLE_TAG}${tags} (scope ${inlineUntrusted(h.scope, 80)}, score ${h.score.toFixed(3)}, ${ageOf(now, h.mtimeMs)}) — read it with ${MEMORY_GET_TOOL_NAME} id ${h.id}`);
|
|
300
358
|
continue;
|
|
301
359
|
}
|
|
302
360
|
hits.push({
|
|
@@ -308,11 +366,11 @@ export function createMemoryEngineTools(opts) {
|
|
|
308
366
|
score: h.score,
|
|
309
367
|
mtimeMs: h.mtimeMs,
|
|
310
368
|
sizeBytes: h.sizeBytes,
|
|
311
|
-
...
|
|
369
|
+
...offFace,
|
|
312
370
|
});
|
|
313
371
|
const hook = h.description ? ` — ${inlineUntrusted(h.description, 200)}` : "";
|
|
314
372
|
lines.push("");
|
|
315
|
-
lines.push(`${i + 1}. ${entryPath(h.scope, h.slug)}${
|
|
373
|
+
lines.push(`${i + 1}. ${entryPath(h.scope, h.slug)}${tags}${hook} (id ${h.id}, score ${h.score.toFixed(3)}, ${ageOf(now, h.mtimeMs)})`);
|
|
316
374
|
const body = (entryById.get(h.id)?.body ?? "").trim();
|
|
317
375
|
if (body !== "")
|
|
318
376
|
lines.push(delimitUntrusted(`memory entry ${h.slug}`, body, MEMORY_SEARCH_SNIPPET_CAP));
|
|
@@ -349,6 +407,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
349
407
|
const lineCursor = Math.max(0, Math.floor(rawLineCursor ?? 0) || 0);
|
|
350
408
|
let entry;
|
|
351
409
|
let entryPlane;
|
|
410
|
+
let entryHeader;
|
|
352
411
|
let mtimeMs;
|
|
353
412
|
try {
|
|
354
413
|
if (id !== undefined) {
|
|
@@ -365,7 +424,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
365
424
|
`If the id might be misremembered: ${MEMORY_SEARCH_TOOL_NAME} finds entries by keyword, and ` +
|
|
366
425
|
`${MEMORY_INDEX_TOOL_NAME} lists every mounted entry with its exact id and slug.`, "not_found", { id });
|
|
367
426
|
}
|
|
368
|
-
|
|
427
|
+
entryHeader = (await entryPlane.backend.listHeaders(entryPlane.scopes)).find((h) => h.id === entry.id);
|
|
428
|
+
mtimeMs = entryHeader?.mtimeMs;
|
|
369
429
|
}
|
|
370
430
|
else {
|
|
371
431
|
const matches = [];
|
|
@@ -388,6 +448,17 @@ export function createMemoryEngineTools(opts) {
|
|
|
388
448
|
`If the slug is misremembered: ${MEMORY_SEARCH_TOOL_NAME} matches keywords; ${MEMORY_INDEX_TOOL_NAME} lists exact slugs.` +
|
|
389
449
|
pollutionSentence(), "not_found", { slug: slug, ...(scope !== undefined ? { scope } : {}) });
|
|
390
450
|
}
|
|
451
|
+
try {
|
|
452
|
+
const names = await shadowNamesAcross(planes.map((plane) => plane.challengeExclusions?.()));
|
|
453
|
+
const [shadowed] = await pruneUndeliverableCarriers([shadowedOf(matches.map((m) => m.header), names)]);
|
|
454
|
+
const standing = matches.filter((m) => shadowed?.has(m.header.id) !== true);
|
|
455
|
+
if (standing.length > 0 && standing.length < matches.length) {
|
|
456
|
+
matches.length = 0;
|
|
457
|
+
matches.push(...standing);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
}
|
|
391
462
|
if (matches.length > 1) {
|
|
392
463
|
const candidates = matches.map((m) => ({ scope: m.header.scope, slug: m.header.slug, id: m.header.id }));
|
|
393
464
|
const listing = candidates.map((c) => `- scope ${JSON.stringify(inlineUntrusted(c.scope, 80))}, id ${c.id}`).join("\n");
|
|
@@ -400,7 +471,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
400
471
|
}
|
|
401
472
|
entry = found;
|
|
402
473
|
entryPlane = matches[0].plane;
|
|
403
|
-
|
|
474
|
+
entryHeader = matches[0].header;
|
|
475
|
+
mtimeMs = entryHeader.mtimeMs;
|
|
404
476
|
}
|
|
405
477
|
}
|
|
406
478
|
catch (err) {
|
|
@@ -436,6 +508,17 @@ export function createMemoryEngineTools(opts) {
|
|
|
436
508
|
supersededBy = undefined;
|
|
437
509
|
}
|
|
438
510
|
}
|
|
511
|
+
let shadowedBy;
|
|
512
|
+
if (entryHeader !== undefined) {
|
|
513
|
+
try {
|
|
514
|
+
const names = await shadowNamesAcross(planes.map((plane) => plane.challengeExclusions?.()));
|
|
515
|
+
const [pruned] = await pruneUndeliverableCarriers([shadowedOf([entryHeader], names)]);
|
|
516
|
+
shadowedBy = pruned?.get(entry.id);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
shadowedBy = undefined;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
439
522
|
try {
|
|
440
523
|
entryPlane?.recordRetrieved([entry.id]);
|
|
441
524
|
}
|
|
@@ -450,6 +533,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
450
533
|
`Memory entry ${entryPath(entry.scope, entry.slug)} (id ${entry.id}${mtimeMs !== undefined ? `, ${ageOf(now, mtimeMs)}` : ""})`,
|
|
451
534
|
...(exposed ? [MEMORY_EXPOSURE_BANNER] : []),
|
|
452
535
|
...(supersededBy !== undefined ? [memorySupersededNote(supersededBy)] : []),
|
|
536
|
+
...(shadowedBy !== undefined ? [memoryShadowedNote(shadowedBy)] : []),
|
|
453
537
|
...(fm.name !== undefined ? [`name: ${inlineUntrusted(fm.name, 120)}`] : []),
|
|
454
538
|
...(fm.description !== undefined ? [`description: ${inlineUntrusted(fm.description, 200)}`] : []),
|
|
455
539
|
...(fm.type !== undefined ? [`type: ${inlineUntrusted(fm.type, 40)}`] : []),
|
|
@@ -494,6 +578,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
494
578
|
...(nextLineCursor !== undefined ? { nextLineCursor } : {}),
|
|
495
579
|
...(exposed ? { exposure: "external" } : {}),
|
|
496
580
|
...(supersededBy !== undefined ? { supersededBy } : {}),
|
|
581
|
+
...(shadowedBy !== undefined ? { shadowedBy } : {}),
|
|
497
582
|
};
|
|
498
583
|
return { content: head.join("\n"), details };
|
|
499
584
|
}
|
|
@@ -536,6 +621,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
536
621
|
...(nextLineCursor !== undefined ? { nextLineCursor } : {}),
|
|
537
622
|
...(exposed ? { exposure: "external" } : {}),
|
|
538
623
|
...(supersededBy !== undefined ? { supersededBy } : {}),
|
|
624
|
+
...(shadowedBy !== undefined ? { shadowedBy } : {}),
|
|
539
625
|
};
|
|
540
626
|
return { content: head.join("\n"), details };
|
|
541
627
|
},
|
|
@@ -558,9 +644,12 @@ export function createMemoryEngineTools(opts) {
|
|
|
558
644
|
includeSuperseded: Type.Optional(Type.Boolean({
|
|
559
645
|
description: "Also list entries a newer consolidated entry has superseded (kept as evidence; hidden from the listing by default). Superseded rows are tagged, with the superseding entry's id.",
|
|
560
646
|
})),
|
|
647
|
+
includeShadowed: Type.Optional(Type.Boolean({
|
|
648
|
+
description: "Also list entries a same-named entry in this session's write home shadows (kept as evidence; hidden from the listing by default). Shadowed rows are tagged, with the shadowing entry's id.",
|
|
649
|
+
})),
|
|
561
650
|
}, { additionalProperties: false }),
|
|
562
651
|
execute: async (args, ctx) => {
|
|
563
|
-
const { offset: rawOffset, limit: rawLimit, listingRev: givenListingRev, scope, includeSuperseded, } = args;
|
|
652
|
+
const { offset: rawOffset, limit: rawLimit, listingRev: givenListingRev, scope, includeSuperseded, includeShadowed, } = args;
|
|
564
653
|
const signal = ctx.signal;
|
|
565
654
|
const offset = Math.max(0, Math.floor(rawOffset ?? 0) || 0);
|
|
566
655
|
const limit = Math.max(1, Math.min(MEMORY_INDEX_MAX_PAGE_ROWS, Math.floor(rawLimit ?? MEMORY_INDEX_PAGE_ROWS) || MEMORY_INDEX_PAGE_ROWS));
|
|
@@ -583,14 +672,20 @@ export function createMemoryEngineTools(opts) {
|
|
|
583
672
|
}
|
|
584
673
|
const rows = [];
|
|
585
674
|
let supersededHidden = 0;
|
|
675
|
+
let shadowedHidden = 0;
|
|
586
676
|
try {
|
|
677
|
+
const faces = [];
|
|
678
|
+
for (let i = 0; i < planes.length; i++)
|
|
679
|
+
faces.push(await planeFaceOf(planes[i], exclusions[i]));
|
|
680
|
+
const names = await shadowNamesAcross(exclusions, faces);
|
|
681
|
+
const shadowedMaps = await pruneUndeliverableCarriers(faces.map((f) => shadowedOf(f.headers, names)));
|
|
587
682
|
for (let i = 0; i < planes.length; i++) {
|
|
588
683
|
const plane = planes[i];
|
|
589
684
|
if (plane.scopes.length === 0)
|
|
590
685
|
continue;
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
const
|
|
686
|
+
const headers = faces[i].headers;
|
|
687
|
+
const superseded = faces[i].superseded;
|
|
688
|
+
const shadowed = shadowedMaps[i];
|
|
594
689
|
const scopeSet = new Set(plane.scopes);
|
|
595
690
|
for (const h of headers) {
|
|
596
691
|
if (!scopeSet.has(h.scope))
|
|
@@ -604,7 +699,12 @@ export function createMemoryEngineTools(opts) {
|
|
|
604
699
|
supersededHidden++;
|
|
605
700
|
continue;
|
|
606
701
|
}
|
|
607
|
-
|
|
702
|
+
const shadowedBy = shadowed.get(h.id);
|
|
703
|
+
if (shadowedBy !== undefined && includeShadowed !== true) {
|
|
704
|
+
shadowedHidden++;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
rows.push({ planeIndex: i, header: h, ...(supersededBy !== undefined ? { supersededBy } : {}), ...(shadowedBy !== undefined ? { shadowedBy } : {}) });
|
|
608
708
|
}
|
|
609
709
|
}
|
|
610
710
|
}
|
|
@@ -629,13 +729,16 @@ export function createMemoryEngineTools(opts) {
|
|
|
629
729
|
return a.header.id < b.header.id ? -1 : a.header.id > b.header.id ? 1 : 0;
|
|
630
730
|
});
|
|
631
731
|
const total = rows.length;
|
|
632
|
-
const supersededNote = supersededHidden > 0 && includeSuperseded !== true
|
|
732
|
+
const supersededNote = (supersededHidden > 0 && includeSuperseded !== true
|
|
633
733
|
? ` ${supersededHidden} superseded entr${supersededHidden === 1 ? "y is" : "ies are"} hidden — pass includeSuperseded to list them.`
|
|
634
|
-
: ""
|
|
734
|
+
: "") +
|
|
735
|
+
(shadowedHidden > 0 && includeShadowed !== true
|
|
736
|
+
? ` ${shadowedHidden} shadowed entr${shadowedHidden === 1 ? "y is" : "ies are"} hidden — pass includeShadowed to list them.`
|
|
737
|
+
: "");
|
|
635
738
|
if (total === 0) {
|
|
636
739
|
const where = scope !== undefined ? ` in scope ${inlineUntrusted(scope, 80)}` : "";
|
|
637
|
-
const details = { outcome: "ok", entries: [], offset: 0, shown: 0, total: 0, ...(supersededHidden > 0 ? { supersededHidden } : {}) };
|
|
638
|
-
return { content: `No ${supersededHidden > 0 ? "active " : ""}memory entries are mounted${where} in this session.${supersededNote}` + pollutionSentence(), details };
|
|
740
|
+
const details = { outcome: "ok", entries: [], offset: 0, shown: 0, total: 0, ...(supersededHidden > 0 ? { supersededHidden } : {}), ...(shadowedHidden > 0 ? { shadowedHidden } : {}) };
|
|
741
|
+
return { content: `No ${supersededHidden > 0 || shadowedHidden > 0 ? "active " : ""}memory entries are mounted${where} in this session.${supersededNote}` + pollutionSentence(), details };
|
|
639
742
|
}
|
|
640
743
|
if (offset >= total) {
|
|
641
744
|
return refusedIndex("offset_past_end", `offset ${offset} is past the end — the listing has ${total} row${total === 1 ? "" : "s"}.`);
|
|
@@ -686,7 +789,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
686
789
|
}
|
|
687
790
|
const h = r.header;
|
|
688
791
|
const exposed = carry && (h.exposure === "external" || (pageEntryById.has(h.id) && exposedEntry(pageEntryById.get(h.id))));
|
|
689
|
-
const supersededSuffix = r.supersededBy !== undefined ? ` ${MEMORY_SEARCH_SUPERSEDED_TAG}` : ""
|
|
792
|
+
const supersededSuffix = `${r.supersededBy !== undefined ? ` ${MEMORY_SEARCH_SUPERSEDED_TAG}` : ""}${r.shadowedBy !== undefined ? ` ${MEMORY_SEARCH_SHADOWED_TAG}` : ""}`;
|
|
690
793
|
const line = exposed
|
|
691
794
|
? `${offset + consumed + 1}. [mem:${h.id}] ${MEMORY_EXPOSURE_HANDLE_TAG}${supersededSuffix} (scope ${inlineUntrusted(h.scope, 80)}, ${ageOf(now, h.mtimeMs)}) — read it with ${MEMORY_GET_TOOL_NAME} id ${h.id}`
|
|
692
795
|
: `${offset + consumed + 1}. ${entryPath(h.scope, h.slug)}${supersededSuffix}${h.description ? ` — ${inlineUntrusted(h.description, 200)}` : ""} (id ${h.id}, ${ageOf(now, h.mtimeMs)})`;
|
|
@@ -699,7 +802,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
699
802
|
bytes += lineBytes;
|
|
700
803
|
consumed++;
|
|
701
804
|
if (exposed) {
|
|
702
|
-
entries.push({ exposure: "external", id: h.id, scope: h.scope, mtimeMs: h.mtimeMs, sizeBytes: h.sizeBytes, ...(r.supersededBy !== undefined ? { supersededBy: r.supersededBy } : {}) });
|
|
805
|
+
entries.push({ exposure: "external", id: h.id, scope: h.scope, mtimeMs: h.mtimeMs, sizeBytes: h.sizeBytes, ...(r.supersededBy !== undefined ? { supersededBy: r.supersededBy } : {}), ...(r.shadowedBy !== undefined ? { shadowedBy: r.shadowedBy } : {}) });
|
|
703
806
|
}
|
|
704
807
|
else {
|
|
705
808
|
entries.push({
|
|
@@ -711,6 +814,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
711
814
|
mtimeMs: h.mtimeMs,
|
|
712
815
|
sizeBytes: h.sizeBytes,
|
|
713
816
|
...(r.supersededBy !== undefined ? { supersededBy: r.supersededBy } : {}),
|
|
817
|
+
...(r.shadowedBy !== undefined ? { shadowedBy: r.shadowedBy } : {}),
|
|
714
818
|
});
|
|
715
819
|
}
|
|
716
820
|
}
|
|
@@ -728,6 +832,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
728
832
|
total,
|
|
729
833
|
listingRev,
|
|
730
834
|
...(supersededHidden > 0 && includeSuperseded !== true ? { supersededHidden } : {}),
|
|
835
|
+
...(shadowedHidden > 0 && includeShadowed !== true ? { shadowedHidden } : {}),
|
|
731
836
|
...(end < total || capTripped ? { nextOffset: end } : {}),
|
|
732
837
|
};
|
|
733
838
|
return { content: [head, ...lines, ...(footer !== undefined ? [footer] : [])].join("\n"), details };
|
|
@@ -438,6 +438,16 @@ export interface MemorySessionHandle {
|
|
|
438
438
|
* clean-entry mis-labeling). Absent on handles minted by engines predating the field ⇒ consumers
|
|
439
439
|
* treat as "unknown", which the discipline seat maps to the historic over-teaching side. */
|
|
440
440
|
markedEntriesPresent?: boolean;
|
|
441
|
+
/** The names the SESSION's write home carries (name → the carrying entry's id), supplied by the
|
|
442
|
+
* caller when the write home lives on a DIFFERENT plane than this one (the dual-root read-only
|
|
443
|
+
* plane). Absent on a plane holding the write scope itself (the rule reads this plane's own
|
|
444
|
+
* headers) and in a session with no write home, where the shadow set is empty. */
|
|
445
|
+
shadowNames?: ReadonlyMap<string, string>;
|
|
446
|
+
/** How many entries of each scope this plane's index rebuild retired from the default read face
|
|
447
|
+
* by write-home name — the mount-time snapshot the runner reports per scope. Only non-zero
|
|
448
|
+
* scopes have a key; the tool faces derive their own LIVE answer per call, so a stale snapshot
|
|
449
|
+
* can mis-report a number, never mis-serve an entry. */
|
|
450
|
+
shadowedByScope?: ReadonlyMap<string, number>;
|
|
441
451
|
/** Lines of the derived MEMORY.md at materialize time (the index-cleared fuse baseline). */
|
|
442
452
|
indexBaselineLines: number;
|
|
443
453
|
/** 评审 M6 — the derived index TEXT as of materialize. `inject` reads THIS when no on-disk
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EngineNotice } from "./engine-notice.js";
|
|
1
2
|
/**
|
|
2
3
|
* A scored memory candidate returned by {@link MemoryStore.searchScored} (design/41). Carries a
|
|
3
4
|
* **stable id** (for {@link MemoryStore.update}/{@link MemoryStore.delete}) and a similarity `score`.
|
|
@@ -525,7 +526,33 @@ export interface NormalizedMemorySpec {
|
|
|
525
526
|
scopeOrigins?: Readonly<Record<string, "deployment" | "request">>;
|
|
526
527
|
/** See {@link MemorySpecInput.capture} (design/383 §2.1 — the session capture opt-out). */
|
|
527
528
|
capture?: "off";
|
|
529
|
+
/** See {@link MemorySpecInput.projectKey} — carried verbatim; a normalized spec never invents it. */
|
|
530
|
+
projectKey?: MemoryProjectKey;
|
|
528
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* The closed vocabulary a deployment states a MISSING project key with: the working root is not a
|
|
534
|
+
* repository, it is a bare repository, or it could not be read. Purely descriptive — the engine
|
|
535
|
+
* branches on presence (`absent` vs `scope`), never on which word; the word rides the operator
|
|
536
|
+
* notice so a deployment can tell "no project here" from "I could not look".
|
|
537
|
+
*/
|
|
538
|
+
export declare const MEMORY_PROJECT_KEY_ABSENT_REASONS: readonly ["not_git", "bare", "unreadable"];
|
|
539
|
+
export type MemoryProjectKeyAbsentReason = (typeof MEMORY_PROJECT_KEY_ABSENT_REASONS)[number];
|
|
540
|
+
/**
|
|
541
|
+
* The assembly point's STATEMENT about this task's project identity — the key it derived for the
|
|
542
|
+
* working root, or the reason there is none. Exactly one arm is spelled.
|
|
543
|
+
*
|
|
544
|
+
* It is a DECLARATION, not a second way to configure memory: the layering is still `scopes` and
|
|
545
|
+
* `writeScope`, and this key only lets the engine refuse the one misconfiguration those two cannot
|
|
546
|
+
* express — an assembler that derived a project key and then left `writeScope` to its default, so
|
|
547
|
+
* every entry the session writes lands in the shared layer instead of the project's.
|
|
548
|
+
*/
|
|
549
|
+
export type MemoryProjectKey = {
|
|
550
|
+
scope: string;
|
|
551
|
+
absent?: never;
|
|
552
|
+
} | {
|
|
553
|
+
absent: MemoryProjectKeyAbsentReason;
|
|
554
|
+
scope?: never;
|
|
555
|
+
};
|
|
529
556
|
/** The memory spec accepted on `TaskSpec.memory`. RB-479-B② (ruled 2026-08-02): ONE form —
|
|
530
557
|
* the legacy singular `scope` field is REMOVED; `scopes` (ordered read layers) is the only spelling.
|
|
531
558
|
* An input still carrying an own `scope` property is REFUSED by {@link normalizeMemorySpec}
|
|
@@ -573,6 +600,24 @@ export type MemorySpecInput = {
|
|
|
573
600
|
* prepare, strictly BEFORE any record lands (a denied declaration leaves zero record residue).
|
|
574
601
|
*/
|
|
575
602
|
capture?: "off";
|
|
603
|
+
/**
|
|
604
|
+
* The assembly point's statement about this task's PROJECT IDENTITY ({@link MemoryProjectKey}).
|
|
605
|
+
* Two things happen to it and nothing else: a `{ scope }` arm must EQUAL `writeScope` (a
|
|
606
|
+
* read-only layering excepted), or the task refuses to start with
|
|
607
|
+
* `config.memory_write_scope_mismatch`; an `{ absent }` arm mints one operator notice
|
|
608
|
+
* (`memory.project_scope_absent`) naming the reason, and the layering is used as written.
|
|
609
|
+
*
|
|
610
|
+
* Why this rather than "derive writeScope from it": the layering is the deployment's to compose,
|
|
611
|
+
* and a key that quietly re-pointed the write face would be a second configuration channel. What
|
|
612
|
+
* it closes is the SILENT direction — an omitted `writeScope` defaults to the LAST scope, so an
|
|
613
|
+
* assembler that derived a project key and forgot to pin the write face writes every entry of
|
|
614
|
+
* every project into the shared layer, and nothing says so. Absent ⇒ the spec reads as before.
|
|
615
|
+
*
|
|
616
|
+
* Any other shape — both arms, neither, an empty `scope`, a reason outside
|
|
617
|
+
* {@link MEMORY_PROJECT_KEY_ABSENT_REASONS} — is REFUSED loudly
|
|
618
|
+
* (`config.memory_project_key_spelling`), never folded to "no key stated".
|
|
619
|
+
*/
|
|
620
|
+
projectKey?: MemoryProjectKey;
|
|
576
621
|
};
|
|
577
622
|
/**
|
|
578
623
|
* Normalize {@link import("./types.js").TaskSpec.memory} into ONE canonical
|
|
@@ -592,6 +637,21 @@ export type MemorySpecInput = {
|
|
|
592
637
|
*
|
|
593
638
|
*/
|
|
594
639
|
export declare function normalizeMemorySpec(input: MemorySpecInput | undefined): NormalizedMemorySpec | undefined;
|
|
640
|
+
/**
|
|
641
|
+
* The operator disclosure for a declared-absent project key (`memory.project_scope_absent`,
|
|
642
|
+
* audience `operator`): the deployment stated it could derive no project identity for this working
|
|
643
|
+
* root, so the layering it sent is used exactly as written — typically writing into a layer shared
|
|
644
|
+
* with every other project. The notice states the deployment's own reason word and the root it was
|
|
645
|
+
* about; whether that reading is correct is a question about how the deployment resolves working
|
|
646
|
+
* roots, which is why nobody is judged here. One per prepared leg, at the declaration — not at the
|
|
647
|
+
* mount, because the declaration is already the fact.
|
|
648
|
+
*/
|
|
649
|
+
export declare function memoryProjectScopeAbsentNotice(input: {
|
|
650
|
+
reason: MemoryProjectKeyAbsentReason;
|
|
651
|
+
taskRoot: string;
|
|
652
|
+
sessionId: string;
|
|
653
|
+
writeScope: string | null;
|
|
654
|
+
}): EngineNotice;
|
|
595
655
|
/** Max bytes of memory injected into the prompt; larger is truncated with a marker. (CodeWhale: 100 KiB)
|
|
596
656
|
* Exported so the inject-all de-dup seeding (prepare-task.ts) can detect truncation and NOT seed dropped
|
|
597
657
|
* (truncated-out) note ids as "already surfaced" — see the MINOR-1 audit fix there. */
|
package/dist/core/memory.js
CHANGED
|
@@ -363,6 +363,7 @@ export function parseNoteTimestamp(ts) {
|
|
|
363
363
|
const ms = Date.parse(`${ts.replace(" ", "T")}:00Z`);
|
|
364
364
|
return Number.isFinite(ms) ? { mtimeMs: ms } : { mtimeMs: 0, timestampMissing: true };
|
|
365
365
|
}
|
|
366
|
+
export const MEMORY_PROJECT_KEY_ABSENT_REASONS = ["not_git", "bare", "unreadable"];
|
|
366
367
|
export function normalizeMemorySpec(input) {
|
|
367
368
|
if (!input)
|
|
368
369
|
return undefined;
|
|
@@ -385,6 +386,7 @@ export function normalizeMemorySpec(input) {
|
|
|
385
386
|
e.code = "config.memory_capture_spelling";
|
|
386
387
|
throw e;
|
|
387
388
|
}
|
|
389
|
+
const projectKey = readProjectKey(input);
|
|
388
390
|
const raw = input.scopes ?? [];
|
|
389
391
|
const scopes = [];
|
|
390
392
|
const seen = new Set();
|
|
@@ -421,13 +423,56 @@ export function normalizeMemorySpec(input) {
|
|
|
421
423
|
...(input.execIsExternalContent === true ? { execIsExternalContent: true } : {}),
|
|
422
424
|
};
|
|
423
425
|
const origins = input.scopeOrigins !== undefined ? { scopeOrigins: input.scopeOrigins } : {};
|
|
426
|
+
if (projectKey?.scope !== undefined && writeScope !== null && writeScope !== projectKey.scope) {
|
|
427
|
+
const e = new Error(`TaskSpec.memory.projectKey declares project scope ${JSON.stringify(projectKey.scope)} while writeScope is ${JSON.stringify(writeScope)} — ` +
|
|
428
|
+
`every entry this session writes would land in ${JSON.stringify(writeScope)} instead of the project's own layer. ` +
|
|
429
|
+
`Pin writeScope to the project scope (or drop projectKey if the layering is deliberate). Refusing to start mis-addressed.`);
|
|
430
|
+
e.code = "config.memory_write_scope_mismatch";
|
|
431
|
+
throw e;
|
|
432
|
+
}
|
|
424
433
|
const capture = input.capture === "off" ? { capture: "off" } : {};
|
|
434
|
+
const project = projectKey !== undefined ? { projectKey } : {};
|
|
425
435
|
if (input.scopeContract === "v2") {
|
|
426
436
|
for (const key of writeScope !== null ? [...scopes, writeScope] : scopes)
|
|
427
437
|
parseScopeKey(key);
|
|
428
|
-
return { scopes, writeScope, enabled: input.enabled !== false, scopeContract: "v2", ...contentSafety, ...origins, ...capture };
|
|
438
|
+
return { scopes, writeScope, enabled: input.enabled !== false, scopeContract: "v2", ...contentSafety, ...origins, ...capture, ...project };
|
|
439
|
+
}
|
|
440
|
+
return { scopes, writeScope, enabled: input.enabled !== false, ...contentSafety, ...origins, ...capture, ...project };
|
|
441
|
+
}
|
|
442
|
+
function readProjectKey(input) {
|
|
443
|
+
const raw = input.projectKey;
|
|
444
|
+
if (raw === undefined)
|
|
445
|
+
return undefined;
|
|
446
|
+
const refuse = (why) => {
|
|
447
|
+
const e = new Error(`TaskSpec.memory.projectKey must be exactly { scope: "<non-empty>" } or { absent: ${MEMORY_PROJECT_KEY_ABSENT_REASONS.map((r) => JSON.stringify(r)).join(" | ")} } — ${why}. ` +
|
|
448
|
+
`Refusing to guess whether this deployment has a project identity.`);
|
|
449
|
+
e.code = "config.memory_project_key_spelling";
|
|
450
|
+
throw e;
|
|
451
|
+
};
|
|
452
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
453
|
+
return refuse(`got ${raw === null ? "null" : Array.isArray(raw) ? "an array" : typeof raw}`);
|
|
454
|
+
const scope = raw.scope;
|
|
455
|
+
const absent = raw.absent;
|
|
456
|
+
if ((scope === undefined) === (absent === undefined))
|
|
457
|
+
return refuse(absent === undefined ? "neither arm was spelled" : "both arms were spelled");
|
|
458
|
+
if (absent !== undefined) {
|
|
459
|
+
if (typeof absent !== "string" || !MEMORY_PROJECT_KEY_ABSENT_REASONS.includes(absent)) {
|
|
460
|
+
return refuse(`the absence reason ${typeof absent === "string" ? JSON.stringify(absent.slice(0, 64)) : typeof absent} is not one of the stated words`);
|
|
461
|
+
}
|
|
462
|
+
return { absent: absent };
|
|
429
463
|
}
|
|
430
|
-
|
|
464
|
+
if (typeof scope !== "string" || scope.trim() === "")
|
|
465
|
+
return refuse(typeof scope === "string" ? "the project scope is blank" : `the project scope is ${typeof scope}`);
|
|
466
|
+
return { scope: scope.trim() };
|
|
467
|
+
}
|
|
468
|
+
export function memoryProjectScopeAbsentNotice(input) {
|
|
469
|
+
return {
|
|
470
|
+
code: "memory.project_scope_absent",
|
|
471
|
+
message: `No project identity was derived for this session's working root (${input.reason}), so its memory layering is used as declared: ` +
|
|
472
|
+
`${input.writeScope === null ? "the session writes nothing (read-only layering)" : `entries this session writes go to the shared layer ${JSON.stringify(input.writeScope)}`}. ` +
|
|
473
|
+
`If this root should have its own project memory, fix how the deployment resolves it — nothing here retries.`,
|
|
474
|
+
detail: { reason: input.reason, taskRoot: input.taskRoot, sessionId: input.sessionId, ...(input.writeScope !== null ? { writeScope: input.writeScope } : {}) },
|
|
475
|
+
};
|
|
431
476
|
}
|
|
432
477
|
export const MAX_MEMORY_BYTES = 100 * 1024;
|
|
433
478
|
export const MEMORY_WRAPPER_TAGS = ["user_memory"];
|
|
@@ -221,6 +221,19 @@ export interface RuleSyncJoinDelta {
|
|
|
221
221
|
* calling layer, and closes identically for every caller.
|
|
222
222
|
*/
|
|
223
223
|
export type RuleWriteDelta = RuleAddDelta | RuleTightenAddDelta | RuleDeleteDelta | RuleSyncJoinDelta;
|
|
224
|
+
/**
|
|
225
|
+
* The refusal every backend owes a delta whose `kind` is outside {@link RuleWriteDelta} — minted here so
|
|
226
|
+
* the two bundled backends (and an out-of-repo twin) answer an unnamed operation with ONE sentence
|
|
227
|
+
* instead of each guessing.
|
|
228
|
+
*
|
|
229
|
+
* The parameter is `never` on purpose: the only way to reach it is from a `default` arm the type system
|
|
230
|
+
* has already proved unreachable, so the factory doubles as the compile-time fence (a fifth arm added to
|
|
231
|
+
* the union stops satisfying `never` and reds at every backend's switch, naming the missing word). The
|
|
232
|
+
* runtime half is not decoration — a delta arrives as DATA (a foreign caller, a replayed record, a
|
|
233
|
+
* deserialized join), and the alternative to throwing is folding an unnamed kind as whichever arm
|
|
234
|
+
* an if/else chain happens to end with: a write nobody defined, landed under a name nobody checked.
|
|
235
|
+
*/
|
|
236
|
+
export declare function unknownRuleWriteDeltaError(delta: never): Error;
|
|
224
237
|
/**
|
|
225
238
|
* The backend write face. Exported as part of the BACKEND CONTRACT (ruled 2026-08-10) so an
|
|
226
239
|
* out-of-repo store implementation hangs the same face the file backend does, instead of mirroring the
|
|
@@ -531,6 +544,22 @@ export declare function ruleStoreChecksum(payload: unknown): Promise<string>;
|
|
|
531
544
|
*/
|
|
532
545
|
export declare const REMOVAL_LIVENESS_WORDS: readonly ["yes", "no", "unknown"];
|
|
533
546
|
export type RemovalLiveness = (typeof REMOVAL_LIVENESS_WORDS)[number];
|
|
547
|
+
/**
|
|
548
|
+
* What a terminal write FAILURE may claim about the bytes — the failure half of the honesty
|
|
549
|
+
* {@link RemovalLiveness} keeps on the success half, spelled ONCE and read by every write entry here.
|
|
550
|
+
*
|
|
551
|
+
* `"no"` = the store ITSELF answered: a face that cannot write at all, a target the type face refuses,
|
|
552
|
+
* a read that failed before any write, a refused preflight, an epoch seal, a compare-and-set that lost
|
|
553
|
+
* every attempt. Each of those is the store saying nothing landed, so saying it is a fact.
|
|
554
|
+
* `"unknown"` = the apply THREW, which proves nothing either way (a backend can commit and then lose
|
|
555
|
+
* its acknowledgement), or the read-back that would have settled it could not be taken.
|
|
556
|
+
*
|
|
557
|
+
* The word exists because the caller's two branches — retry, or reconcile with `list()` — are not
|
|
558
|
+
* derivable from the failure PROSE. A consumer reading the strength out of the sentence is parsing a
|
|
559
|
+
* message written for a person, and every such reader has to be updated by hand when the sentence is.
|
|
560
|
+
*/
|
|
561
|
+
export declare const RULE_WRITE_COMMITTED_WORDS: readonly ["no", "unknown"];
|
|
562
|
+
export type RuleWriteCommitted = (typeof RULE_WRITE_COMMITTED_WORDS)[number];
|
|
534
563
|
/**
|
|
535
564
|
* The result of a removal.
|
|
536
565
|
*
|
|
@@ -548,21 +577,32 @@ export type RemoveResult =
|
|
|
548
577
|
rev: number;
|
|
549
578
|
stillLive: RemovalLiveness;
|
|
550
579
|
}
|
|
551
|
-
/** Nothing to remove — including a repeated delete. No empty tombstone is minted.
|
|
580
|
+
/** Nothing to remove — including a repeated delete. No empty tombstone is minted. `stillLive` is the
|
|
581
|
+
* SAME read-back the `removed` arm reports, taken at the same point in the call ({@link
|
|
582
|
+
* readRuleLiveness}): "there was nothing to remove" is a statement about the snapshot this call read,
|
|
583
|
+
* and an approval landing after it leaves the rule standing. Both 200-shaped arms therefore carry the
|
|
584
|
+
* word, from one source. It is a LATER OBSERVATION, not an atomic absence: `rev` is the revision the
|
|
585
|
+
* decision was taken at, the word is what a second look saw, and the store may change again right
|
|
586
|
+
* after — `yes` beside a `no-op` is the honest pair, not a contradiction. */
|
|
552
587
|
| {
|
|
553
588
|
status: "no-op";
|
|
554
589
|
rev: number;
|
|
590
|
+
stillLive: RemovalLiveness;
|
|
555
591
|
}
|
|
556
|
-
/** Terminal failure, in one of two strengths
|
|
557
|
-
* {@link
|
|
558
|
-
*
|
|
592
|
+
/** Terminal failure, in one of two strengths — word for word the dual of {@link AddResult}'s, and
|
|
593
|
+
* `committed` is the word for it ({@link RuleWriteCommitted}), so the strength is READ, not parsed out
|
|
594
|
+
* of the sentence. When the STORE answered every attempt (the compare-and-set simply lost), nothing
|
|
595
|
+
* landed: `committed: "no"`, and the read-back confirms it in the text. When the apply THREW, a missing
|
|
559
596
|
* tombstone cannot tell a write that never landed from one whose evidence a paired collection erased
|
|
560
597
|
* (`collectBelowFrontier` recycles a tombstone together with the adds it covered), so that outcome is
|
|
561
|
-
* reported INDETERMINATE and the caller reconciles with `list()` rather than
|
|
562
|
-
* read-back itself failed,
|
|
598
|
+
* `committed: "unknown"`, reported INDETERMINATE, and the caller reconciles with `list()` rather than
|
|
599
|
+
* retrying blind. When the read-back itself failed, the word is the one the apply earned — a throw
|
|
600
|
+
* leaves it `unknown`, an answered compare-and-set stays `no`, because an unreadable store cannot
|
|
601
|
+
* un-answer what the store already said. */
|
|
563
602
|
| {
|
|
564
603
|
status: "failed";
|
|
565
604
|
error: string;
|
|
605
|
+
committed: RuleWriteCommitted;
|
|
566
606
|
};
|
|
567
607
|
/**
|
|
568
608
|
* Remove a persisted rule.
|
|
@@ -602,7 +642,7 @@ export type RuleAddLanding = {
|
|
|
602
642
|
| {
|
|
603
643
|
landed: false;
|
|
604
644
|
reason: string;
|
|
605
|
-
committed:
|
|
645
|
+
committed: RuleWriteCommitted;
|
|
606
646
|
};
|
|
607
647
|
/**
|
|
608
648
|
* Land ONE add — the terminal write both doors take (the consent protocol's redemption and the
|
|
@@ -652,10 +692,13 @@ export type AddResult =
|
|
|
652
692
|
* survives the tombstone a concurrent removal laid on it — but a sync collection can erase even
|
|
653
693
|
* that, so a dot it cannot find is reported INDETERMINATE rather than as a confirmed nothing, and
|
|
654
694
|
* the caller reconciles with `list()` instead of retrying blind (a blind retry mints a fresh dot and
|
|
655
|
-
* can reinstate a restriction somebody had just removed).
|
|
695
|
+
* can reinstate a restriction somebody had just removed). `committed` ({@link RuleWriteCommitted}) is
|
|
696
|
+
* which of the two this is, as a WORD — the landing's own answer, passed through rather than re-derived,
|
|
697
|
+
* so this entry and the consent protocol's door cannot disagree about a write they both took. */
|
|
656
698
|
| {
|
|
657
699
|
status: "failed";
|
|
658
700
|
error: string;
|
|
701
|
+
committed: RuleWriteCommitted;
|
|
659
702
|
};
|
|
660
703
|
/**
|
|
661
704
|
* Add a persisted rule in ONE step, with no approval record.
|