@sema-agent/core 5.20.0 → 5.21.1

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 (313) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/agents/agent-definition.d.ts +7 -0
  3. package/dist/agents/agent-transcript-tool.d.ts +30 -0
  4. package/dist/agents/builtin-agents.d.ts +60 -0
  5. package/dist/agents/cascade.d.ts +107 -0
  6. package/dist/agents/cumulative-stats.d.ts +56 -0
  7. package/dist/agents/observer.d.ts +162 -0
  8. package/dist/agents/peer-admission.d.ts +95 -0
  9. package/dist/agents/repair-loop.d.ts +226 -0
  10. package/dist/agents/retain-ledger.d.ts +141 -0
  11. package/dist/agents/roster-store.d.ts +105 -0
  12. package/dist/agents/send-message-tool.d.ts +92 -0
  13. package/dist/agents/session-util.d.ts +5 -0
  14. package/dist/agents/subagent-steps.d.ts +66 -0
  15. package/dist/agents/subagent.d.ts +600 -0
  16. package/dist/agents/suspend-guard.d.ts +29 -0
  17. package/dist/agents/teacher.d.ts +75 -0
  18. package/dist/agents/team.d.ts +120 -1
  19. package/dist/agents/tool-filter.d.ts +34 -0
  20. package/dist/agents/verify.d.ts +198 -0
  21. package/dist/bench/metrics.d.ts +455 -0
  22. package/dist/brain/anthropic.d.ts +30 -0
  23. package/dist/brain/circuit-breaker.d.ts +33 -0
  24. package/dist/brain/context-overflow.d.ts +60 -3
  25. package/dist/brain/degrading.d.ts +67 -0
  26. package/dist/brain/errors.d.ts +42 -0
  27. package/dist/brain/failover.d.ts +15 -0
  28. package/dist/brain/media-degrade.d.ts +39 -0
  29. package/dist/brain/model-presets.d.ts +31 -0
  30. package/dist/brain/open-responses.d.ts +19 -0
  31. package/dist/brain/openai.d.ts +46 -0
  32. package/dist/brain/reasoning.d.ts +106 -1
  33. package/dist/brain/repetition.d.ts +83 -0
  34. package/dist/brain/request-params.d.ts +56 -0
  35. package/dist/brain/retry.d.ts +46 -0
  36. package/dist/brain/routing.d.ts +12 -0
  37. package/dist/brain/status-sink.d.ts +30 -1
  38. package/dist/brain/stream-engine.d.ts +147 -0
  39. package/dist/brain/stream-shared.d.ts +34 -0
  40. package/dist/brain/terminal-cause.d.ts +31 -0
  41. package/dist/brain/timeout.d.ts +107 -0
  42. package/dist/brain/tool-call-id.d.ts +20 -0
  43. package/dist/brain/tool-call-repair.d.ts +13 -0
  44. package/dist/config/catalog.d.ts +47 -0
  45. package/dist/config/defaults.d.ts +33 -0
  46. package/dist/core/a2a-task-state.d.ts +53 -0
  47. package/dist/core/a2a.d.ts +51 -0
  48. package/dist/core/arg-summary.d.ts +62 -0
  49. package/dist/core/ask-question.d.ts +272 -2
  50. package/dist/core/auto-compaction.d.ts +467 -0
  51. package/dist/core/auto-compaction.js +20 -6
  52. package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
  53. package/dist/core/auto-mode-prompt.d.ts +27 -0
  54. package/dist/core/auto-mode.d.ts +54 -3
  55. package/dist/core/auto-promote.d.ts +100 -0
  56. package/dist/core/background-agent-store.d.ts +292 -0
  57. package/dist/core/background-shell.d.ts +110 -0
  58. package/dist/core/cache-break-detector.d.ts +34 -0
  59. package/dist/core/canonical-json.d.ts +57 -0
  60. package/dist/core/checkpoint-store.d.ts +1574 -16
  61. package/dist/core/compliance.d.ts +30 -0
  62. package/dist/core/consolidate-scope.d.ts +75 -0
  63. package/dist/core/context-edit.d.ts +99 -0
  64. package/dist/core/context-guard.d.ts +46 -0
  65. package/dist/core/exec-gate.d.ts +44 -0
  66. package/dist/core/exec-output-tail.d.ts +61 -0
  67. package/dist/core/file-snapshot-store.d.ts +104 -0
  68. package/dist/core/fs-write-gate-policy.d.ts +36 -0
  69. package/dist/core/git-worktree-env.d.ts +31 -0
  70. package/dist/core/governance-codes.d.ts +19 -0
  71. package/dist/core/hooks.d.ts +608 -1
  72. package/dist/core/human-input-projection.d.ts +37 -0
  73. package/dist/core/human-input-projection.js +13 -0
  74. package/dist/core/image-downsample.d.ts +74 -0
  75. package/dist/core/locked-config.d.ts +37 -0
  76. package/dist/core/lsp-diagnostics.d.ts +77 -0
  77. package/dist/core/lsp-protocol.d.ts +29 -0
  78. package/dist/core/lsp-session.d.ts +60 -1
  79. package/dist/core/lsp.d.ts +150 -1
  80. package/dist/core/mailbox-store.d.ts +57 -0
  81. package/dist/core/mcp.d.ts +381 -0
  82. package/dist/core/media-byte-cap.d.ts +21 -0
  83. package/dist/core/memory-admission.d.ts +71 -0
  84. package/dist/core/memory-engine/content-origin.d.ts +64 -0
  85. package/dist/core/memory-engine/data-plane.d.ts +43 -0
  86. package/dist/core/memory-engine/dual-root.d.ts +24 -0
  87. package/dist/core/memory-engine/engine.d.ts +230 -0
  88. package/dist/core/memory-engine/engine.js +103 -35
  89. package/dist/core/memory-engine/file-backend.d.ts +155 -0
  90. package/dist/core/memory-engine/frontmatter.d.ts +16 -0
  91. package/dist/core/memory-engine/layout.d.ts +217 -0
  92. package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
  93. package/dist/core/memory-engine/migrate.d.ts +9 -0
  94. package/dist/core/memory-engine/scan.d.ts +12 -0
  95. package/dist/core/memory-engine/scope-contract.d.ts +61 -1
  96. package/dist/core/memory-engine/sync-client.d.ts +87 -0
  97. package/dist/core/memory-engine/sync.d.ts +60 -0
  98. package/dist/core/memory-engine/tools.d.ts +41 -0
  99. package/dist/core/memory-engine/types.d.ts +188 -0
  100. package/dist/core/memory-recall.d.ts +141 -0
  101. package/dist/core/memory-vector.d.ts +20 -0
  102. package/dist/core/memory.d.ts +458 -0
  103. package/dist/core/message-utils.d.ts +6 -0
  104. package/dist/core/oracle-isolation.d.ts +69 -0
  105. package/dist/core/permission-rule-consent.d.ts +138 -0
  106. package/dist/core/permission-rule-model.d.ts +122 -0
  107. package/dist/core/permission-rule-store.d.ts +119 -3
  108. package/dist/core/permission-rules.d.ts +87 -1
  109. package/dist/core/present-plan-tool.d.ts +20 -0
  110. package/dist/core/pricing.d.ts +26 -0
  111. package/dist/core/property-harness.d.ts +86 -0
  112. package/dist/core/protocol-naming.d.ts +38 -0
  113. package/dist/core/protocol-table.d.ts +61 -0
  114. package/dist/core/push-queue.d.ts +1 -0
  115. package/dist/core/remote-env.d.ts +383 -1
  116. package/dist/core/retention-policy.d.ts +33 -0
  117. package/dist/core/retention.d.ts +51 -0
  118. package/dist/core/roles.d.ts +59 -0
  119. package/dist/core/runner/active-skill-scope.d.ts +34 -0
  120. package/dist/core/runner/assemble-result.d.ts +134 -0
  121. package/dist/core/runner/compaction-call-options.d.ts +97 -1
  122. package/dist/core/runner/grounding-signal.d.ts +10 -0
  123. package/dist/core/runner/image.d.ts +17 -0
  124. package/dist/core/runner/image.js +29 -15
  125. package/dist/core/runner/memory-consolidation.d.ts +60 -0
  126. package/dist/core/runner/prepare-memory.d.ts +59 -0
  127. package/dist/core/runner/prepare-task.d.ts +1011 -2
  128. package/dist/core/runner/prepare-task.js +12 -11
  129. package/dist/core/runner/prompt-suggestions.d.ts +7 -0
  130. package/dist/core/runner/runtask.d.ts +304 -3
  131. package/dist/core/runner/session-file-state-replay.d.ts +49 -0
  132. package/dist/core/runner/session-rule-policy.d.ts +58 -0
  133. package/dist/core/runner/strict-output-schema.d.ts +18 -3
  134. package/dist/core/runner/synthetic-tools.d.ts +113 -0
  135. package/dist/core/runner/teardown-bounded.d.ts +34 -0
  136. package/dist/core/runner/tool-disclosure.d.ts +227 -0
  137. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  138. package/dist/core/runner/turn-attachments.d.ts +634 -0
  139. package/dist/core/runner/usage-accounting.d.ts +32 -0
  140. package/dist/core/runtime.d.ts +9 -0
  141. package/dist/core/safe-notify.d.ts +64 -0
  142. package/dist/core/safety-axis-vocab.d.ts +23 -0
  143. package/dist/core/safety-merge-corpus.d.ts +37 -0
  144. package/dist/core/scheduler.d.ts +121 -0
  145. package/dist/core/secret-env.d.ts +32 -0
  146. package/dist/core/select-model.d.ts +15 -0
  147. package/dist/core/sensitive-path-policy.d.ts +42 -0
  148. package/dist/core/session-policy-store.d.ts +94 -0
  149. package/dist/core/session-reconcile.d.ts +80 -0
  150. package/dist/core/session-store.d.ts +85 -0
  151. package/dist/core/session.d.ts +153 -0
  152. package/dist/core/shared-memory/contract.d.ts +22 -0
  153. package/dist/core/shared-memory/normalize.d.ts +123 -2
  154. package/dist/core/shared-memory/tools.d.ts +14 -0
  155. package/dist/core/shared-memory/types.d.ts +105 -0
  156. package/dist/core/shutdown-debug.d.ts +6 -0
  157. package/dist/core/side-query.d.ts +38 -0
  158. package/dist/core/side-query.js +6 -1
  159. package/dist/core/skill-tool-specifier.d.ts +72 -0
  160. package/dist/core/skills-directory.d.ts +100 -1
  161. package/dist/core/spec-contract.d.ts +89 -0
  162. package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
  163. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
  164. package/dist/core/store-contracts/contract-harness.d.ts +20 -0
  165. package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
  166. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
  167. package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
  168. package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
  169. package/dist/core/strategy-store.d.ts +37 -0
  170. package/dist/core/stub-env.d.ts +7 -0
  171. package/dist/core/surrogate-safe-slice.d.ts +35 -0
  172. package/dist/core/task-notification.d.ts +202 -0
  173. package/dist/core/task-outcome.d.ts +53 -0
  174. package/dist/core/task-registry-agent.d.ts +337 -1
  175. package/dist/core/task-registry-monitor.d.ts +12 -0
  176. package/dist/core/task-registry-shared.d.ts +540 -0
  177. package/dist/core/task-registry.d.ts +343 -0
  178. package/dist/core/task-registry.js +13 -2
  179. package/dist/core/task-tool-shape.d.ts +44 -0
  180. package/dist/core/tighten-task-spec.d.ts +21 -0
  181. package/dist/core/tool-detach.d.ts +21 -0
  182. package/dist/core/tool-errors.d.ts +131 -0
  183. package/dist/core/tool-errors.js +4 -0
  184. package/dist/core/tool-name-aliases.d.ts +27 -0
  185. package/dist/core/tool-policy.d.ts +555 -0
  186. package/dist/core/tool-result-budget.d.ts +32 -0
  187. package/dist/core/tool-result-store.d.ts +174 -1
  188. package/dist/core/tools.d.ts +45 -0
  189. package/dist/core/trace.d.ts +323 -0
  190. package/dist/core/types.d.ts +3859 -2
  191. package/dist/core/untrusted-egress.d.ts +8 -0
  192. package/dist/core/untrusted-text.d.ts +156 -0
  193. package/dist/core/usage-window-store.d.ts +95 -0
  194. package/dist/core/version.d.ts +1 -0
  195. package/dist/core/warm-resume.d.ts +17 -0
  196. package/dist/core/wiring-manifest.d.ts +169 -0
  197. package/dist/core/with-retry.d.ts +24 -0
  198. package/dist/core/workflow-journal-store.d.ts +160 -0
  199. package/dist/core/workflow-run-store-contract.d.ts +25 -0
  200. package/dist/core/workflow-run-store.d.ts +119 -0
  201. package/dist/engine/compaction/compaction.d.ts +256 -1
  202. package/dist/engine/compaction/utils.d.ts +94 -0
  203. package/dist/engine/execution-env/kill-tree.d.ts +29 -0
  204. package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
  205. package/dist/engine/harness/agent-harness.d.ts +116 -0
  206. package/dist/engine/harness/agent-harness.js +3 -14
  207. package/dist/engine/harness/messages.d.ts +15 -0
  208. package/dist/engine/harness/types.d.ts +464 -2
  209. package/dist/engine/llm/diagnostics.d.ts +4 -0
  210. package/dist/engine/llm/event-stream.d.ts +3 -0
  211. package/dist/engine/llm/index.d.ts +7 -0
  212. package/dist/engine/llm/types.d.ts +500 -3
  213. package/dist/engine/llm/validation.d.ts +3 -0
  214. package/dist/engine/loop/agent-loop.d.ts +87 -2
  215. package/dist/engine/loop/runtime-deps.d.ts +7 -0
  216. package/dist/engine/loop/types.d.ts +424 -0
  217. package/dist/engine/lsp/frame-decoder.d.ts +13 -0
  218. package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
  219. package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
  220. package/dist/engine/session/import-validate.d.ts +27 -0
  221. package/dist/engine/session/log-digest.d.ts +93 -0
  222. package/dist/engine/session/memory-repo.d.ts +6 -0
  223. package/dist/engine/session/memory-storage.d.ts +2 -0
  224. package/dist/engine/session/session.d.ts +75 -0
  225. package/dist/engine/session/storage-base.d.ts +8 -0
  226. package/dist/fixtures/index.d.ts +36 -0
  227. package/dist/index.d.ts +16 -1
  228. package/dist/index.js +0 -1
  229. package/dist/internal/harness-types.d.ts +6 -0
  230. package/dist/internal/harness.d.ts +11 -0
  231. package/dist/internal/llm.d.ts +6 -0
  232. package/dist/orchestration/builtin-workflows.d.ts +53 -0
  233. package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
  234. package/dist/orchestration/goal.d.ts +57 -0
  235. package/dist/orchestration/goal.js +3 -0
  236. package/dist/orchestration/run-spec.d.ts +42 -0
  237. package/dist/orchestration/run-workflow-tool.d.ts +169 -0
  238. package/dist/orchestration/workflow-governance.d.ts +61 -0
  239. package/dist/orchestration/workflow-meta.d.ts +28 -0
  240. package/dist/orchestration/workflow-observe.d.ts +60 -0
  241. package/dist/orchestration/workflow-primitives.d.ts +23 -1
  242. package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
  243. package/dist/orchestration/workflow-script-runner.d.ts +88 -0
  244. package/dist/orchestration/workflow-script-store.d.ts +98 -0
  245. package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
  246. package/dist/orchestration/workflow-types.d.ts +169 -2
  247. package/dist/orchestration/workflow.d.ts +358 -0
  248. package/dist/prompt-assembly/artifact-store.d.ts +33 -0
  249. package/dist/prompt-assembly/artifact.d.ts +25 -0
  250. package/dist/prompt-assembly/assemble.d.ts +20 -0
  251. package/dist/prompt-assembly/composer.d.ts +29 -0
  252. package/dist/prompt-assembly/epoch.d.ts +55 -1
  253. package/dist/prompt-assembly/event-registry.d.ts +35 -0
  254. package/dist/prompt-assembly/explain.d.ts +12 -0
  255. package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
  256. package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
  257. package/dist/prompt-assembly/types.d.ts +115 -0
  258. package/dist/prompts/coordinator.d.ts +27 -0
  259. package/dist/prompts/default.d.ts +539 -0
  260. package/dist/prompts/simple-sections.d.ts +45 -0
  261. package/dist/prompts/supervisor.d.ts +66 -0
  262. package/dist/scenarios/env.d.ts +28 -0
  263. package/dist/scenarios/full-body.d.ts +50 -0
  264. package/dist/scenarios/scenario-registry.d.ts +60 -0
  265. package/dist/scenarios/teacher-quickstart.d.ts +27 -0
  266. package/dist/server/http.d.ts +17 -0
  267. package/dist/stores/cc/lockfile.d.ts +6 -0
  268. package/dist/stores/cc/mailbox-store.d.ts +8 -0
  269. package/dist/stores/cc/roster-adapter.d.ts +4 -0
  270. package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
  271. package/dist/stores/file/background-agent-store.d.ts +24 -0
  272. package/dist/stores/file/checkpoint-store.d.ts +38 -0
  273. package/dist/stores/file/file-snapshot-store.d.ts +25 -0
  274. package/dist/stores/file/fs-atomic.d.ts +155 -0
  275. package/dist/stores/file/index.d.ts +89 -0
  276. package/dist/stores/file/mailbox-store.d.ts +36 -0
  277. package/dist/stores/file/memory-store.d.ts +82 -0
  278. package/dist/stores/file/permission-rule-store.d.ts +79 -0
  279. package/dist/stores/file/session-policy-store.d.ts +28 -0
  280. package/dist/stores/file/session-store.d.ts +40 -0
  281. package/dist/stores/file/shared-ledger.d.ts +83 -0
  282. package/dist/stores/file/tool-result-store.d.ts +11 -0
  283. package/dist/stores/file/usage-window-store.d.ts +18 -0
  284. package/dist/stores/file/workflow-journal-store.d.ts +100 -0
  285. package/dist/stores/file/workflow-run-store.d.ts +11 -0
  286. package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
  287. package/dist/tools/fs/encoding.d.ts +60 -0
  288. package/dist/tools/fs/fs-bash.d.ts +133 -0
  289. package/dist/tools/fs/fs-pdf.d.ts +28 -0
  290. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  291. package/dist/tools/fs/fs-search-tools.js +0 -1
  292. package/dist/tools/fs/fs-shared.d.ts +360 -0
  293. package/dist/tools/fs/fs-write.d.ts +16 -0
  294. package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
  295. package/dist/tools/fs/index.d.ts +79 -0
  296. package/dist/tools/fs/notebook.d.ts +56 -0
  297. package/dist/tools/fs/pdf.d.ts +70 -0
  298. package/dist/tools/fs/pdf.js +8 -2
  299. package/dist/tools/fs/repo-map.d.ts +10 -0
  300. package/dist/tools/fs/safety.d.ts +376 -0
  301. package/dist/tools/fs/search.d.ts +131 -0
  302. package/dist/tools/loop-tick.d.ts +24 -0
  303. package/dist/tools/monitor.d.ts +55 -0
  304. package/dist/tools/scheduler-tools.d.ts +51 -0
  305. package/dist/tools/sql-adapters.d.ts +18 -0
  306. package/dist/tools/sql.d.ts +25 -0
  307. package/dist/tools/task-list.d.ts +77 -0
  308. package/dist/tools/todo.d.ts +8 -0
  309. package/dist/tools/web.d.ts +184 -0
  310. package/dist/tools/worktree.d.ts +81 -0
  311. package/package.json +2 -2
  312. package/dist/tools/gitea-issue.d.ts +0 -13
  313. package/dist/tools/gitea-issue.js +0 -75
