@sema-agent/core 5.38.0 → 5.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +182 -10
  2. package/dist/agents/send-message-tool.d.ts +8 -0
  3. package/dist/agents/send-message-tool.js +8 -0
  4. package/dist/agents/teacher.js +9 -3
  5. package/dist/agents/verify.js +9 -3
  6. package/dist/core/checkpoint-store.d.ts +12 -0
  7. package/dist/core/governance-codes.js +2 -0
  8. package/dist/core/hooks.d.ts +23 -0
  9. package/dist/core/hooks.js +53 -4
  10. package/dist/core/mailbox-store.d.ts +39 -0
  11. package/dist/core/mailbox-store.js +9 -0
  12. package/dist/core/memory-engine/engine.d.ts +27 -0
  13. package/dist/core/memory-engine/engine.js +103 -1
  14. package/dist/core/memory-engine/export-bundle.d.ts +192 -0
  15. package/dist/core/memory-engine/export-bundle.js +306 -0
  16. package/dist/core/memory-engine/file-backend.d.ts +178 -1
  17. package/dist/core/memory-engine/file-backend.js +637 -6
  18. package/dist/core/memory-engine/index.d.ts +2 -1
  19. package/dist/core/memory-engine/index.js +1 -0
  20. package/dist/core/memory-engine/layout.d.ts +89 -1
  21. package/dist/core/memory-engine/layout.js +131 -1
  22. package/dist/core/memory-engine/memory-backend-contract.d.ts +1 -1
  23. package/dist/core/memory-engine/memory-backend-contract.js +52 -0
  24. package/dist/core/memory-engine/tools.js +8 -1
  25. package/dist/core/permission-rule-consent.d.ts +27 -4
  26. package/dist/core/permission-rule-consent.js +41 -4
  27. package/dist/core/permission-rule-model.d.ts +7 -1
  28. package/dist/core/runner/prepare-task.js +24 -3
  29. package/dist/core/runner/runtask.js +5 -0
  30. package/dist/core/runner/synthetic-tools.js +3 -1
  31. package/dist/core/runner/tool-disclosure.js +2 -1
  32. package/dist/core/sensitive-path-policy.js +3 -3
  33. package/dist/core/store-contracts/mailbox-store-contract.d.ts +29 -1
  34. package/dist/core/store-contracts/mailbox-store-contract.js +78 -0
  35. package/dist/core/types.d.ts +21 -0
  36. package/dist/core/write-protect.d.ts +73 -0
  37. package/dist/core/write-protect.js +195 -0
  38. package/dist/index.d.ts +4 -3
  39. package/dist/index.js +4 -3
  40. package/dist/orchestration/governance-baseline-validity.d.ts +44 -0
  41. package/dist/orchestration/governance-baseline-validity.js +55 -0
  42. package/dist/orchestration/run-workflow-tool.js +33 -8
  43. package/dist/orchestration/workflow-script-runner.js +9 -4
  44. package/dist/tools/fs/read-deny.d.ts +15 -5
  45. package/dist/tools/fs/read-deny.js +33 -12
  46. package/dist/tools/fs/safety.d.ts +5 -2
  47. package/dist/tools/fs/safety.js +5 -3
  48. package/dist/tools/fs/search.d.ts +33 -0
  49. package/dist/tools/fs/search.js +72 -0
  50. package/package.json +1 -1
  51. package/test/export-surface.snapshot.json +21 -1
@@ -28,8 +28,9 @@ import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentList
28
28
  import { inlineUntrusted } from "../untrusted-text.js";
29
29
  import { policyAskClassOf } from "../ask-class.js";
30
30
  import { emitTrace } from "../trace.js";
31
- import { createSessionRulePolicy } from "./session-rule-policy.js";
31
+ import { createSessionRulePolicy, PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
32
32
  import { cloneObserverInput, createHookEnvCapabilities, createPreToolUseConstraintPolicy, formatHookFeedback, mintHookInvocationIdentity, persistedRuleMandateOf, runToolGate } from "../hooks.js";
33
+ import { createWriteProtectionCheck } from "../write-protect.js";
33
34
  import { orgRuleVerdictFor } from "../permission-rule-org.js";
34
35
  import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
35
36
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
@@ -326,6 +327,13 @@ function orgRevisionEvidenceOf(resolution, onDefect) {
326
327
  function persistedRuleHitOf(admitting) {
327
328
  return admitting === undefined ? undefined : { rule: admitting.rule, dots: admitting.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) };
328
329
  }
330
+ function cwdConflictsRestoreError(requestedCwd) {
331
+ const e = new Error(`RunInternals.requestedCwd ("${requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
332
+ `the restored workspace's own mount path is authoritative for the task root, so a requested cwd on this leg ` +
333
+ `would be ignored (or worse, probed against an unrestored environment). Drop requestedCwd on resume legs.`);
334
+ e.code = "config.cwd_conflicts_restore";
335
+ return e;
336
+ }
329
337
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
330
338
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
331
339
  spec = doors.spec;
@@ -420,6 +428,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
420
428
  e.code = "config.cwd_unsupported";
421
429
  throw e;
422
430
  }