@@ -1,56 +1,211 @@
1
1
  import { scopeDirName, type MemoryPartitionIncidentSink } from "./layout.js";
2
2
  import type { HarvestRejection, MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "./types.js";
3
+ /** Files/dirs the entry scan never treats as entries: the derived index, dotfiles (`.hydrate`,
4
+ * transaction staging files), and anything not `.md`. */
3
5
  export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
6
+ /** Default nesting depth cap under a scope dir (CC memory is flat; a small allowance for topic dirs). */
4
7
  export declare const DEFAULT_MAX_ENTRY_DEPTH = 3;
8
+ /** One on-disk entry file the scan found. */
5
9
  export interface ScannedEntryFile {
10
+ /** Absolute path. */
6
11
  path: string;
12
+ /** Slug = path relative to the scope dir, without `.md` (may contain `/` for nested topics). */
7
13
  slug: string;
8
14
  }
15
+ /**
16
+ * Enumerate a scope dir's entry files (depth-capped, symlinks skipped, dotfiles/index/non-`.md`
17
+ * ignored, `exclude` dirs — sibling scope homes under the root — skipped). Shared by the backend
18
+ * and the engine's harvest scan so the two can never disagree on what counts as an entry file.
19
+ */
9
20
  export declare function scanEntryFiles(dir: string, opts?: {
10
21
  maxDepth?: number;
11
22
  exclude?: ReadonlySet<string>;
23
+ /** Harvest gate hook: called for every path the scan SKIPS, with why (backend scans ignore it). */
12
24
  onSkip?: (path: string, kind: "symlink" | "depth" | "nonmd" | "dotfile" | "unreadable") => void;
13
25
  }): ScannedEntryFile[];
26
+ /** Construction options for {@link FileMemoryEngineBackend}. */
14
27
  export interface FileMemoryEngineBackendOptions {
28
+ /** ⚠️ 分家坑(service 报告 2026-07-09):不显式传时,控制平面按无参 `resolveMemoryEngineRoot()`
29
+ * 派生(env/默认 config 根)——若你的记忆目录带自定义根,控制面会与记忆目录分家。自建 backend 的
30
+ * consumer 应显式钉本字段(engine 同参)。 */
15
31
  controlDir?: string;
32
+ /** 注入时钟(determinism,engine 同款);默认 `Date.now`。 */
16
33
  now?: () => number;
34
+ /**
35
+ * Where mount-time partition-key incidents go. The migration probes promise "never silent", and a
36
+ * self-constructed backend has no other channel to keep that promise on — the runner-assembled path
37
+ * bridges its own sink into `RunnerDeps.onError`, and without this seat the identical incident was
38
+ * unobservable here. Absent ⇒ the probe stays silent (it is advisory; a mount never fails over it).
39
+ */
17
40
  onIncident?: MemoryPartitionIncidentSink;
18
41
  }
42
+ /**
43
+ * ⚠️ DEPLOYMENT BOUNDARY (N0 定谳,channel 给service-AI.md [412],2026-07-09):this backend — and the
44
+ * file memory engine's materialize/harvest — operates on the WORKER-HOST fs plane (node:fs), matching
45
+ * CC's `~/.claude` host-plane memory (the only behavior anchor). It is NOT ExecutionEnv-aware: when a
46
+ * deployment routes the model's file tools to a REMOTE sandbox (e2b/k8s/ssh/adb/local-docker), the two
47
+ * planes do not intersect — the model cannot see the memory dir and harvest cannot see sandbox writes.
48
+ * Remote-hands deployments MUST gate the engine off for non-host lanes (reference: service
49
+ * `memoryEngineBackendFor` lane gate, fail-closed + `MEMORY_ENGINE_REMOTE_LANE=allow` override for
50
+ * lanes that genuinely share the worker fs, e.g. ssh+NFS). A future "remote-memory-materialize" design
51
+ * (ExecutionEnv-seam placement/harvest with sandbox-lifecycle contracts) is demand-triggered — do not
52
+ * bolt it on here.
53
+ */
19
54
  export declare class FileMemoryEngineBackend implements MemoryBackend {
55
+ /** The repo-level memory dir this backend is the authority for (engine reads it for zero-copy). */
20
56
  readonly directoryRoot: string;
57
+ /** B3 — the engine control plane home (ledger/scopes/shadow/quarantine/journal). Model-invisible:
58
+ * NEVER under {@link directoryRoot}, always on the config-root side even for an in-repo memory dir.
59
+ * The engine picks this up (like `directoryRoot`) so both sides share ONE control plane. */
21
60
  readonly controlPlaneRoot: string;
61
+ /** Injected clock (opus 1.258 LOW: match the engine's `this.now` determinism discipline). */
22
62
  private readonly now;
63
+ /**
64
+ * The COMMITTED-rev ledger (`revs.json`, id → rev). Zero-copy makes it load-bearing for CAS: the
65
+ * entry files are the SAME files the session edits, so "read the current rev from the file" would
66
+ * make every legitimate update self-conflict (the file already holds the new content). The ledger
67
+ * records the rev as of the last COMMIT (applyPatches) — a CAS compares against IT, so an in-session
68
+ * edit passes (baseRev == ledger) while a concurrently-committed change conflicts (ledger advanced).
69
+ * Read-side (`listHeaders`/`getByIds`/`search`) SYNCS the ledger to disk THROUGH the inbound gate
70
+ * (B2 — out-of-session edits, e.g. a git pull into an in-repo memory dir, are secret/cap-scanned;
71
+ * rejects are quarantined + shadow-restored, never adopted); the applyPatches lookup does NOT sync
72
+ * (that would erase the very baseline CAS needs). B3: corrupt ⇒ {@link ControlPlaneCorruptError}
73
+ * (fail-closed), never a silent empty-ledger downgrade.
74
+ */
23
75
  private ledger;
76
+ /** B2 — inbound-gate findings accumulated by read-side syncs; the engine drains them into
77
+ * `HarvestReport.inboundFindings` at harvest. */
24
78
  private inboundFindings;
79
+ /** Batch-planning scan memo (applyPatchesLocked only): planning reads pre-batch disk exclusively,
80
+ * so one `readScope(scope, false)` per scope serves every patch of the batch. Set for the planning
81
+ * loop, cleared before EXECUTE mutates the disk. Never caches `sync:true` reads. */
25
82
  private batchScan;
26
83
  constructor(dir: string, opts?: FileMemoryEngineBackendOptions);
84
+ /** B3 fail-closed preflight (engine harvest duck-types this): force-reload every load-bearing
85
+ * sidecar from disk; throws {@link ControlPlaneCorruptError} when one is corrupt. Also completes
86
+ * any interrupted transaction (H4 recovery). */
27
87
  checkControlPlane(): void;
88
+ /** B2 — drain the accumulated inbound-gate findings (engine harvest reporting). */
28
89
  drainInboundFindings(): HarvestRejection[];
90
+ /** B1 — read an entry's COMMITTED content copy from the control-plane shadow (undefined = none).
91
+ * The engine's missing-file recovery reads this (zero-copy: a deleted file is gone from the
92
+ * directory scan, so the shadow is the only committed copy left). */
29
93
  readCommittedShadow(id: string): string | undefined;
30
94
  private shadowPath;
31
95
  private loadLedger;
96
+ /** B3: atomic + fail-closed — a ledger that cannot be persisted throws (the commit is incomplete
97
+ * and the caller must know), never a silent swallow. */
32
98
  private saveLedger;
99
+ /**
100
+ * @param opts.unlocked RB-447 (2026-07-31, hardening review) — set by the two callers that run
101
+ * WITHOUT the txn mutex (the constructor and {@link checkControlPlane}). `applyPatches` serializes
102
+ * reload→plan→journal→execute→ledger behind a cross-process lock precisely because two writers over
103
+ * one root would otherwise interleave; these two ran the recovery and the staged sweep with no lock
104
+ * at all, and both are DESTRUCTIVE. A second process merely CONSTRUCTING a backend on the same
105
+ * memory dir — which happens at every task start — while process A sat between STAGE and EXECUTE
106
+ * removed A's `.stage-*` tmps, so A's `renameSync(op.staged, op.target)` threw ENOENT and the engine
107
+ * converted a healthy transaction into a `sidecar_corrupt` incident. Dropping A's journal has the
108
+ * worse shape: if A then crashed, its roll-forward record is gone.
109
+ *
110
+ * The gate is the txn mutex ITSELF, not a heuristic: an unlocked caller stands down only while
111
+ * someone else is holding a LIVE lock (present and not past its stale line), because that is
112
+ * precisely the window in which the journal and the `.stage-*` files belong to a transaction in
113
+ * flight. With no live holder — a genuine crash, or the ordinary cold start these two callers were
114
+ * written for — recovery and the sweep run exactly as before, so the documented "a crash is rolled
115
+ * forward/back on the next construction" behavior is unchanged. A live holder's own transaction
116
+ * finishes its journal itself; if that holder dies, the lock goes stale and the next construction or
117
+ * the next locked `applyPatches` recovers it. Nothing is deferred indefinitely.
118
+ */
33
119
  private recoverJournal;
120
+ /** RB-447 — is another writer mid-transaction right now? True while the txn lock dir exists and is
121
+ * not past its stale line (a stale lock is a crashed holder: its artifacts ARE ours to recover).
122
+ * Only consulted by the two UNLOCKED callers — inside `applyPatches` the lock is ours by
123
+ * construction. Cheap: one `stat` on the lock dir, no lock acquisition, never blocks. */
34
124
  private txnInFlight;
125
+ /** Remove orphaned `.stage-*` files (a crash BEFORE the journal rename = clean rollback: nothing
126
+ * was committed, the staged tmps are litter). Dotfiles are invisible to entry scans regardless. */
35
127
  private sweepStagedFiles;
128
+ /** The names of sibling scope subdirs to exclude when scanning the ROOT scope's own nested content. */
36
129
  private excludedSubdirNames;
37
130
  private scopeDir;
131
+ /** B2 inbound gate for content that did NOT come through applyPatches (git pull / hand edits /
132
+ * a hand-seeded file): secret + cap + structure. Returns the rejection, or undefined (clean). */
38
133
  private inboundGate;
134
+ /** Read every COMMITTED entry of `scope` — a file must carry a valid `id:` to count. An id-less
135
+ * file is UNCOMMITTED session material: it becomes an entry only through the engine's harvest
136
+ * gates (which mint the id on commit). Reading it as an entry here would let gate-rejected files
137
+ * (secret / cap / quarantine) leak into the authoritative set through the zero-copy disk scan.
138
+ * `sync` (read-side callers) adopts a committed file's CURRENT disk rev into the committed-rev
139
+ * ledger ONLY through the inbound gate (B2: out-of-session edits, e.g. git-pull, are scanned;
140
+ * a reject is moved to control-plane quarantine and the committed shadow content is restored to
141
+ * disk). The applyPatches lookup passes false (the ledger must keep the baseline for CAS). */
39
142
  private readScope;
40
143
  listHeaders(scopes: readonly string[]): Promise<MemoryEntryHeader[]>;
41
144
  private listHeadersWith;
42
145
  getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
43
146
  private getByIdsWith;
147
+ /**
148
+ * design/178 §3 (ruled 2026-08-08) — a SIDE-EFFECT-FREE projection of this backend for the session's
149
+ * RETRIEVAL face (`memory_search` / `memory_get`).
150
+ *
151
+ * The ordinary read path carries the inbound-adoption channel: a disk revision that disagrees with
152
+ * the ledger is judged an out-of-session change, gated, and — when it passes — adopted as the new
153
+ * COMMITTED baseline (ledger + shadow rewritten). That is a deliberate lifecycle synchronization
154
+ * point, and it belongs to the engine's own moments (materialize / harvest), not to every model
155
+ * lookup: a retrieval landing between them would otherwise COMMIT the session's own in-flight edits
156
+ * with no harvest gate and no pollution discipline in the way. Reads through this view resolve
157
+ * entries from disk without touching the ledger, the shadow, or the quarantine.
158
+ *
159
+ * Not running the inbound gate on this path is the point, not a concession: the view reports what
160
+ * is on the plane, and admitting anything stays the harvest's job.
161
+ *
162
+ * WRITES ARE REFUSED, loudly — a view handed to a write path is a defect, not a fallback.
163
+ */
44
164
  retrievalView(): MemoryBackend;
165
+ /** Lexical floor (design/138 §2.1): `1 - Jaccard` distance over name+description+body, ascending. */
45
166
  search(query: string, scopes: readonly string[], opts?: {
46
167
  limit?: number;
47
168
  }): Promise<ScoredMemoryEntry[]>;
48
169
  private searchWith;
170
+ /**
171
+ * H4 — the entry transaction, staged and journaled:
172
+ * 1. RELOAD the ledger from disk (验收 M2: a concurrently-committed transaction from another
173
+ * process becomes a CAS conflict here, never a blind write over it) + finish any interrupted
174
+ * transaction (redo journal).
175
+ * 2. PLAN every patch against current state (CAS/conflicts decided now; no disk writes yet).
176
+ * 3. STAGE: write each new file content to a same-directory `.stage-*` tmp (fsync'd).
177
+ * 4. JOURNAL: atomically write the full redo log (ops with content + the post-txn ledger snapshot).
178
+ * ← the commit point: a crash after this rolls FORWARD (recovery redoes the journal); a crash
179
+ * before it rolls BACK (staged tmps are swept, nothing was committed).
180
+ * 5. EXECUTE: atomic renames / deletes / shadow updates.
181
+ * 6. Persist the ledger (atomic, fail-closed) and drop the journal.
182
+ */
49
183
  applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
50
184
  private txnLockDir;
185
+ /**
186
+ * 对抗复审 L1/L2/L3 — the mkdir mutex, hardened (same shape as layout.ts' acquireSidecarLock, but
187
+ * fail-closed timings and an async wait):
188
+ * - L2: every acquisition writes an OWNER TOKEN (`<lock>/owner`, crypto-random — never a
189
+ * timestamp) immediately after mkdir. Release only removes the lock while the token is still
190
+ * ours; {@link assertTxnLockOwnership} re-verifies it at the commit point.
191
+ * - L1: the stale steal is SINGLE-WINNER — atomic rename of the stale lock dir to a unique
192
+ * tombstone (exactly one racer's rename succeeds; losers ENOENT back into the wait loop), then
193
+ * the winner removes the tombstone and re-acquires through a NORMAL mkdir. Never a bare
194
+ * rmSync+mkdir (two same-window stealers would force-rm each other's FRESH lock = double hold).
195
+ * - L2 (steal side): only a lock that is BOTH past its stale line AND carries an owner token is
196
+ * stealable (a tokenless dir is a mid-acquisition, not a crashed holder).
197
+ * - L3: the wait deadline is `max(waitMs, observed lock's stale line + grace)` — a holder that
198
+ * crashed with `remaining stale > waitMs` no longer strands every waiter in an all-throw window.
199
+ */
51
200
  private acquireTxnLock;
201
+ /** 对抗复审 L2 — 提交点归属复验: called immediately before the JOURNAL write (the persistent commit
202
+ * point). A holder that stalled past the stale deadline (slow disk / GC pause / long txn) and was
203
+ * stolen from must ABORT — its staged tmps carry no committed state and the stealer's journal
204
+ * recovery is now authoritative; committing anyway would overwrite the stealer's transaction. */
52
205
  private assertTxnLockOwnership;
53
206
  private applyPatchesLocked;
207
+ /** Plan one patch: decide CAS/conflicts against CURRENT state, emit journal ops (no entry-file
208
+ * writes here — staging/execution happen in {@link applyPatches}). */
54
209
  private planOne;
55
210
  private locateById;
56
211
  getConsolidationCursor(scope: string): Promise<string | undefined>;
@@ -1,12 +1,28 @@
1
1
  import type { MemoryEntry, MemoryEntryFrontmatter } from "./types.js";
2
+ /** A parsed entry file: frontmatter (may be absent — CC allows bare files; harvest mints them) + body. */
2
3
  export interface ParsedEntryFile {
4
+ /** The hidden immutable id (`id:` frontmatter line), when present. */
3
5
  id?: string;
4
6
  frontmatter: MemoryEntryFrontmatter;
5
7
  body: string;
8
+ /** True when the file had a well-formed `---` frontmatter block at all. */
6
9
  hadFrontmatter: boolean;
7
10
  }
11
+ /** The id-shape contract, shared with the WRITE path (KNOWN-ISSUES 幽灵行加固): the File backend's
12
+ * applyPatches refuses any patch whose id this rejects — such an entry would be applied + ledgered,
13
+ * yet THIS parser drops the id on read (a fresh one gets minted), leaving the file on disk but the
14
+ * id permanently invisible. Read gate and write gate must stay one predicate. */
8
15
  export declare function isValidEntryId(value: string): boolean;
16
+ /** Parse one memory file's text into frontmatter + body. Never throws — an unfenced file is all-body. */
9
17
  export declare function parseEntryFile(text: string): ParsedEntryFile;
18
+ /** Serialize an entry to its canonical file projection (id FIRST — the hidden immutable field). */
10
19
  export declare function serializeEntryFile(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
20
+ /**
21
+ * Content-hash rev (the CAS baseline). Computed over the SEMANTIC content — id, structured
22
+ * frontmatter fields, preserved extra lines, and body — via a canonical JSON tuple, NOT raw file
23
+ * bytes: engine (parsing disk files) and backend (storing entries) must agree on the rev regardless
24
+ * of incidental serialization formatting, or every materialize would see a phantom diff.
25
+ */
11
26
  export declare function computeEntryRev(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
27
+ /** Parse a file's text directly into a full MemoryEntry (id must already be present/decided). */
12
28
  export declare function entryFromFile(text: string, id: string, slug: string, scope: string): MemoryEntry;
@@ -1,79 +1,296 @@
1
1
  import type { MemoryAnnouncement } from "./types.js";
2
+ /** Cursor sidecar for the design/84 Seam B pair on the FileBackend (`{ [scope]: cursor }`). B3: control plane. */
2
3
  export declare const CURSORS_FILE = "cursors.json";
4
+ /** The control-plane subdir name under a key dir — ONE spelling for every derivation and for the
5
+ * migration probes that move a control plane between key dirs. Exported for the sibling planes
6
+ * (dual-root's personal plane) so no second literal exists; not part of the package surface. */
3
7
  export declare const CONTROL_PLANE_DIR = ".engine";
8
+ /** B3 fail-closed: a control-plane sidecar exists but cannot be trusted (unparseable / wrong shape).
9
+ * Read paths propagate it (materialize fails ⇒ the runner falls open to a memory-less session with
10
+ * onError); harvest converts it into a refused report (incident `sidecar_corrupt`). It is NEVER
11
+ * silently degraded to an empty ledger / unclaimed root — that would let a corrupted (or truncated)
12
+ * sidecar erase CAS baselines and re-open the root claim. */
4
13
  export declare class ControlPlaneCorruptError extends Error {
5
14
  constructor(message: string, opts?: {
6
15
  cause?: unknown;
7
16
  });
8
17
  }
18
+ /**
19
+ * CC-style repo PARTITION key: the repo root path, canonicalized, then slugged. Collisions across
20
+ * repos require pathological sibling names and only merge their memory dirs (never corrupt them).
21
+ *
22
+ * RB-472 (design/161, four-repo receipts on record): the key CANONICALIZES first — without
23
+ * realpath, the same repo reached through a symlink alias and through its physical path minted two
24
+ * different partition keys = two memory dirs, silently splitting memory. `canonicalize` degrades to
25
+ * plain resolve semantics for a not-yet-existing path (nearest existing ancestor + remainder).
26
+ * Its input is the HOST-declared repo root; a model-reachable directory is keyed by
27
+ * {@link deriveDirKey} instead, and that asymmetry is deliberate (see there).
28
+ *
29
+ * Known limit, registered rather than fixed (ruled 2026-08-03): on a case-INSENSITIVE volume two
30
+ * spellings that differ only in case still mint two keys — realpath does not case-fold, and folding
31
+ * here would MERGE two genuinely distinct repos on a case-SENSITIVE volume. This round closes the
32
+ * symlink axis only; the case axis predates it and is unchanged.
33
+ */
9
34
  export declare function deriveRepoKey(repoRoot: string): string;
35
+ /** Sink for the mount-time partition-key incidents (`memory.partition_split` /
36
+ * `memory.partition_adopt_failed`). ONE shape for every host that can receive them — the runner's
37
+ * onError bridge, the engine, and the file backend. */
10
38
  export type MemoryPartitionIncidentSink = (err: Error & {
11
39
  code?: string;
12
40
  }) => void;
41
+ /** RB-472 adoption leg: a pre-5.x deployment keyed its repo partition by the resolve-only path, so
42
+ * when the two mints differ its `memory/` + `.engine/` + siblings live under {@link deriveDirKey}'s
43
+ * dir. Move the whole partition onto the canonical key (see {@link migrateKeyedPartition}). */
13
44
  export declare function adoptLegacyRepoDirs(configRoot: string, repoRoot: string, onIncident?: MemoryPartitionIncidentSink): void;
45
+ /**
46
+ * The REVERSE one-shot migration (ruled 2026-08-03): for the width of ONE release the control-plane
47
+ * key canonicalized like the repo key does, which put a directory-pinned mount's `.engine` under the
48
+ * canonical key. {@link deriveDirKey} is the control-plane mint again (that ruling's security half),
49
+ * so such a control plane sits at an address this build no longer reads — move it back, once.
50
+ *
51
+ * Scoped to the `.engine` subtree on purpose: the canonical-key dir may host unrelated siblings.
52
+ *
53
+ * Known limit, registered rather than fixed: when the SAME physical directory is ALSO mounted under
54
+ * its physical spelling, that mount's own resolve-keyed plane IS this migration's source dir, and the
55
+ * probe cannot tell the two apart (both are `<key>/.engine`). Concurrently mounting one directory
56
+ * under two spellings is outside what this probe can discriminate; the window it recovers from is one
57
+ * release wide.
58
+ */
14
59
  export declare function adoptCanonicalKeyedControlDir(configRoot: string, dir: string, onIncident?: MemoryPartitionIncidentSink): void;
60
+ /**
61
+ * `<configRoot>/<repoKey>/memory` — the repo-level stable mount (§2.7: taskRoot 外, worktree-shared).
62
+ *
63
+ * Worktree note (验收 L5, documented behavior): the KEY must be the BASE repo root. The runner passes
64
+ * `deps.rootPath ?? taskRootPath` — when `rootPath` is configured, every derived worktree of that repo
65
+ * shares ONE memory dir (a worktree moves the task cwd, never the memory mount); when `rootPath` is
66
+ * NOT configured the taskRootPath fallback keys memory to the task's own checkout path, so an
67
+ * isolated-worktree task without `rootPath` gets a worktree-private memory dir. That fallback is the
68
+ * accepted degraded behavior (no silent cross-repo mixing; configure `rootPath` for worktree sharing).
69
+ */
15
70
  export declare function deriveRepoMemoryDir(configRoot: string, repoRoot: string): string;
71
+ /** design/142 §1.5 — the IDENTITY-keyed mount (v2 scope contract + a resolved project marker):
72
+ * `<configRoot>/proj-<projectId>/memory`. Unlike the path-derived {@link deriveRepoMemoryDir},
73
+ * this key survives rename/move/re-clone/container remounts — identity lives in the repo's marker,
74
+ * the mount stops referencing the path entirely. */
16
75
  export declare function deriveProjectMemoryDir(configRoot: string, projectId: string): string;
76
+ /** Control-plane sibling of {@link deriveProjectMemoryDir} (B3: always on the config-root side). */
17
77
  export declare function deriveProjectControlDir(configRoot: string, projectId: string): string;
78
+ /** design/142 §1.5 — the path→projectId HINT CACHE (`<configRoot>/project-id-hints.json`). NOT an
79
+ * identity source (the marker is the single truth source); purely the recovery breadcrumb for a
80
+ * deleted/lost marker: materialize can tell "this path used to carry marker X" and prompt a
81
+ * restore instead of silently minting a fresh identity. Best-effort on both faces (a corrupt hint
82
+ * file degrades to "no hints", never blocks a session). */
18
83
  export declare const PROJECT_ID_HINTS_FILE = "project-id-hints.json";
19
84
  export declare function recordProjectIdHint(configRoot: string, repoRoot: string, projectId: string): void;
20
85
  export declare function lookupProjectIdHint(configRoot: string, repoRoot: string): string | undefined;
86
+ /** B3 — `<configRoot>/<dirKey>/.engine`: the engine CONTROL PLANE for a DIRECTORY-pinned mount (the
87
+ * memory dir itself is the key). ALWAYS on the config-root side — when the memory dir is configured
88
+ * in-repo, the control plane still lives here (model-invisible, not injected, not in-repo) — and
89
+ * keyed RESOLVE-ONLY, because the key input is a directory the model's file channel may be able to
90
+ * replace: see {@link deriveDirKey} for why canonicalizing it is a redirect handle.
91
+ * The repo-partition face has its own sibling mint, {@link deriveRepoControlPlaneDir}. */
21
92
  export declare function deriveControlPlaneDir(configRoot: string, key: string): string;
93
+ /** B3 — the REPO-partition control plane: the sibling of {@link deriveRepoMemoryDir} under the SAME
94
+ * canonical repo key. Paired on purpose: {@link adoptLegacyRepoDirs} adopts the repo partition as ONE
95
+ * directory (`memory/` and `.engine/` inside it), so keying its control plane differently from its
96
+ * memory dir would strand one of the two on every alias-reached mount. Safe to canonicalize here —
97
+ * the key input is the host-declared repo root, not a directory the model can replace. */
22
98
  export declare function deriveRepoControlPlaneDir(configRoot: string, repoRoot: string): string;
99
+ /**
100
+ * Resolve the memory-engine config root: explicit (`RunnerDeps.memoryEngineDir`) → `AGENT_DATA_DIR`
101
+ * env → `~/.ai-agent` (the same default family as the file stores' `resolveDataRoot`; re-stated here
102
+ * so core/memory-engine does not import from src/stores).
103
+ */
23
104
  export declare function resolveMemoryEngineRoot(explicit?: string): string;
105
+ /**
106
+ * Filesystem-safe scope dir label. 评审 H5 (唯一映射): the cleaned slug alone is LOSSY
107
+ * (`org:7` and `org/7` both fold to `org-7` — two tenants, one directory = cross-scope bleed), so any
108
+ * scope whose cleaned form differs from the original gets a short content-hash suffix
109
+ * (`org-7-a1b2c3`), making the mapping injective for all practical inputs; an already-safe scope maps
110
+ * to itself (stable, human-readable). {@link registerScope} additionally FAIL-CLOSES on a registry
111
+ * collision (two distinct scopes → one dir name), so even a hash collision cannot silently merge scopes.
112
+ */
24
113
  export declare function scopeDirName(scope: string): string;
114
+ /** Which scope owns the memory dir ROOT (undefined = unclaimed). */
25
115
  export declare function rootScopeOf(controlDir: string): string | undefined;
116
+ /**
117
+ * Claim the memory dir root for `scope` iff unclaimed (idempotent, first-writer-wins, durable).
118
+ * Returns the (possibly pre-existing) root owner. Lock-serialized (独立轨 F5): two first-users of a
119
+ * shared mount race the claim under the sidecar lock — exactly one wins, the other SEES the winner.
120
+ */
26
121
  export declare function claimRootScope(controlDir: string, scope: string): string;
122
+ /** Register `scope` in the durable registry (idempotent) and return its home dir under `memoryDir`.
123
+ * H5 fail-closed: a dir-name collision with a DIFFERENT registered scope throws — two scopes must
124
+ * never share one directory (silent cross-scope merge). Lock-serialized (独立轨 F5). */
27
125
  export declare function registerScope(memoryDir: string, controlDir: string, scope: string): string;
126
+ /** All registered scopes → home dir name (`""` = the root). */
28
127
  export declare function registeredScopes(controlDir: string): Record<string, string>;
128
+ /** The stable home directory of `scope`'s entry files: the root when it owns the root, else the
129
+ * REGISTERED subdir (falls back to the {@link scopeDirName} formula for a never-registered scope). */
29
130
  export declare function scopeDirFor(memoryDir: string, controlDir: string, scope: string): string;
131
+ /** Canonicalize a path for containment checks (resolves symlinks on the EXISTING prefix). */
30
132
  export declare function canonicalize(p: string): string;
133
+ /** True iff `child` (canonicalized) is `root` or inside it. */
31
134
  export declare function isContainedIn(root: string, child: string): boolean;
135
+ /** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */
32
136
  export declare function ensureDirExists(dir: string): void;
137
+ /** B2/L4 — the control-plane subdir holding gate-rejected originals. ONE name for all three writers
138
+ * (engine harvest, engine derived-index rung, backend inbound gate): they write the same directory,
139
+ * so its name and its permissions must not depend on which of them ran first. */
33
140
  export declare const QUARANTINE_DIR = "quarantine";
141
+ /** REF-A9 — what {@link quarantineAndTombstone} reports back; both hosts escalate `detail` into
142
+ * their report channel (harvest `quarantineFailures` / inbound `quarantine_failed` findings). */
34
143
  export interface QuarantineOutcome {
144
+ /** The captured copy under the quarantine dir. Absent ⇔ the capture itself failed. */
35
145
  dest?: string;
146
+ /** True ⇔ the model-visible file no longer holds the suspect content (deleted, or tombstoned). */
36
147
  removed: boolean;
148
+ /** Set on ANY deviation from the clean capture+delete path — never a silent best-effort downgrade. */
37
149
  detail?: string;
38
150
  }
151
+ /**
152
+ * B2/L4 + C-F9 (S2-0) — capture a gate-rejected file into control-plane quarantine FROM THE
153
+ * ALREADY-READ `content` (never re-reading `path`: the disk bytes may have been swapped since the
154
+ * gate scanned them — TOCTOU), then remove the suspect from the model-visible plane: delete, and if
155
+ * that fails overwrite in place with {@link DELETED_TOMBSTONE}.
156
+ *
157
+ * REF-A9: ONE implementation for the engine's harvest rung and the backend's inbound-gate rung.
158
+ * They were line-for-line twins carrying the same historical fix ("mirrors the engine-side fix",
159
+ * opus review 1.257.2) and had already drifted twice — the backend minted its quarantine filename
160
+ * off `Date.now()` instead of its own injected clock, and the two created the SHARED quarantine dir
161
+ * with different permissions (whoever ran first decided how readable the captured secrets were).
162
+ *
163
+ * NOT to be reused for the derived-index rung (`MemoryEngine.gateDerivedIndex`): that one CLEARS the
164
+ * file (the index is derived and is rebuilt from entry frontmatter), which is the opposite of a
165
+ * tombstone — folding them would launder that distinction away.
166
+ */
167
+ /**
168
+ * Write `content` to `path`, refusing to follow a symlink at the FINAL component (`O_NOFOLLOW`).
169
+ *
170
+ * Every containment write in this domain is a check-then-use: the path was judged in-root when the
171
+ * scan read it, and the write happens later. A symlink dropped in that window turns a containment
172
+ * response — the thing whose job is removing suspect bytes — into a write through the link, i.e.
173
+ * this process putting its own committed memory content wherever the link points. Re-checking
174
+ * before writing cannot close a race; the kernel resolving and rejecting in one step can.
175
+ *
176
+ * SCOPE, stated exactly because the difference matters: `O_NOFOLLOW` constrains the LAST component
177
+ * only. An ANCESTOR directory swapped for a symlink in the same window still redirects the write,
178
+ * and closing that needs per-component `openat`-style traversal from a pinned root descriptor —
179
+ * which Node's `fs` does not expose (there is no relative-open API), so it is not reachable here
180
+ * without native bindings. What this buys is the leaf variant, which is the cheap one; the ancestor
181
+ * variant stays open and is recorded with the rest of the family rather than papered over.
182
+ *
183
+ * Throws like `writeFileSync`, including ELOOP when the final component IS a symlink. The write
184
+ * itself goes through `writeFileSync` on the open descriptor, which writes every byte or throws —
185
+ * a bare `writeSync` may short-write, and reporting a truncated restore as contained (after
186
+ * `O_TRUNC` already destroyed the destination) is the failure this guard exists to prevent.
187
+ */
39
188
  export declare function writeFileNoFollow(path: string, content: string): void;
40
189
  export declare function quarantineAndTombstone(path: string, content: string, quarantineDir: string, now: () => number): QuarantineOutcome;
190
+ /** S2-B — the queued memory-change announcements (control plane; drained at session-first inject). */
41
191
  export declare const ANNOUNCEMENTS_FILE = "announcements.json";
192
+ /** S2-B — bounded queue: overflow FOLDS (oldest dropped, counted) instead of growing unbounded. */
42
193
  export declare const MEMORY_ANNOUNCEMENTS_MAX = 20;
194
+ /** S2-C — per-file write-rejection counts (the beforeWrite silent-reject fuse; O-F9/C-F7: scoped
195
+ * state in `.engine/`, so the count survives suspend/resume and backend re-construction). */
43
196
  export declare const SCAN_FUSE_FILE = "scan-fuse.json";
197
+ /** Rejections at which the write-time feedback mutes (S2-C: 拒 ≥3 次熔断为静默拒). */
44
198
  export declare const SCAN_FUSE_THRESHOLD = 3;
199
+ /** Options of {@link acquireSidecarLock}. `waitCapMs` exists FOR the wait-bound pins (same hook shape
200
+ * as the file backend's txn-lock timings) — production callers leave it at {@link LOCK_WAIT_CAP_MS}. */
45
201
  export interface SidecarLockOptions {
46
202
  onDeadline?: "proceed" | "throw";
47
203
  waitCapMs?: number;
48
204
  }
205
+ /** Cross-process mutex via mkdir (atomic on POSIX). A lock older than {@link LOCK_STALE_MS} is
206
+ * presumed crashed and stolen; after the wait cap we proceed anyway (fail-open — these sidecars are
207
+ * not CAS-load-bearing, and wedging a session start on a lock would be worse).
208
+ *
209
+ * opus 1.258 review (MED, steal TOCTOU): a bare rmSync+mkdir steal lets TWO stealers both win (B's
210
+ * force-rm deletes A's FRESH lock). The steal is made single-winner by an atomic `renameSync` of the
211
+ * stale lock dir to a unique tombstone first — rename is atomic, exactly one racer succeeds; the
212
+ * loser's rename throws ENOENT and it loops back to a normal mkdir wait. The deadline branch keeps
213
+ * the documented proceed-anyway semantics but steals through the same single-winner gate.
214
+ *
215
+ * Wait-bound (ruled 2026-08-04): EVERY round runs the same three steps — attempt, diagnose, then
216
+ * the deadline check and the pause. The two "retry immediately" shortcuts this loop used to take
217
+ * (holder released between mkdir and stat; steal attempted) skipped both of the last two, so a
218
+ * condition that keeps answering the same way — an unwritable control dir (read-only mount, full
219
+ * volume, revoked permission) makes mkdir fail forever while the lock dir it would stat never
220
+ * exists — spun this synchronous loop at full speed and never reached its own cap. */
49
221
  export declare function acquireSidecarLock(lockDir: string, opts?: SidecarLockOptions): string;
50
222
  interface AnnouncementsRecord {
223
+ /** How many announcements were dropped by the bounded-queue fold (disclosed at render). */
51
224
  folded: number;
52
225
  queue: MemoryAnnouncement[];
53
226
  }
227
+ /** S2-B — enqueue one announcement (journaled + locked; bounded with overflow folding). */
54
228
  export declare function enqueueMemoryAnnouncement(controlDir: string, ann: MemoryAnnouncement): void;
229
+ /** S2-B — drain the queue (session-first inject, 时机①). Returns what was queued and resets it. */
55
230
  export declare function drainMemoryAnnouncements(controlDir: string): AnnouncementsRecord;
231
+ /** Test/observability peek (journal-aware, non-destructive). */
56
232
  export declare function peekMemoryAnnouncements(controlDir: string): AnnouncementsRecord;
233
+ /** S2-C — record one write-time scan rejection for `key` (canonical file path); returns the count
234
+ * BEFORE this bump (callers mute the feedback when it is already ≥ {@link SCAN_FUSE_THRESHOLD}). */
57
235
  export declare function bumpScanFuse(controlDir: string, key: string): number;
58
236
  export declare function readIndexRevs(controlDir: string): Record<string, string>;
59
237
  export declare function writeIndexRevs(controlDir: string, revs: Record<string, string>): void;
60
238
  export declare function scanFuseCount(controlDir: string, key: string): number;
239
+ /** Directory (under the control plane) holding one marker file per polluted session. */
61
240
  export declare const SESSION_POLLUTION_DIR = "session-pollution";
62
241
  export interface SessionPollutionRecord {
63
242
  at: number;
64
243
  reason: string;
65
244
  }
245
+ /**
246
+ * Mark `sessionId` polluted (idempotent; the FIRST record wins — `wx` create refuses overwrite).
247
+ * Returns true when a durable marker exists after the call (created now or already present);
248
+ * false ⇔ the marker could not be persisted — the caller must keep its own in-memory mark and
249
+ * disclose (the session stays polluted for this process either way).
250
+ */
66
251
  export declare function markSessionPolluted(controlDir: string, sessionId: string, reason: string, now: () => number): boolean;
252
+ /** Read the durable pollution record for `sessionId` (undefined = no marker).
253
+ * A marker that EXISTS still reads as polluted whether or not its bytes can be read or parsed
254
+ * (fail-closed: neither corruption nor an unreadable node may launder the state) — a synthesized
255
+ * record stands in. Only ABSENCE is clean, so a control plane that cannot be stat'ed at all reads
256
+ * clean for every session rather than disabling memory deployment-wide. */
67
257
  export declare function readSessionPollution(controlDir: string, sessionId: string): SessionPollutionRecord | undefined;
258
+ /** The retrieved-account sidecar (control plane): entry id → { count, lastAt }. */
68
259
  export declare const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
260
+ /** Bound on tracked ids — over the cap, the entries with the OLDEST `lastAt` are evicted first
261
+ * (a popularity account tolerates forgetting cold ids; unbounded growth does not tolerate itself). */
69
262
  export declare const USAGE_RETRIEVED_MAX_IDS = 4096;
263
+ /** One retrieved-account row. */
70
264
  export interface RetrievedAccountRow {
71
265
  count: number;
72
266
  lastAt: number;
73
267
  }
268
+ /** Record one retrieval (a search/get hit actually RETURNED to the model) for each id. Journaled +
269
+ * locked like every sidecar; throws propagate to the caller, which treats them fail-open. */
74
270
  export declare function recordRetrievedAccount(controlDir: string, ids: readonly string[], now: () => number): void;
271
+ /** Journal-aware read of the retrieved account (observability/tests; nothing in the engine's
272
+ * retention/ranking paths consumes it — see the section note). */
75
273
  export declare function readRetrievedAccount(controlDir: string): Record<string, RetrievedAccountRow>;
274
+ /** S2-C — clear the fuse for files harvest just ACCEPTED (a committed write resets the strike count). */
76
275
  export declare function clearScanFuse(controlDir: string, keys: Iterable<string>): void;
276
+ /**
277
+ * REF-C6 — write EVERY byte of `data` to `fd`, looping until the OS has taken all of them.
278
+ *
279
+ * A bare `writeSync(fd, data)` issues ONE write(2) and may legally SHORT-WRITE (POSIX): it returns
280
+ * how many bytes it took and silently drops the rest. `src/stores/file/fs-atomic.ts` already
281
+ * documents and fixes this defect class twice (`writeThenLink`'s "silently SHORT-WRITES" note and
282
+ * `AppendLog.append`'s RB-66 loop) — it was never propagated to the memory-engine control plane,
283
+ * where a short write would fsync+rename a TRUNCATED sidecar/entry into place while every
284
+ * fail-closed check upstream still reported success. Zero progress throws instead of spinning.
285
+ */
77
286
  export declare function writeAllSync(fd: number, data: string): void;
287
+ /** B3/H4 — TORN-WRITE-safe atomic replace (R5, ruling 2026-08-02: wording narrowed, no dirsync
288
+ * added): tmp file in the SAME directory (rename atomicity needs one fs), content fsync'd, then
289
+ * renamed over the target. A crash can leave the OLD content or the NEW content, never a torn
290
+ * file. What this does NOT promise: the rename itself surviving a power loss — the directory entry
291
+ * is not fsync'd (stores/file/fs-atomic.ts has the dir-fsync variant for stores that need it), so
292
+ * a crash in that window can resurface the old content. Sidecars tolerate that (they are rebuilt
293
+ * or re-derived); do not route data that can't through here. Throws on failure (fail-closed: a
294
+ * sidecar that cannot be written must not be silently skipped). */
78
295
  export declare function atomicWriteFileSync(path: string, data: string): void;
79
296
  export {};
@@ -1,9 +1,21 @@
1
1
  import type { MemoryBackend } from "./types.js";
2
2
  export interface MemoryBackendContractHooks {
3
3
  make: () => Promise<MemoryBackend> | MemoryBackend;
4
+ /** A second backend instance over the SAME storage as the given one (cross-instance CAS cases). */
4
5
  makeSibling?: (of: MemoryBackend) => Promise<MemoryBackend> | MemoryBackend;
6
+ /**
7
+ * design/142 §6.3: the ONLY way to run the suite without `makeSibling`. State WHY this store has no
8
+ * shared-storage second instance (e.g. "process-exclusive single-writer file store; cross-process CAS
9
+ * covered by <suite>"). Runs as a named, visible case (a LOUD skip) — never a silent 16/17 pass.
10
+ * Authoritative-backend certification (§6.1 (a)) must provide `makeSibling` instead.
11
+ */
5
12
  skipCrossInstanceCas?: string;
13
+ /** Wrap each case (vitest `it`, sequential SQL chaining, …). Default: run sequentially. */
6
14
  runAssertion?: (name: string, fn: () => Promise<void>) => void | Promise<void>;
7
15
  }
16
+ /** Run the full conformance suite. Throws (assert) on the first violated contract clause. */
8
17
  export declare function memoryBackendContract(hooks: MemoryBackendContractHooks): Promise<void>;
18
+ /** Cross-backend value equivalence: same fixture ⇒ IDENTICAL (id, slug, score) search projections on
19
+ * two backends (File vs Pg lexical floor must not drift — assertMemoryValueEquivalence precedent).
20
+ * mtimeMs/sizeBytes are backend-native (fs stat vs column) and deliberately not compared. */
9
21
  export declare function assertMemoryBackendSearchEquivalence(a: MemoryBackend, b: MemoryBackend): Promise<void>;
@@ -1,7 +1,16 @@
1
1
  import type { MemoryBackend, PatchReport } from "./types.js";
2
2
  export interface MigrateScopeReport {
3
+ /** Entries moved to the new scope (CAS-clean). */
3
4
  moved: number;
5
+ /** Entries that hit a CAS conflict (concurrently edited during migration) — re-run to retry. */
4
6
  conflicts: PatchReport["conflicts"];
7
+ /** Whether the consolidation cursor was carried over (only when the old scope had one). */
5
8
  cursorMoved: boolean;
6
9
  }
10
+ /**
11
+ * Move EVERY entry from `fromScope` to `toScope` on one backend (id-preserving, rev-recomputed via
12
+ * the cross-scope move contract), then carry the consolidation cursor. NOT transactional across
13
+ * entries (the contract's per-patch independence): a conflict leaves that entry in place — the
14
+ * report says so and a re-run picks it up. Refuses same-key no-ops.
15
+ */
7
16
  export declare function migrateScope(backend: MemoryBackend, fromScope: string, toScope: string): Promise<MigrateScopeReport>;
@@ -1,7 +1,19 @@
1
1
  import type { ScanFinding } from "./types.js";
2
+ /** S2-B 判决四 — the per-path-segment whitelist for memory-domain file names. */
2
3
  export declare const MEMORY_FILENAME_SEGMENT_RE: RegExp;
4
+ /**
5
+ * Scan a memory-domain file NAME (a path relative to the writable root, `/`-separated). Returns the
6
+ * finding or undefined (clean). Rejected names quarantine at harvest and hard-reject at write time —
7
+ * a name like `foo</system-reminder>bar.md` is an injection payload wherever the path is echoed.
8
+ */
3
9
  export declare function scanMemoryFileName(relPath: string): ScanFinding | undefined;
10
+ /**
11
+ * Scan memory-domain file CONTENT. Returns every finding (empty = clean). `maxBytes` (when given)
12
+ * adds the per-file cap rung — harvest passes it via its own pre-check today, the write-time hook
13
+ * passes the engine's `perFileBytes` so the model gets the feedback BEFORE the bytes ever land.
14
+ */
4
15
  export declare function scanMemoryWrite(text: string, opts?: {
5
16
  maxBytes?: number;
6
17
  }): ScanFinding[];
18
+ /** Model-facing remediation line per scan code (S2-C: 拒写+结构化错误含整改指引). */
7
19
  export declare function scanRemediation(code: ScanFinding["code"]): string;