431
+ if (internals?.requestedCwd !== undefined && resume?.workspaceHandle !== undefined) {
432
+ await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-conflicts-restore leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
433
+ throw cwdConflictsRestoreError(internals.requestedCwd);
434
+ }
423
435
  try {
424
436
  ownedEnv = deps.executionEnvFactory
425
437
  ? await deps.executionEnvFactory({
@@ -1247,6 +1259,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1247
1259
  if (c.ok)
1248
1260
  additionalRootsCanonical.push(c.value);
1249
1261
  }
1262
+ for (const a of [additionalRootsCanonical, additionalReadRootsCanonical])
1263
+ a.splice(0, a.length, ...new Set(a));
1250
1264
  const readFileState = new Map((resume?.seed.readFileState ?? []).map(([k, v]) => [rebaseRestoredPath(k), v]));
1251
1265
  readFileStateForCheckpoint = readFileState;
1252
1266
  seedContextFiles = async (files) => {
@@ -1413,6 +1427,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1413
1427
  toolEffects.set("TaskStop", "write");
1414
1428
  tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
1415
1429
  if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
1430
+ toolEffects.set(SEND_MESSAGE_TOOL_NAME, "write");
1431
+ axisExplicitNegatives.set(SEND_MESSAGE_TOOL_NAME, { ...axisExplicitNegatives.get(SEND_MESSAGE_TOOL_NAME), egress: false });
1416
1432
  const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
1417
1433
  const reviveSpawn = delegationForRevive !== undefined && deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined
1418
1434
  ? async (req) => {
@@ -2813,7 +2829,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2813
2829
  list.push({ cls, layer });
2814
2830
  foldAskClasses.set(toolCallId, list);
2815
2831
  };
2816
- const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName);
2832
+ const sandboxBoundaryCapable = (toolName) => egressTools.has(toolName) || toolName.includes("__") || ownToolNames.has(toolName) || toolName === SEND_MESSAGE_TOOL_NAME;
2817
2833
  const emitSandboxAdmitted = (info) => {
2818
2834
  emitTrace(deps.tracer, () => ({
2819
2835
  kind: "permission.sandbox_admitted",
@@ -4220,13 +4236,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4220
4236
  if (eff !== undefined && !toolEffects.has(t.name))
4221
4237
  toolEffects.set(t.name, eff);
4222
4238
  }
4239
+ const writeProtectionCheck = createWriteProtectionCheck(deps.writeProtectedPaths);
4240
+ const writeProtectionArmed = writeProtectionCheck !== undefined && tools.some((t) => PATH_CONFINABLE_WRITE_TOOLS.has(t.name));
4223
4241
  toolCallGateArmedRef.armed =
4224
4242
  effectivePolicy !== undefined ||
4225
4243
  hooks?.preToolUse !== undefined ||
4226
4244
  egressTools.size > 0 ||
4227
4245
  irreversibleTools.size > 0 ||
4228
4246
  spec.enablePlanMode === true ||
4229
- complianceDenies.has("web_fetch");
4247
+ complianceDenies.has("web_fetch") ||
4248
+ writeProtectionArmed;
4230
4249
  if (toolCallGateArmedRef.armed) {
4231
4250
  harness.on("tool_call", async (e) => {
4232
4251
  blockedToolCalls.delete(e.toolCallId);
@@ -4269,6 +4288,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4269
4288
  suspendAsk,
4270
4289
  resolveContentAsk,
4271
4290
  egress: egressTools.has(e.toolName),
4291
+ peerMessage: e.toolName === SEND_MESSAGE_TOOL_NAME,
4292
+ ...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
4272
4293
  irreversibility: irreversibilityTier.get(e.toolName),
4273
4294
  reversibilityProbe: reversibilityProbes.get(e.toolName),
4274
4295
  abortSignal: abortController.signal,
@@ -4126,6 +4126,11 @@ export class Runner {
4126
4126
  if (cp.state.workspaceHandle !== undefined && this.deps.executionEnvFactory === undefined) {
4127
4127
  throw new CheckpointError("checkpoint.unsupported_version", "checkpoint has a remote workspaceHandle but no RunnerDeps.executionEnvFactory is wired to rebuild the env", { reason: "env_factory_missing" });
4128
4128
  }
4129
+ if (cp.state.workspaceHandle !== undefined && internals?.requestedCwd !== undefined) {
4130
+ throw new CheckpointError("checkpoint.cwd_conflicts_restore", `internals.requestedCwd ("${internals.requestedCwd}") cannot be combined with a checkpoint workspace restore — ` +
4131
+ "the restored workspace's own mount path is authoritative for the task root. Drop requestedCwd on resume legs " +
4132
+ "(the checkpoint stays pending and is resumable without it).");
4133
+ }
4129
4134
  if (cp.state.inheritedGate?.requiresParentConstraint === true) {
4130
4135
  const supplied = internals?.inheritedGate?.parentConstraints?.length ?? 0;
4131
4136
  if (supplied === 0) {
@@ -196,7 +196,9 @@ export function createSkillTool(skills, scope) {
196
196
  const name = String(a.skill ?? "");
197
197
  const skill = byName.get(name);
198
198
  if (!skill) {
199
- throw new Error(`Unknown skill "${name}". Available skills: ${names}.`);
199
+ throw new Error(`Unknown skill "${name}". Available skills: ${names}. ` +
200
+ `(Only skills registered with this deployment are loadable — other products' skills directories are not read. ` +
201
+ `If the user wants one of those here, read its source file and recreate its content for this deployment instead.)`);
200
202
  }
201
203
  if (scope && skill.manifest) {
202
204
  scope.push({ kind: "manifest", manifest: skill.manifest });
@@ -45,7 +45,8 @@ export function classifyDeferred(opts) {
45
45
  if (!pinned.has(name))
46
46
  deferred.add(name);
47
47
  if (opts.deferMode === "auto") {
48
- const candidates = opts.fullTools.filter((t) => !deferred.has(t.name) && !pinned.has(t.name));
48
+ const callerNames = new Set(opts.specs.map((s) => s.name));
49
+ const candidates = opts.fullTools.filter((t) => !deferred.has(t.name) && !pinned.has(t.name) && callerNames.has(t.name));
49
50
  const inlineFace = opts.fullTools.filter((t) => !deferred.has(t.name));
50
51
  const total = inlineFace.reduce((n, t) => n + inlinedChars(t), 0);
51
52
  const window = (opts.model.contextTokens ?? opts.model.contextWindow ?? 0) * CHARS_PER_TOKEN;
@@ -39,8 +39,8 @@ function compilePatterns(patterns) {
39
39
  }
40
40
  return out;
41
41
  }
42
- function matchSensitive(canonicalKey, compiled) {
43
- return matchSegmentPatterns(canonicalKey, compiled);
42
+ function matchSensitive(canonicalKey, compiled, aliasResolved) {
43
+ return matchSegmentPatterns(canonicalKey, compiled, { aliasResolved });
44
44
  }
45
45
  export function createSensitivePathPolicy(opts) {
46
46
  const compiled = compilePatterns(opts.patterns);
@@ -64,7 +64,7 @@ export function createSensitivePathPolicy(opts) {
64
64
  }
65
65
  return { action: "allow" };
66
66
  }
67
- const hit = matchSensitive(canon.key, compiled);
67
+ const hit = matchSensitive(canon.key, compiled, canon.aliasResolved === true);
68
68
  if (hit) {
69
69
  return {
70
70
  action: "deny",
@@ -1,4 +1,4 @@
1
- import type { MailboxStore } from "../mailbox-store.js";
1
+ import { type MailboxStore } from "../mailbox-store.js";
2
2
  import { type ContractAssertionRunner } from "./contract-harness.js";
3
3
  /**
4
4
  * design/159 S1 — the cross-backend {@link MailboxStore} contract, extracted VERBATIM from
@@ -46,3 +46,31 @@ export declare function mailboxAckOwnershipContract(mk: () => MailboxStore, runA
46
46
  * 放宽」:核心层没有一条为了任何后端而弱化。第三方后端若有租户轴,应当也挂这一层。
47
47
  */
48
48
  export declare function mailboxBundledOnlyContract(mk: () => MailboxStore, runAssertion?: ContractAssertionRunner): Promise<void>;
49
+ export interface MailboxTombstonedRecipientContractHooks {
50
+ /** Put `(scope, handle)` into the deployment's PRE-DELETE state — whatever that is for this
51
+ * backend (a cascade marking the session row, a `deleting` column, a tombstone table). Core does
52
+ * not name the mechanism, only what `append` must then do. */
53
+ tombstone: (store: MailboxStore, scope: string, handle: string) => Promise<void>;
54
+ runAssertion?: ContractAssertionRunner;
55
+ }
56
+ /**
57
+ * T2 可选能力层(pre-delete 分册)—— 只挂**看得见收件人生命周期**的后端:它的 mailbox 行与某个
58
+ * 删除级联要清掉的 session 行同生共死(server 的留存删除级联是首个此形消费方)。三个捆绑后端都
59
+ * 没有这个视角,因此都不挂——**这不是「做不到所以放宽」**:没有预删除态的后端根本没有可拒的对象,
60
+ * 而有这个态却静默接受的后端,拿到的 seq 是一张级联马上就要撕掉的回执(`append` 是 durable-first,
61
+ * 回执即承诺),且没有任何其它面会说出这件事。
62
+ *
63
+ * 契约不规定态怎么置(那是部署自己的删除协议),只钉「拒」这件事的三条轴 —— **三条一起列在这里,
64
+ * 是因为逐条补的过程本身证明了单看一条会假绿**(每加一条,都有一个能过掉前面全部用例的错误实现被
65
+ * 抓出来):
66
+ * 1. **拒因轴**:必须拒,且每条路径上都带同一个码(空箱与非空箱都算路径);
67
+ * 2. **副作用轴**:被拒的 append 对箱零改动 —— 不入箱(幽灵投递)、不清箱(删除窗口内静默丢)、
68
+ * 也不动活租约(动了就撬开 X-1 的单消费者围栏,同一批消息会被二次投递);
69
+ * 3. **收件人边界轴**:态属于一个 `(scope, handle)` 整体,且这个整体是**单射**的 —— 兄弟 handle
70
+ * 不受牵连、跨租户同名不受牵连、拼键歧义不得把两个收件人折成一个。
71
+ * 新增用例请按这三条轴归位;要加第四条轴,先说清它是哪一类假绿。
72
+ *
73
+ * 受众前提与 {@link mailboxBundledOnlyContract} 同线:后端有自己的租户轴(生命周期可见的后端就是
74
+ * 服务端形态的 store),边界轴的两条用例要用两个 scope 构造。
75
+ */
76
+ export declare function mailboxTombstonedRecipientContract(mk: () => MailboxStore, hooks: MailboxTombstonedRecipientContractHooks): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { strict as assert } from "node:assert";
2
+ import { MAILBOX_TOMBSTONED_RECIPIENT_CODE } from "../mailbox-store.js";
2
3
  import { beginContract } from "./contract-harness.js";
3
4
  export const MAILBOX_CONTRACT_SCOPE = "default";
4
5
  const msg = (content, sentAt = 1000) => ({ content, sentAt });
@@ -191,3 +192,80 @@ export async function mailboxBundledOnlyContract(mk, runAssertion) {
191
192
  });
192
193
  await settle();
193
194
  }
195
+ export async function mailboxTombstonedRecipientContract(mk, hooks) {
196
+ const { run: runRaw, settle } = beginContract(hooks.runAssertion);
197
+ const run = (name, fn) => runRaw(name, () => withStores(mk, fn));
198
+ const S = MAILBOX_CONTRACT_SCOPE;
199
+ const codeOf = (e) => e?.code;
200
+ run("预删除态的收件人:append 响亮拒且带码(既不静默收下级联即将删掉的消息,也不裸抛)", async (make) => {
201
+ const s = make();
202
+ await hooks.tombstone(s, S, "a1");
203
+ let thrown;
204
+ try {
205
+ await s.append(S, "a1", msg("m1"));
206
+ }
207
+ catch (e) {
208
+ thrown = e;
209
+ }
210
+ assert.notEqual(thrown, undefined, "静默收下 = 给发件人一张级联马上撕掉的回执");
211
+ assert.equal(codeOf(thrown), MAILBOX_TOMBSTONED_RECIPIENT_CODE, "裸抛与拒同形:消费方要能按码分诊");
212
+ let again;
213
+ try {
214
+ await s.append(S, "a1", msg("m2"));
215
+ }
216
+ catch (e) {
217
+ again = e;
218
+ }
219
+ assert.equal(codeOf(again), MAILBOX_TOMBSTONED_RECIPIENT_CODE);
220
+ });
221
+ run("拒的粒度是收件人,不是开关:同 scope 下另一个活收件人照常收信", async (make) => {
222
+ const s = make();
223
+ await hooks.tombstone(s, S, "a1");
224
+ assert.equal(await s.append(S, "a2", msg("still-live")), 1, "活收件人不受邻居的删除级联牵连");
225
+ assert.equal(await s.peekCount(S, "a2"), 1);
226
+ });
227
+ run("收件人身份是 (scope, handle) 整体:A 租户的删除级联不得拒掉 B 租户的同名收件人", async (make) => {
228
+ const s = make();
229
+ await hooks.tombstone(s, "tenant-a", "a1");
230
+ assert.equal(await s.append("tenant-b", "a1", msg("other-tenant")), 1, "同名 handle 在另一个租户下是另一个收件人");
231
+ assert.equal(await s.peekCount("tenant-b", "a1"), 1);
232
+ });
233
+ run("两件套还得是单射的:把 (scope, handle) 拼成一个字符串记态,不得让两个不同收件人撞成一个", async (make) => {
234
+ const s = make();
235
+ await hooks.tombstone(s, "tenant a", "b");
236
+ assert.equal(await s.append("tenant", "a b", msg("distinct-recipient")), 1, "拼键歧义不得把另一个收件人一起拒掉");
237
+ assert.equal(await s.peekCount("tenant", "a b"), 1);
238
+ });
239
+ run("副作用轴 · 活租约:被拒的 append 不得动别人手上的租约(单消费者围栏不因删除窗口松开)", async (make) => {
240
+ const s = make();
241
+ await s.append(S, "a1", msg("in-flight"));
242
+ const held = await s.claimLease(S, "a1", "consumer-A", 60_000, 10_000);
243
+ assert.equal(held?.messages.length, 1);
244
+ await hooks.tombstone(s, S, "a1");
245
+ await assert.rejects(s.append(S, "a1", msg("after-tombstone", 2_000)), "预删除态下的 append 必须拒");
246
+ assert.equal(await s.claimLease(S, "a1", "rival", 60_000, 11_000), null, "A 的活租约必须仍然 fence 住别人");
247
+ await s.ack(S, "a1", "consumer-A", held.maxSeq);
248
+ assert.equal(await s.peekCount(S, "a1"), 0);
249
+ });
250
+ run("拒的是入口,不是清仓:被拒的 append 零副作用,已入箱的消息按原 seq/内容原封不动", async (make) => {
251
+ const s = make();
252
+ const seq = await s.append(S, "a1", msg("parked-before"));
253
+ await hooks.tombstone(s, S, "a1");
254
+ let thrown;
255
+ let refused = false;
256
+ try {
257
+ await s.append(S, "a1", msg("after-tombstone", 2_000));
258
+ }
259
+ catch (e) {
260
+ refused = true;
261
+ thrown = e;
262
+ }
263
+ assert.equal(refused, true, "预删除态下的 append 必须拒");
264
+ assert.equal(codeOf(thrown), MAILBOX_TOMBSTONED_RECIPIENT_CODE, "箱非空时的拒同样要带码");
265
+ assert.equal(await s.peekCount(S, "a1"), 1, "被拒的 append 不得入箱(幽灵投递),也不得清箱");
266
+ const lease = await s.claimLease(S, "a1", "w1", 60_000, 10_000);
267
+ assert.deepEqual(lease?.messages.map((m) => m.seq), [seq]);
268
+ assert.deepEqual(lease?.messages.map((m) => m.content), ["parked-before"], "取到的仍是拒之前那一条");
269
+ });
270
+ await settle();
271
+ }
@@ -4785,6 +4785,27 @@ export interface RunnerDeps {
4785
4785
  * ignored). Same deployment-only seat, clamp argument and checkpoint posture as the tiers key.
4786
4786
  */
4787
4787
  readDenyBuiltinExclude?: readonly string[];
4788
+ /**
4789
+ * backlog #286 (#279 — CC 2.1.233 `DANGEROUS_FILES`/`DANGEROUS_DIRECTORIES`/
4790
+ * `DANGEROUS_DIRECTORY_PATHS` parity): the WRITE-protection table. DEFAULT-ON: a path-confinable
4791
+ * write (Write/Edit/NotebookEdit) whose target lands on a table row has a surviving `allow`
4792
+ * demoted to `ask` at the tool gate (`decisionReason: "safety"`; a deny/ask verdict is untouched;
4793
+ * approval flows the ordinary ask-resolution chain — classifier, blanket `onAsk`, durable park —
4794
+ * with no `requiresRealApproval` mandate). Absent =
4795
+ * {@link import("./write-protect.js").WRITE_PROTECTED_DEFAULT_TABLE} (the CC triple verbatim +
4796
+ * the two argued sema rows). This key IS the whole-table escape hatch, deployment seat ONLY (no
4797
+ * TaskSpec twin, no governed-workflow channel): `[]` = no table (explicit and legal); a non-empty
4798
+ * list REPLACES the built-in table whole (compose additions as
4799
+ * `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`; drop rows by filtering the exported table — the
4800
+ * visible/deletable admin face). Bad values refuse loudly at prepare (#123): garbage shapes,
4801
+ * glob metacharacters (the table speaks LITERAL names — glob semantics live in
4802
+ * `createSensitivePathPolicy`), unknown kinds, impossible kind/name combinations. Matching is
4803
+ * lexical over the spelled target with one case fold (ı/ſ included) — a symlink alias evades it
4804
+ * by construction; the canonicalizing opt-in deny policy remains the hard layer. Not frozen into
4805
+ * checkpoints: a resumed task follows the CURRENT deployment table (an approved parked call
4806
+ * bypasses the gate as always — the human already adjudicated it).
4807
+ */
4808
+ writeProtectedPaths?: readonly import("./write-protect.js").WriteProtectedEntry[];
4788
4809
  /**
4789
4810
  * design/199 件A — the DEPLOYMENT's read-face declaration
4790
4811
  * ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
@@ -0,0 +1,73 @@
1
+ /**
2
+ * How one table row matches the judged path:
3
+ * · `"basename"` — the target's last segment equals the row name (CC `DANGEROUS_FILES` form);
4
+ * · `"segment"` — ANY path segment equals the row name (CC `DANGEROUS_DIRECTORIES` form);
5
+ * · `"segment-run"` — a CONSECUTIVE run of segments equals the row's `/`-separated segments
6
+ * (CC `DANGEROUS_DIRECTORY_PATHS` form, e.g. `.config/git`).
7
+ */
8
+ export type WriteProtectedKind = "basename" | "segment" | "segment-run";
9
+ /** One row of the write-protection table: a LITERAL name and how it matches. The `name` is also the
10
+ * row's stable identity — the string a refusal/ask message cites. */
11
+ export interface WriteProtectedRow {
12
+ readonly name: string;
13
+ readonly kind: WriteProtectedKind;
14
+ }
15
+ /**
16
+ * One deployment-authored entry for the replacement seat (`RunnerDeps.writeProtectedPaths`).
17
+ * String shorthand: a bare name ≡ `{ name, kind: "segment" }` (the WIDER single-segment kind —
18
+ * over-matching is the fail-safe direction for a tighten); a name containing `/` ≡
19
+ * `{ name, kind: "segment-run" }`. Spell `kind: "basename"` explicitly when last-segment-only
20
+ * matching is the intent.
21
+ */
22
+ export type WriteProtectedEntry = string | WriteProtectedRow;
23
+ /** A table hit: which row matched (the row's canonical name + kind). */
24
+ export interface WriteProtectedHit {
25
+ readonly name: string;
26
+ readonly kind: WriteProtectedKind;
27
+ }
28
+ export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
29
+ /**
30
+ * The ONE case fold of this module, applied to BOTH sides of every comparison (table names at
31
+ * compile, path segments at match) — `toLowerCase` plus the two confusable letters CC's fold maps
32
+ * (U+0131 dotless ı → i, U+017F long ſ → s). Deliberately NOT full Unicode confusable folding:
33
+ * that would be a different, wider claim than the one made (the read deny set's ASCII-contract doc
34
+ * states the same boundary for its own fold).
35
+ */
36
+ export declare function foldWriteProtectCase(s: string): string;
37
+ /**
38
+ * Resolve the ACTIVE table under the deployment seat (validated, loud — #123's bad-value states all
39
+ * throw and name the knob). `undefined` = the default table; `[]` = NO table (an explicit, legal
40
+ * posture — the whole-table escape hatch's empty end); a non-empty list REPLACES the default table
41
+ * whole (compose additions as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, drop rows by filtering the
42
+ * exported table — the visible/deletable admin face). Exact duplicates fold (idempotent, not one of
43
+ * #123's bad-value states). Exported so an admin face can preview the effective table under a
44
+ * candidate configuration with the engine's own rules.
45
+ */
46
+ export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
47
+ /** The compiled judge over one resolved table. Pure and synchronous — literal folded comparisons,
48
+ * no filesystem access (see the module header for the declared lexical scope). */
49
+ export interface WriteProtectionMatcher {
50
+ /** The resolved rows this judge was compiled from (canonical names — the disclosure face). */
51
+ readonly rows: readonly WriteProtectedRow[];
52
+ /** Judge ONE path spelling. Returns the first matching row (basename rows first, then segment,
53
+ * then segment-run — deterministic), or null. */
54
+ matchPath(path: string): WriteProtectedHit | null;
55
+ }
56
+ /**
57
+ * Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
58
+ * resolved table is EMPTY (`[]` replacement) — the caller then mounts no tighten at all, keeping an
59
+ * opted-out deployment's decision path byte-identical to a build without this layer.
60
+ */
61
+ export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[]): WriteProtectionMatcher | undefined;
62
+ /**
63
+ * Build the ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`): covered tools are the
64
+ * path-confinable write set (Write/Edit/NotebookEdit — the shared spelling), the judged target is
65
+ * the SAME string the tool itself will resolve (`writeTargetPath`, the shared single source with
66
+ * the sensitive-path guard and the fs-write gate), and the verdict is the table judge's. A covered
67
+ * write with NO resolvable target returns null here — this layer is ADDITIVE friction on a named
68
+ * set of targets, not a containment boundary (a target-less call cannot land on a named row, and
69
+ * the tool's own schema validation refuses it before any write); the fail-closed treatment of the
70
+ * unresolvable case belongs to the containment gates (fs-write-gate-policy documents that split).
71
+ * Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
72
+ */
73
+ export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[]): ((toolName: string, args: unknown) => WriteProtectedHit | null) | undefined;
@@ -0,0 +1,195 @@
1
+ import { writeTargetPath } from "../tools/fs/safety.js";
2
+ import { PATH_CONFINABLE_WRITE_TOOLS } from "./runner/session-rule-policy.js";
3
+ const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
4
+ export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
5
+ { name: ".gitconfig", kind: "basename" },
6
+ { name: ".gitmodules", kind: "basename" },
7
+ { name: ".bashrc", kind: "basename" },
8
+ { name: ".bash_profile", kind: "basename" },
9
+ { name: ".zshrc", kind: "basename" },
10
+ { name: ".zprofile", kind: "basename" },
11
+ { name: ".profile", kind: "basename" },
12
+ { name: ".zshenv", kind: "basename" },
13
+ { name: ".zlogin", kind: "basename" },
14
+ { name: ".zlogout", kind: "basename" },
15
+ { name: ".bash_login", kind: "basename" },
16
+ { name: ".bash_aliases", kind: "basename" },
17
+ { name: ".bash_logout", kind: "basename" },
18
+ { name: ".envrc", kind: "basename" },
19
+ { name: ".ripgreprc", kind: "basename" },
20
+ { name: ".mcp.json", kind: "basename" },
21
+ { name: ".claude.json", kind: "basename" },
22
+ { name: ".npmrc", kind: "basename" },
23
+ { name: ".yarnrc", kind: "basename" },
24
+ { name: ".yarnrc.yml", kind: "basename" },
25
+ { name: ".pnp.cjs", kind: "basename" },
26
+ { name: ".pnp.loader.mjs", kind: "basename" },
27
+ { name: ".pnpmfile.cjs", kind: "basename" },
28
+ { name: "bunfig.toml", kind: "basename" },
29
+ { name: ".bunfig.toml", kind: "basename" },
30
+ { name: ".bazelrc", kind: "basename" },
31
+ { name: ".bazelversion", kind: "basename" },
32
+ { name: ".bazeliskrc", kind: "basename" },
33
+ { name: ".pre-commit-config.yaml", kind: "basename" },
34
+ { name: "lefthook.yml", kind: "basename" },
35
+ { name: ".lefthook.yml", kind: "basename" },
36
+ { name: "lefthook.yaml", kind: "basename" },
37
+ { name: ".lefthook.yaml", kind: "basename" },
38
+ { name: "gradle-wrapper.properties", kind: "basename" },
39
+ { name: "maven-wrapper.properties", kind: "basename" },
40
+ { name: ".devcontainer.json", kind: "basename" },
41
+ { name: "pyrightconfig.json", kind: "basename" },
42
+ { name: ".git", kind: "segment" },
43
+ { name: ".vscode", kind: "segment" },
44
+ { name: ".idea", kind: "segment" },
45
+ { name: ".claude", kind: "segment" },
46
+ { name: ".husky", kind: "segment" },
47
+ { name: ".cargo", kind: "segment" },
48
+ { name: ".devcontainer", kind: "segment" },
49
+ { name: ".yarn", kind: "segment" },
50
+ { name: ".mvn", kind: "segment" },
51
+ { name: ".config/git", kind: "segment-run" },
52
+ { name: ".ssh", kind: "segment" },
53
+ { name: ".gnupg", kind: "segment" },
54
+ ]);
55
+ export function foldWriteProtectCase(s) {
56
+ return s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
57
+ }
58
+ const KINDS = ["basename", "segment", "segment-run"];
59
+ function describeEntryValue(v) {
60
+ try {
61
+ return JSON.stringify(v) ?? String(v);
62
+ }
63
+ catch {
64
+ return String(v);
65
+ }
66
+ }
67
+ function normalizeWriteProtectedEntry(entry) {
68
+ const shape = typeof entry === "string" ? { name: entry, kind: entry.includes("/") ? "segment-run" : "segment" } : entry;
69
+ if (typeof shape !== "object" || shape === null || typeof shape.name !== "string") {
70
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(entry)} is not a name string or { name, kind } row.`);
71
+ }
72
+ const name = shape.name;
73
+ if (!KINDS.includes(shape.kind)) {
74
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(entry)} has kind ${describeEntryValue(shape.kind)} — known kinds: ${KINDS.join(", ")}.`);
75
+ }
76
+ const kind = shape.kind;
77
+ if (name.includes("*") || name.includes("?")) {
78
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(name)} contains a wildcard metacharacter — this table speaks LITERAL names only (a silently-literal "*" would guard less than it reads); pattern semantics live in createSensitivePathPolicy.`);
79
+ }
80
+ if (name.includes("\\")) {
81
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(name)} contains a backslash — names are "/"-separated (both path families are matched); spell the segments with "/".`);
82
+ }
83
+ const segments = name.split("/").filter((s) => s.length > 0);
84
+ if (segments.length === 0) {
85
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(name)} contains no path segments and would protect nothing — remove the entry or spell the name.`);
86
+ }
87
+ if (segments.some((s) => s === "." || s === "..")) {
88
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(name)} contains a "." or ".." segment — the judge folds dot segments lexically before matching, so such a row could never match anything; spell the real name.`);
89
+ }
90
+ if (segments.length > 1 && kind !== "segment-run") {
91
+ throw new Error(`writeProtectedPaths: entry ${describeEntryValue(name)} spans ${segments.length} segments but declares kind "${kind}" — multi-segment names match as kind "segment-run".`);
92
+ }
93
+ return { name: segments.join("/"), kind };
94
+ }
95
+ export function resolveWriteProtectedTable(entries) {
96
+ if (entries === undefined)
97
+ return WRITE_PROTECTED_DEFAULT_TABLE;
98
+ if (!Array.isArray(entries)) {
99
+ throw new Error(`writeProtectedPaths: expected an array of entries (whole-table replacement; [] = no write-protection table), got ${describeEntryValue(entries)}.`);
100
+ }
101
+ const out = [];
102
+ const seen = new Set();
103
+ for (const entry of entries) {
104
+ const row = normalizeWriteProtectedEntry(entry);
105
+ const key = `${row.kind}:${foldWriteProtectCase(row.name)}`;
106
+ if (seen.has(key))
107
+ continue;
108
+ seen.add(key);
109
+ out.push(row);
110
+ }
111
+ return freezeTable(out);
112
+ }
113
+ export function compileWriteProtection(entries) {
114
+ const rows = resolveWriteProtectedTable(entries);
115
+ if (rows.length === 0)
116
+ return undefined;
117
+ const basenames = new Map();
118
+ const segments = new Map();
119
+ const runs = [];
120
+ for (const row of rows) {
121
+ const folded = foldWriteProtectCase(row.name);
122
+ if (row.kind === "basename") {
123
+ if (!basenames.has(folded))
124
+ basenames.set(folded, row.name);
125
+ }
126
+ else if (row.kind === "segment") {
127
+ if (!segments.has(folded))
128
+ segments.set(folded, row.name);
129
+ }
130
+ else {
131
+ runs.push({ name: row.name, parts: folded.split("/").filter((s) => s.length > 0) });
132
+ }
133
+ }
134
+ return {
135
+ rows,
136
+ matchPath(path) {
137
+ const viewOf = (win32) => {
138
+ const segs = [];
139
+ for (const raw of path.split(/[\\/]/)) {
140
+ if (raw.length === 0)
141
+ continue;
142
+ const dot = !win32 || /^\.{1,2}$/.test(raw) ? raw : raw.replace(/ +$/, "");
143
+ if (dot === ".")
144
+ continue;
145
+ if (dot === "..") {
146
+ const last = segs[segs.length - 1];
147
+ if (segs.length > 0 && last !== "..")
148
+ segs.pop();
149
+ else
150
+ segs.push("..");
151
+ continue;
152
+ }
153
+ const stripped = win32 ? raw.replace(/[. ]+$/, "") : raw;
154
+ segs.push(foldWriteProtectCase(stripped.length > 0 ? stripped : raw));
155
+ }
156
+ return segs;
157
+ };
158
+ const judge = (segs) => {
159
+ if (segs.length === 0)
160
+ return null;
161
+ const base = basenames.get(segs[segs.length - 1] ?? "");
162
+ if (base !== undefined)
163
+ return { name: base, kind: "basename" };
164
+ for (const s of segs) {
165
+ const hit = segments.get(s);
166
+ if (hit !== undefined)
167
+ return { name: hit, kind: "segment" };
168
+ }
169
+ for (const run of runs) {
170
+ const n = run.parts.length;
171
+ for (let i = 0; i + n <= segs.length; i++) {
172
+ if (run.parts.every((p, j) => segs[i + j] === p))
173
+ return { name: run.name, kind: "segment-run" };
174
+ }
175
+ }
176
+ return null;
177
+ };
178
+ const posix = viewOf(false);
179
+ return judge(posix) ?? judge(viewOf(true));
180
+ },
181
+ };
182
+ }
183
+ export function createWriteProtectionCheck(entries) {
184
+ const matcher = compileWriteProtection(entries);
185
+ if (matcher === undefined)
186
+ return undefined;
187
+ return (toolName, args) => {
188
+ if (!PATH_CONFINABLE_WRITE_TOOLS.has(toolName))
189
+ return null;
190
+ const path = writeTargetPath(toolName, args);
191
+ if (typeof path !== "string" || path.length === 0)
192
+ return null;
193
+ return matcher.matchPath(path);
194
+ };
195
+ }
package/dist/index.d.ts CHANGED
@@ -91,6 +91,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, ty
91
91
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
92
92
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
93
93
  export { deploymentReadFaceClampNotice, resolveReadFace, type ReadFace, type ReadFaceInputs } from "./tools/fs/index.js";
94
+ export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, } from "./core/write-protect.js";
94
95
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
95
96
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
96
97
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
@@ -114,7 +115,7 @@ export { type StoreDurability } from "./core/checkpoint-store.js";
114
115
  export { type StoreFidelity } from "./core/checkpoint-store.js";
115
116
  export { projectHumanInput, buildHumanInputEvent, type HumanInputSource, type HumanInputFrame, type HumanInputEvent, type HumanInputDelivery, } from "./core/human-input-projection.js";
116
117
  export { TaskRegistry, defaultTaskRegistry, createTaskOutputTool, createTaskStopTool, type SemaTaskType, type SemaTaskStatus, type SemaTaskHandle, type ParkedClaimTicket, type TaskAccess, type UnifiedTaskOutput, type TaskRetrievalStatus, type StopSource, type RegisterMonitorInput, type MonitorTimers, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccessWorkflowRun, DURABLE_AGENT_HANDLE_RE, } from "./core/task-registry.js";
117
- export { InMemoryMailboxStore, type MailboxStore, type MailboxMessage, type MailboxLease } from "./core/mailbox-store.js";
118
+ export { InMemoryMailboxStore, type MailboxStore, type MailboxMessage, type MailboxLease, MailboxStoreError, MAILBOX_TOMBSTONED_RECIPIENT_CODE, } from "./core/mailbox-store.js";
118
119
  export { FileMailboxStore, type FileMailboxStoreOptions } from "./stores/file/mailbox-store.js";
119
120
  export { createFileTaskListStore } from "./stores/file/task-list-store.js";
120
121
  export { createCcFileTaskListStore } from "./stores/cc/task-list-store.js";
@@ -165,7 +166,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
165
166
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
166
167
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleUnreadable, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookInvocationIdentity, type UserPromptSubmitContext, type PostToolBatchContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
167
168
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
168
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
169
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
169
170
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
170
171
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
171
172
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -217,7 +218,7 @@ export { sessionRepoContract } from "./core/store-contracts/session-repo-contrac
217
218
  export { toolResultStoreContract } from "./core/store-contracts/tool-result-store-contract.js";
218
219
  export { fileSnapshotStoreContract } from "./core/store-contracts/file-snapshot-store-contract.js";
219
220
  export { permissionRuleSyncContract, type PermissionRuleSyncContractHooks } from "./core/store-contracts/permission-rule-sync-contract.js";
220
- export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, } from "./core/store-contracts/mailbox-store-contract.js";
221
+ export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, type MailboxTombstonedRecipientContractHooks, } from "./core/store-contracts/mailbox-store-contract.js";
221
222
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
222
223
  export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
223
224
  export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";