@sema-agent/core 5.64.0 → 6.0.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 (165) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/agents/subagent.d.ts +2 -2
  3. package/dist/agents/subagent.js +11 -0
  4. package/dist/agents/verify.d.ts +1 -1
  5. package/dist/brain/anthropic.js +1 -1
  6. package/dist/brain/errors.d.ts +29 -0
  7. package/dist/brain/errors.js +20 -0
  8. package/dist/brain/open-responses.js +2 -2
  9. package/dist/brain/route-adjudicator.d.ts +8 -1
  10. package/dist/brain/route-adjudicator.js +1 -0
  11. package/dist/brain/status-sink.js +12 -1
  12. package/dist/brain/stream-engine.js +17 -6
  13. package/dist/core/auto-compaction.d.ts +26 -0
  14. package/dist/core/auto-compaction.js +7 -2
  15. package/dist/core/auto-mode-arming.d.ts +138 -0
  16. package/dist/core/auto-mode-arming.js +181 -0
  17. package/dist/core/auto-mode-defaults.d.ts +13 -0
  18. package/dist/core/auto-mode-defaults.js +5 -0
  19. package/dist/core/auto-mode-prompt.d.ts +14 -3
  20. package/dist/core/auto-mode-prompt.js +10 -7
  21. package/dist/core/auto-mode-rebuild.d.ts +75 -0
  22. package/dist/core/auto-mode-rebuild.js +41 -0
  23. package/dist/core/auto-mode.d.ts +15 -0
  24. package/dist/core/auto-mode.js +4 -2
  25. package/dist/core/checkpoint-store.d.ts +113 -4
  26. package/dist/core/context-edit.d.ts +47 -5
  27. package/dist/core/context-guard.d.ts +1 -1
  28. package/dist/core/file-history-retention.d.ts +106 -0
  29. package/dist/core/file-history-retention.js +36 -0
  30. package/dist/core/file-history-store.d.ts +768 -0
  31. package/dist/core/file-history-store.js +880 -0
  32. package/dist/core/governance-codes.d.ts +2 -1
  33. package/dist/core/governance-codes.js +14 -0
  34. package/dist/core/hooks.d.ts +48 -8
  35. package/dist/core/hooks.js +39 -22
  36. package/dist/core/lsp.d.ts +2 -2
  37. package/dist/core/mcp.d.ts +29 -7
  38. package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
  39. package/dist/core/memory-engine/consolidation-driver.js +71 -4
  40. package/dist/core/memory-engine/consolidation.d.ts +25 -2
  41. package/dist/core/memory-engine/consolidation.js +4 -1
  42. package/dist/core/memory-engine/distiller.d.ts +84 -1
  43. package/dist/core/memory-engine/distiller.js +68 -0
  44. package/dist/core/memory-engine/dual-root.js +6 -0
  45. package/dist/core/memory-engine/engine.d.ts +329 -15
  46. package/dist/core/memory-engine/engine.js +364 -34
  47. package/dist/core/memory-engine/file-backend.d.ts +30 -0
  48. package/dist/core/memory-engine/file-backend.js +14 -13
  49. package/dist/core/memory-engine/frontmatter.d.ts +22 -1
  50. package/dist/core/memory-engine/frontmatter.js +3 -0
  51. package/dist/core/memory-engine/header-hints.d.ts +5 -0
  52. package/dist/core/memory-engine/index.d.ts +5 -4
  53. package/dist/core/memory-engine/index.js +5 -4
  54. package/dist/core/memory-engine/layout.d.ts +88 -2
  55. package/dist/core/memory-engine/layout.js +112 -3
  56. package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
  57. package/dist/core/memory-engine/provenance-wording.js +3 -0
  58. package/dist/core/memory-engine/tools.d.ts +89 -8
  59. package/dist/core/memory-engine/tools.js +263 -22
  60. package/dist/core/memory-engine/types.d.ts +80 -1
  61. package/dist/core/memory-recall.d.ts +6 -0
  62. package/dist/core/memory.d.ts +27 -1
  63. package/dist/core/memory.js +16 -2
  64. package/dist/core/permission-rule-consent.d.ts +20 -0
  65. package/dist/core/permission-rule-consent.js +12 -3
  66. package/dist/core/permission-rule-model.d.ts +67 -7
  67. package/dist/core/permission-rule-model.js +53 -7
  68. package/dist/core/permission-rule-store.js +15 -10
  69. package/dist/core/permission-rule-sync.js +15 -11
  70. package/dist/core/remote-env.d.ts +3 -3
  71. package/dist/core/retention-policy.d.ts +9 -0
  72. package/dist/core/retention-policy.js +5 -2
  73. package/dist/core/retention.d.ts +13 -2
  74. package/dist/core/runner/assemble-result.d.ts +19 -1
  75. package/dist/core/runner/assemble-result.js +17 -2
  76. package/dist/core/runner/compaction-call-options.d.ts +93 -0
  77. package/dist/core/runner/compaction-call-options.js +3 -0
  78. package/dist/core/runner/memory-capture-optout.d.ts +80 -0
  79. package/dist/core/runner/memory-capture-optout.js +53 -0
  80. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  81. package/dist/core/runner/prepare-config-doors.js +16 -0
  82. package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
  83. package/dist/core/runner/prepare-hands-readface.js +103 -8
  84. package/dist/core/runner/prepare-memory.d.ts +88 -0
  85. package/dist/core/runner/prepare-memory.js +306 -25
  86. package/dist/core/runner/prepare-task.d.ts +156 -5
  87. package/dist/core/runner/prepare-task.js +488 -98
  88. package/dist/core/runner/runtask.d.ts +27 -20
  89. package/dist/core/runner/runtask.js +283 -99
  90. package/dist/core/runner/session-file-state-replay.d.ts +18 -10
  91. package/dist/core/runner/session-file-state-replay.js +52 -1
  92. package/dist/core/runner/tool-disclosure.js +2 -1
  93. package/dist/core/runner/turn-attachments.d.ts +22 -12
  94. package/dist/core/session-store.d.ts +1 -1
  95. package/dist/core/session-store.js +6 -1
  96. package/dist/core/session.d.ts +34 -1
  97. package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
  98. package/dist/core/store-contracts/file-history-store-contract.js +720 -0
  99. package/dist/core/stub-env.d.ts +4 -0
  100. package/dist/core/stub-env.js +1 -0
  101. package/dist/core/task-registry-shared.js +30 -2
  102. package/dist/core/tool-errors.js +1 -0
  103. package/dist/core/tool-policy.d.ts +172 -1
  104. package/dist/core/tool-policy.js +32 -1
  105. package/dist/core/tool-result-store.js +2 -1
  106. package/dist/core/trace.d.ts +24 -0
  107. package/dist/core/types.d.ts +875 -97
  108. package/dist/core/types.js +4 -3
  109. package/dist/core/untrusted-text.d.ts +1 -1
  110. package/dist/core/untrusted-text.js +8 -0
  111. package/dist/core/workflow-run-store-contract.js +17 -0
  112. package/dist/core/workflow-run-store.d.ts +20 -0
  113. package/dist/core/workflow-run-store.js +1 -0
  114. package/dist/engine/compaction/compaction.d.ts +88 -10
  115. package/dist/engine/compaction/compaction.js +109 -30
  116. package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
  117. package/dist/engine/execution-env/node-execution-env.js +28 -0
  118. package/dist/engine/harness/agent-harness.d.ts +52 -1
  119. package/dist/engine/harness/agent-harness.js +36 -1
  120. package/dist/engine/harness/types.d.ts +44 -1
  121. package/dist/engine/llm/types.d.ts +50 -4
  122. package/dist/engine/loop/agent-loop.d.ts +5 -1
  123. package/dist/engine/loop/agent-loop.js +25 -0
  124. package/dist/engine/loop/types.d.ts +19 -0
  125. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  126. package/dist/engine/session/session.js +1 -1
  127. package/dist/index.d.ts +18 -8
  128. package/dist/index.js +14 -6
  129. package/dist/orchestration/run-workflow-tool.d.ts +20 -2
  130. package/dist/orchestration/run-workflow-tool.js +22 -3
  131. package/dist/orchestration/workflow-governance.d.ts +59 -1
  132. package/dist/orchestration/workflow-governance.js +61 -8
  133. package/dist/orchestration/workflow-meta.d.ts +4 -2
  134. package/dist/orchestration/workflow-primitives.js +56 -13
  135. package/dist/orchestration/workflow-types.d.ts +112 -1
  136. package/dist/orchestration/workflow-types.js +2 -2
  137. package/dist/orchestration/workflow.d.ts +20 -0
  138. package/dist/orchestration/workflow.js +182 -14
  139. package/dist/prompt-assembly/event-registry.js +1 -1
  140. package/dist/prompts/default.d.ts +15 -7
  141. package/dist/prompts/default.js +3 -0
  142. package/dist/stores/file/file-history-store.d.ts +368 -0
  143. package/dist/stores/file/file-history-store.js +1248 -0
  144. package/dist/stores/file/index.d.ts +22 -13
  145. package/dist/stores/file/index.js +4 -4
  146. package/dist/stores/file/permission-rule-store.js +1 -0
  147. package/dist/stores/file/strategy-store.d.ts +3 -3
  148. package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
  149. package/dist/tools/fs/bash-readonly-classifier.js +106 -4
  150. package/dist/tools/fs/fs-bash.js +9 -5
  151. package/dist/tools/fs/fs-shared.d.ts +52 -1
  152. package/dist/tools/fs/fs-shared.js +14 -0
  153. package/dist/tools/fs/fs-write.d.ts +5 -5
  154. package/dist/tools/fs/fs-write.js +71 -14
  155. package/dist/tools/fs/index.d.ts +6 -1
  156. package/dist/tools/fs/index.js +1 -1
  157. package/dist/tools/web.js +2 -1
  158. package/package.json +5 -1
  159. package/test/export-surface.snapshot.json +159 -23
  160. package/dist/core/file-snapshot-store.d.ts +0 -165
  161. package/dist/core/file-snapshot-store.js +0 -259
  162. package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
  163. package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
  164. package/dist/stores/file/file-snapshot-store.d.ts +0 -58
  165. package/dist/stores/file/file-snapshot-store.js +0 -353
@@ -0,0 +1,768 @@
1
+ import { type FileHistoryRetentionPolicy } from "./file-history-retention.js";
2
+ import type { ExecutionEnv } from "../internal/harness-types.js";
3
+ /** rel path of `child` under `root` in "/" form (win root) / verbatim form (posix root); null when not under.
4
+ * Fold "\" only under a WIN-family root ({@link isWinFormPath}); a POSIX tree may legally contain literal
5
+ * backslashes in FILE NAMES, and folding them would corrupt both the track key and the restore target. */
6
+ declare function relUnder(root: string, child: string): string | null;
7
+ /** Join root + a "/"-form relPath using the ROOT's native separator (keys stay family-consistent). */
8
+ declare function joinUnder(root: string, rel: string): string;
9
+ /** The parent directory of an addressed path, as a pure family-aware string op (no host node:path). */
10
+ declare function parentDirOf(absPath: string): string | undefined;
11
+ /** The re-join verdict of {@link resolveTrackKey}: an absolute path to operate on, or a NAMED
12
+ * refusal (the fail-closed arm — a recorded key that would escape `root` is never coerced into
13
+ * something writable). A-082 idx5: named so the fence can be part of the public surface. */
14
+ export type FileHistoryTrackKeyResolution = {
15
+ ok: true;
16
+ abs: string;
17
+ } | {
18
+ ok: false;
19
+ reason: string;
20
+ };
21
+ /** The path family a track key was MINTED under — the half a bare key string cannot carry. */
22
+ export type FileHistoryKeyFamily = "posix" | "win";
23
+ /**
24
+ * A track key's SELF-DESCRIPTION (design/381 §3 amendment, closing the A-082 r2 registered
25
+ * residual): a key is a bare string, so it carries the shape it was minted in but never the family
26
+ * it was minted under. Recorded beside every tracked path and carried by the session-sync envelope,
27
+ * so a graph that arrives from ANOTHER MACHINE can be judged at the door instead of silently
28
+ * re-joining under the local root as an oddly-named in-root file.
29
+ */
30
+ export interface FileHistoryTrackKeyDescriptor {
31
+ /** `"root-relative"` = re-join against the CURRENT root; `"absolute"` = restore where captured. */
32
+ kind: "root-relative" | "absolute";
33
+ family: FileHistoryKeyFamily;
34
+ }
35
+ /** The family of a root — the one place a backend asks "which family am I operating in?". */
36
+ export declare function trackKeyFamilyOf(root: string): FileHistoryKeyFamily;
37
+ /**
38
+ * {@link trackKeyOf} plus the self-description a backend must record with it. Mint through THIS and
39
+ * a graph stays judgeable after it crosses machines; mint through the bare key alone and the
40
+ * provenance is gone for good (the residual this closes).
41
+ */
42
+ export declare function describeTrackKey(root: string, absPath: string): {
43
+ key: string;
44
+ } & FileHistoryTrackKeyDescriptor;
45
+ /**
46
+ * Track-key coordinate (design/381 DV-6, the CC `wZu` relativization ported to the trusted task
47
+ * root): a path under `root` is recorded root-RELATIVE (worktree/moved-root safe — restore re-joins
48
+ * it against the CURRENT root), a path outside `root` is recorded verbatim-absolute.
49
+ *
50
+ * **Exported as part of the "MUST reuse, NOT re-implement" set** (A-082 idx5, the same §3 分工
51
+ * clause that governs the two env halves): every backend mints keys from the `(absPath, root)` pair
52
+ * `trackEdit`/`snapshot` hand it and re-joins them in `restore`, so this pair — not the env halves —
53
+ * is where a hand-rolled backend re-introduces path escape. The escape check lives INSIDE the pair
54
+ * (mint refuses to record an ambiguous rel form; {@link resolveTrackKey} refuses an unsafe recorded
55
+ * one), so a backend that calls them cannot re-open the hole, and one that re-derives them must
56
+ * re-derive the family-conditional bare-drive rule and the '..' fail-closed arm correctly on its own.
57
+ */
58
+ export declare function trackKeyOf(root: string, absPath: string): string;
59
+ /**
60
+ * Re-join a recorded track key against the CURRENT root (identity for absolute keys) — the
61
+ * {@link trackKeyOf} mirror, exported under the same "MUST reuse" clause. A key that would escape
62
+ * the root is REFUSED by name, never coerced: `{ok:false}` is the fail-closed arm a backend must
63
+ * surface as a per-file `refused` row, not silently drop.
64
+ *
65
+ * CROSS-FAMILY PROVENANCE (A-082 codex r2, registered in slice 1 — CLOSED here in slice 2 exactly as
66
+ * that note prescribed). A key is a bare STRING: it carries the shape it was minted in, never the
67
+ * FAMILY it was minted under. Every key minted against this scope's own root resolves by the mirror
68
+ * above — but a scope carried in from another machine (the {@link FileHistoryExport} envelope,
69
+ * `adoptScope` across hosts) can hold a key whose family differs from the current root's, and under
70
+ * a POSIX root a win-absolute record (`D:\other\x`) used to re-join UNDER the root as an oddly-named
71
+ * in-root file (contained, never traversal — but not the file the record meant). The closure is the
72
+ * §3 amendment the note named: a SELF-DESCRIBING key ({@link FileHistoryTrackKeyDescriptor}: kind +
73
+ * source family) recorded beside every tracked path and carried by the versioned envelope, judged
74
+ * HERE through the optional `minted` argument. Omit it and this function is byte-identical to
75
+ * before; pass it and a foreign-family record is refused by name instead of coerced.
76
+ */
77
+ export declare function resolveTrackKey(root: string, key: string, minted?: FileHistoryTrackKeyDescriptor): FileHistoryTrackKeyResolution;
78
+ export interface FileHistoryError {
79
+ /** `not_found` = no boundary for that (scope, entryId); `aborted` = the signal fired (or the
80
+ * fencing discarded a timed-out attempt); `conflict` = a non-destructive-target law refused
81
+ * (adopt/import onto a scope with its own history) — and ONLY that, because a caller decides
82
+ * whether to stay quiet on it: the engine's fork-carry seat treats `conflict` as "already carried,
83
+ * or grown its own" and says nothing, so an adoption that failed for any OTHER reason (a corrupt
84
+ * source graph, a blob it cannot reach, a consumed-id ledger it cannot carry — including the
85
+ * tombstones a publish owes for unreadable residue it is about to replace) must NOT wear this
86
+ * code or the failure disappears into that silence — those are `adopt_failed`, which the
87
+ * ADOPTION FAMILY shares: `importHistory` publishes under the same target law and answers the
88
+ * same code for the same non-conflict failures (its `unsupported_format` names a bad ENVELOPE or
89
+ * work its shape drove; a fault in the PUBLISH step itself — the disk refusing a good graph —
90
+ * answers `adopt_failed`); `unsupported_format`
91
+ * = a foreign-epoch or wrong-version envelope; the rest name the verb that failed. */
92
+ code: "track_failed" | "snapshot_failed" | "restore_failed" | "adopt_failed" | "not_found" | "aborted" | "unsupported_format" | "conflict";
93
+ message: string;
94
+ }
95
+ export type FileHistoryResult = {
96
+ ok: true;
97
+ } | {
98
+ ok: false;
99
+ error: FileHistoryError;
100
+ };
101
+ /**
102
+ * {@link FileHistoryStore.trackEdit}'s answer. `minted` is the ANNUL HANDLE in boolean form (#491):
103
+ * true = THIS call created the first-touch record, so the caller may {@link
104
+ * FileHistoryStore.annulTrack} it if the edit that record was minted for never lands; false = the
105
+ * path was already tracked (a cheap no-op), and annulling would destroy a record some EARLIER edit
106
+ * legitimately owns. A backend that cannot tell answers `false` — the safe side (a stale record is
107
+ * a cowork hazard, but discarding someone else's pristine baseline is worse).
108
+ */
109
+ export type FileHistoryTrackResult = {
110
+ ok: true;
111
+ minted: boolean;
112
+ } | {
113
+ ok: false;
114
+ error: FileHistoryError;
115
+ };
116
+ /**
117
+ * Three-state discriminant + full per-file ledger on EVERY arm (design/381 DV-15). Refusals are
118
+ * never folded into benign skips; `base_unavailable` (DV-14) is a refusal reason.
119
+ */
120
+ export type FileHistoryRestoreResult = {
121
+ /** `"complete"` = every target applied or identical; `"partial"` = ≥1 refused or failed, others
122
+ * applied; `"failed"` = the operation itself could not run (e.g. no such boundary). */
123
+ outcome: "complete" | "partial" | "failed";
124
+ /** Track keys written or deleted (the converged set). */
125
+ filesChanged: string[];
126
+ /** Track keys already equal to the target (untouched — content-hash proven, never mtime). */
127
+ identicalSkipped: string[];
128
+ /** Per-file security/consistency refusals (skipped, counted, never failing the whole restore). */
129
+ refused: Array<{
130
+ path: string;
131
+ reason: string;
132
+ }>;
133
+ /** Per-file operational failures (env write/remove errors, aborts after k writes). */
134
+ failed: Array<{
135
+ path: string;
136
+ reason: string;
137
+ }>;
138
+ /** Files whose recorded POSIX mode could not be re-applied (env without the optional mode face). */
139
+ modeNotPreserved?: number;
140
+ /** Present on outcome:"failed" (operation-level cause) and optionally on "partial". */
141
+ error?: FileHistoryError;
142
+ };
143
+ export interface FileHistoryDiffStats {
144
+ filesChanged: string[];
145
+ insertions: number;
146
+ deletions: number;
147
+ }
148
+ /** The line-diff bound (see {@link countRestoreLineDiff}): a pair whose edit distance exceeds this
149
+ * is counted as a whole-file replacement. Public so a preview UI can state what it is showing. */
150
+ export declare const FILE_HISTORY_DIFF_LINE_BUDGET = 2000;
151
+ /**
152
+ * The per-file line arithmetic behind {@link FileHistoryStore.diffStats} — ONE rule both bundled
153
+ * backends use, because "how much will this rewind change" is a claim, and two backends answering it
154
+ * differently would be two claims.
155
+ *
156
+ * `undefined` on either side means "does not exist" (a creation or a deletion), so every arm of the
157
+ * restore is expressible: bytes → bytes is a line diff, absent → bytes is a pure insertion, bytes →
158
+ * absent a pure deletion.
159
+ *
160
+ * TWO HONESTY BOUNDS, both stated rather than tuned in silence:
161
+ * · BINARY content (a NUL byte anywhere) contributes 0/0 while still being reported as CHANGED —
162
+ * "lines" is not a fact about a binary file, and printing a number derived from stray `\n` bytes
163
+ * would be inventing one.
164
+ * · Beyond {@link FILE_HISTORY_DIFF_LINE_BUDGET} the greedy diff STOPS and the pair is counted as a
165
+ * whole-file replacement (every current line deleted, every target line inserted). That is not a
166
+ * fallback to a worse estimate so much as the shape such a pair actually has — a diff that large
167
+ * IS a rewrite — and it keeps a preview from turning into an O(N·M) walk over two big files.
168
+ */
169
+ export declare function countRestoreLineDiff(current: Uint8Array | undefined, target: Uint8Array | undefined): {
170
+ insertions: number;
171
+ deletions: number;
172
+ };
173
+ /**
174
+ * Versioned session-sync envelope; `formatVersion` is the refuse-not-coerce gate (design/381 §5.3).
175
+ * COMPLETE-state export: the tracked set and EVERY retained version record travel — including v1
176
+ * records no surviving boundary references (they are restore()'s pristine fallback and reap()
177
+ * retains them independently) and the parent-identity guards (root-relative under root) that
178
+ * restore's preflight needs. An import that received only boundary references could neither run the
179
+ * parent-chain check nor serve a tracked-later-than-target restore; round-trip-after-reap is a
180
+ * contract-kit test.
181
+ */
182
+ export interface FileHistoryExport {
183
+ formatVersion: "file-history/1";
184
+ tracked: Array<{
185
+ /** Root-relative when captured under the task root, verbatim-absolute otherwise. */
186
+ relOrAbsPath: string;
187
+ /**
188
+ * The key's SELF-DESCRIPTION (§3 amendment, A-082 r2). A bare key string cannot say which
189
+ * family minted it, so a graph that crosses machines was previously re-joined against the local
190
+ * root on a guess. Both members travel with every path and the import door refuses an envelope
191
+ * whose values are outside the closed sets (refuse-not-coerce, same posture as `formatVersion`);
192
+ * {@link resolveTrackKey} then judges each foreign-family key at restore time.
193
+ */
194
+ keyKind: FileHistoryTrackKeyDescriptor["kind"];
195
+ keyFamily: FileHistoryKeyFamily;
196
+ /**
197
+ * DV-14: false ⇒ the pristine base could not be captured (durable base-unavailable marker); the
198
+ * chain then starts above v1 and restore-below-base must keep refusing after import. Travels
199
+ * with the graph; never inferred.
200
+ */
201
+ baseAvailable: boolean;
202
+ /** Full retained chain (v1 first iff baseAvailable). `blobHash:null` = existed-not marker. */
203
+ versions: Array<{
204
+ version: number;
205
+ blobHash: string | null;
206
+ mode?: number;
207
+ parentIdent?: string;
208
+ backupTime: string;
209
+ }>;
210
+ }>;
211
+ boundaries: Array<{
212
+ entryId: string;
213
+ files: Array<{
214
+ relOrAbsPath: string;
215
+ version: number;
216
+ }>;
217
+ }>;
218
+ }
219
+ /**
220
+ * The IDENTITY of an envelope — sha256 over its canonical (sorted) form, so the same graph digests
221
+ * the same however a backend ordered its rows. It is what makes `importHistory`'s idempotent
222
+ * re-publish decidable WITHOUT extra bookkeeping: a target already holding a graph that digests
223
+ * equal to the incoming one is the SAME import arriving twice (ok), while any other non-empty target
224
+ * is locally-grown history the import must never overwrite (typed `conflict`). Exported under the
225
+ * "MUST reuse" clause: two backends that computed identity differently would disagree about which
226
+ * retries are safe.
227
+ */
228
+ export declare function fileHistoryExportDigest(data: FileHistoryExport): string;
229
+ /**
230
+ * The import DOOR's structural judgment, shared by every backend (a second implementation is a second
231
+ * opinion about what is safe to accept): the envelope must carry THIS format version and every row
232
+ * must be shaped and self-described from the closed sets — `unsupported_format` otherwise, never a
233
+ * coercion. Backends call this BEFORE fetching a single blob, so a foreign-epoch payload costs
234
+ * nothing and changes nothing.
235
+ */
236
+ export declare function validateFileHistoryExport(data: FileHistoryExport): FileHistoryResult;
237
+ /**
238
+ * design/381 §3 — per-edited-file rewind history (CC FileHistory topology).
239
+ *
240
+ * Replaces the E19 whole-tree FileSnapshotStore. Three verbs mirror the reference module:
241
+ * `trackEdit` (backup-before-first-edit), `snapshot` (version the tracked set at a turn boundary),
242
+ * `restore` (converge ONLY the tracked set to a recorded boundary).
243
+ *
244
+ * Store discipline (contract-kit enforced):
245
+ * - NEVER throw: every failure is encoded in the returned result (FileSystem posture).
246
+ * - snapshot MUST NOT fail the caller's turn: a failed boundary capture degrades to "no boundary"
247
+ * with disclosure (onError phase:"rewind"). trackEdit is DIFFERENT (DV-14): its `{ok:false}`
248
+ * means "no durable first-touch state exists" and the CALLER's default policy is to REFUSE the
249
+ * edit (typed, loud) — silently proceeding would let a post-recovery retry mint already-modified
250
+ * bytes as pristine v1. Any proceed-unprotected policy is an explicit, adjudicated caller arm,
251
+ * never the store's own behavior.
252
+ * - Structurally out of scope: files the agent never edited. No store method may enumerate, read,
253
+ * or write ANY path outside the tracked set plus its recorded restore targets. (The cowork
254
+ * guarantee lives HERE, as a contract clause.)
255
+ * - BOUNDED by default: one boundary per turn, forever, is unbounded growth. Both bundled backends
256
+ * trim their own scope after each commit (see {@link FileHistoryStore.reap} for the rule, the
257
+ * default and the host override); an out-of-tree backend owes its own bound or an explicit
258
+ * statement that the host must schedule the GC.
259
+ */
260
+ export interface FileHistoryStore {
261
+ /**
262
+ * Backup `absPath`'s CURRENT state before an agent edit (first touch per scope only; later calls
263
+ * are cheap no-ops). The first touch is ONE atomic durable record — pristine blob+record,
264
+ * existence=false marker (rewind of an agent-CREATED file deletes it), or base-unavailable marker
265
+ * (DV-14). Reads bytes via `env` (remote-safe); stores them host-side. `root` is the trusted task
266
+ * root: paths under it are recorded root-RELATIVE (worktree/moved-root safe), paths outside it
267
+ * verbatim-absolute; the parent-identity guard is recorded in the same frame.
268
+ * `{ok:false}` = NO durable first-touch state was persisted — see the discipline block: the
269
+ * caller's default is to REFUSE the edit (DV-14), because proceeding leaves the path
270
+ * indistinguishable from never-touched. `{ok:true, minted}` — see {@link FileHistoryTrackResult}:
271
+ * `minted:true` means this call created the record, which is what makes {@link annulTrack} legal
272
+ * for it (#491).
273
+ */
274
+ trackEdit(scope: string, absPath: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryTrackResult>;
275
+ /**
276
+ * Discard the first-touch record `trackEdit` MINTED for `absPath` — the compensating verb for the
277
+ * one ordering the topology cannot avoid (#491): the pre-image capture must run BEFORE the write,
278
+ * so a write that then fails (a lost create race, ENOSPC, EACCES, a read-only mount) leaves a
279
+ * record for an edit that never happened. Left standing, an `existed-not` v1 makes an untouched
280
+ * path a rewind DELETE target: whoever writes those bytes next — the user, a peer process — gets
281
+ * them removed by a restore that reports `complete`, which is precisely the structural
282
+ * out-of-scope guarantee this contract opens with. So the record must be retractable.
283
+ *
284
+ * Discipline (contract-kit enforced): NEVER-throw like every other verb, and no env IO AT ALL
285
+ * unless the caller asks for the proof read below. Retraction is legal ONLY while the record is
286
+ * still exactly what `trackEdit` minted — a record referenced by ANY committed boundary, or one that has grown
287
+ * past its first version, is REFUSED (typed `conflict`) and left intact, because history that a
288
+ * boundary already froze is no longer the caller's to withdraw. An absent record is an idempotent
289
+ * `{ok:true}` (the retry path). Callers annul ONLY what their own `trackEdit` reported as
290
+ * `minted:true`: a path that was already tracked belongs to the edit that first touched it, and
291
+ * discarding that baseline would be the fake-pristine hole DV-14 exists to weld shut.
292
+ *
293
+ * `verify` is the EVIDENCE arm (adversarial round 2). A caller whose write failed with a code the
294
+ * FileSystem contract defines as "nothing was written" (`already_exists`, `precondition_failed`)
295
+ * has proof and passes nothing. Every other failure — a plain write is not contractually atomic,
296
+ * so ENOSPC, a dying transport or a thrown backend may have TRUNCATED the file before reporting —
297
+ * must hand an `env` here: the store then re-reads the path ONCE and retracts only if the world
298
+ * still matches what the mint recorded (bytes still absent for an existed-not mint, still the same
299
+ * hash for a captured one). A file that changed keeps its record, because the alternative is
300
+ * deleting the only baseline of a file the agent really did modify — damage rewind could no longer
301
+ * reach, which is strictly worse than a stale record.
302
+ */
303
+ annulTrack(scope: string, absPath: string, root: string, verify?: {
304
+ env: ExecutionEnv;
305
+ signal?: AbortSignal;
306
+ }): Promise<FileHistoryResult>;
307
+ /**
308
+ * Record a history boundary keyed by `entryId` — the TURN'S OWN initial session entry, minted
309
+ * after that entry is appended and BEFORE any tool executes, so the boundary means "file state
310
+ * when `entryId`'s turn began" (≡ the reference's user-message-uuid snapshot; every turn
311
+ * incarnation appends a fresh entry, so keys never alias across re-branches from the same fork
312
+ * point). For each tracked file: re-stat via `env`; unchanged (metadata may prove inequality,
313
+ * content hash proves equality — DV-13: never mtime) ⇒ reference the prior version; changed ⇒
314
+ * store a new version; missing ⇒ record a deletion marker. Zero directory-tree traversal, zero
315
+ * size gate. Idempotent per (scope, entryId) over COMMITTED boundaries.
316
+ *
317
+ * Commit visibility + fencing + CAPTURE LEASE (contract-kit enforced): a boundary becomes visible
318
+ * to canRestore/restore ONLY after its complete version mapping is durable. Each attempt is
319
+ * FENCED (a timed-out/aborted attempt may never commit — {@link boundaryPublishVerdict}, no grace
320
+ * threshold), and the boundary's capture window is a LEASE that the caller closes before the
321
+ * first tool executes: after lease close, a fresh CAPTURE (any env read) for that entryId is
322
+ * refused — a retry may only PUBLISH a mapping whose bytes were fully captured before close.
323
+ * The close is NOT persisted and the store has no close verb (design/381 §10 S-6): close is the
324
+ * calling Runner's in-process discipline; after a crash the new process has no capture path for
325
+ * the old entryId (its own prepare mints only its own turn's boundary), and abandoned staging is
326
+ * judged by COMPLETENESS alone — complete ⇒ publishable, incomplete ⇒ abandoned (that turn has no
327
+ * boundary) — with ZERO env reads on the retry path. A turn whose pre-turn capture failed simply
328
+ * has no boundary (disclosed; the reference behaves the same) — there is NO mid-turn re-capture,
329
+ * because re-reading after edits would publish post-turn bytes under a pre-turn key. An entryId
330
+ * whose boundary was COMMITTED and later REAPED is closed by the same law and for the same reason
331
+ * (see {@link FileHistoryStore.reap}): it refuses typed with zero env reads rather than
332
+ * re-capturing a world that has moved on. Retention can therefore make a boundary unavailable,
333
+ * but never wrong.
334
+ */
335
+ snapshot(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryResult>;
336
+ /**
337
+ * Converge ONLY the tracked set to the boundary `entryId`: for each tracked file, target-version
338
+ * bytes ⇒ write via `env` (the guarded-write arm and the symlink/parent-chain/kind preflights are
339
+ * the design/381 slice-3 hardening — until then the write is plain and ADVISORY, stated, never
340
+ * overclaimed); deletion marker ⇒ remove; identical ⇒ untouched; tracked-later-than-target ⇒ fall
341
+ * back to its v1 (pristine) version, and a base-unavailable path (DV-14) is REFUSED with reason
342
+ * `base_unavailable` (left at its current state — never overwritten with a fake baseline).
343
+ * Per-file refusals SKIP that file and are counted, never failing the whole restore (CC form).
344
+ * Paths recorded relative to `root` re-join against the CURRENT root (worktree/moved-cwd safe).
345
+ *
346
+ * Partial outcome is FIRST-CLASS with a three-state discriminant and the per-file ledger always
347
+ * travels: applied / identicalSkipped / refused / failed are SEPARATE columns (a security refusal
348
+ * must never hide inside a benign skip count), so a caller can distinguish "nothing was mutated"
349
+ * from "the env died after k writes". Restore is per-file idempotent (identical files skip), so
350
+ * the documented recovery for a partial outcome is: re-run the same restore; it converges. There
351
+ * is NO all-or-nothing guarantee and none is claimed. Task-level policy on `"partial"` lives in
352
+ * the CALLER (prepare defaults to fail-loud; see DV-15).
353
+ */
354
+ restore(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryRestoreResult>;
355
+ /** Whether a boundary exists for (scope, entryId). */
356
+ canRestore(scope: string, entryId: string): Promise<boolean>;
357
+ /**
358
+ * Optional dry-run: per-file change stats for a prospective restore (CC diffStats; design/381
359
+ * slice 3). A-2 two-state law: `null` = preview UNAVAILABLE ≠ an empty stats object = a true
360
+ * zero-coverage answer — an absent preview must never be rendered as "nothing will change".
361
+ *
362
+ * It is an UPPER BOUND on what a restore will touch, and the reason is worth stating rather than
363
+ * discovering: the preview answers the CONTENT question (which tracked files differ from the
364
+ * boundary, and by how many lines), while the per-file security preflight — symlink/kind, parent
365
+ * chain — is decided by `restore` against the world as it stands THEN. A file listed here can
366
+ * therefore come back refused, and a preview that pre-ran those checks would be making promises
367
+ * about a world it does not own. Files whose target cannot be resolved at all (a missing version
368
+ * record or blob, an unsafe key) are left OUT: the restore reports them per file, and a preview
369
+ * cannot show the size of a change it cannot compute.
370
+ */
371
+ diffStats?(scope: string, entryId: string, env: ExecutionEnv, root: string): Promise<FileHistoryDiffStats | null>;
372
+ /**
373
+ * GC: drop boundaries in `scope` not in `keepEntryIds`; then drop version records (and content
374
+ * blobs) referenced by NO surviving boundary — EXCEPT v1 records AND base-unavailable markers
375
+ * (DV-14), which are retained while the scope lives (the pristine fallback and its explicit
376
+ * refusal state are both part of the graph). Returns the number of boundaries dropped.
377
+ *
378
+ * WHO CALLS IT (design/381 §片2 — the bound is the backend's, not a chore left to the host): both
379
+ * bundled backends drive this verb THEMSELVES. Every committed boundary is followed by a
380
+ * retention pass that trims the scope to the newest
381
+ * {@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP} boundaries —
382
+ * an engine default a deployment overrides through the store's own `retention` option, including
383
+ * `keep: "unbounded"` to switch the self-trim off and take the schedule back. The pass is
384
+ * best-effort by construction (a GC failure never fails the turn that triggered it) and it only
385
+ * ever removes boundaries the caller could not name any more anyway.
386
+ *
387
+ * A backend outside this repo therefore owes ONE of two things, and silence is neither: its own
388
+ * bound (this verb, driven from its own commit path), or a documented statement that the host
389
+ * must schedule `reap`. An unbounded boundary graph is not a tuning choice — one boundary per
390
+ * turn, forever, is a disk that fills with no line anywhere admitting it would.
391
+ *
392
+ * WHAT THE SELF-TRIM DOES NOT COVER, said here rather than left to be discovered: it runs at a
393
+ * COMMIT, so a graph that arrives whole — `adoptScope`, `importHistory` — is published whole and
394
+ * comes under the bound at that scope's next commit, not before. That is deliberate rather than
395
+ * an oversight (round 2 raised it): both verbs promise all-or-nothing visibility of the graph the
396
+ * caller handed over, and trimming during publication would silently discard history the host
397
+ * explicitly asked to transfer. The overshoot is bounded by the envelope the host chose, which is
398
+ * a different thing from the per-turn source this bound exists to stop.
399
+ *
400
+ * A REAPED entryId IS SPENT (adversarial review — a law this verb acquired the day anything
401
+ * started calling it): dropping a boundary must not hand its key back. Until boundaries could be
402
+ * dropped, the committed mapping was itself the record that an entryId had been consumed, and
403
+ * `snapshot`'s idempotent short-circuit read it; a store that forgot a reaped id would let a
404
+ * later `snapshot` for that key take the CAPTURE path and publish post-turn bytes under a
405
+ * pre-turn key. Both bundled backends therefore tombstone what they reap and refuse a later
406
+ * capture of it, typed and with zero env reads. The ledger is IDS, not mappings, and it is
407
+ * unbounded — stated plainly because a bound's own residue growing forever would be a poor joke:
408
+ * one id string per boundary ever reaped, against the complete tracked-set mapping each of those
409
+ * boundaries used to carry. It is the cheap part of what the bound throws away, by two orders of
410
+ * magnitude, and it is what makes the bound safe rather than merely small.
411
+ *
412
+ * Concurrency is unchanged and stays the caller's to reason about: a pass that runs while another
413
+ * entryId's capture is parked at an env read may drop a version that capture staged, and that
414
+ * capture then refuses TYPED (its turn has no boundary) rather than publishing a dangling
415
+ * reference — the arm the commit paths already validate for.
416
+ */
417
+ reap(scope: string, keepEntryIds: string[]): Promise<number>;
418
+ /**
419
+ * Cross-session carry (CC copyFileHistoryForResume): make `toScope` see `fromScope`'s boundaries
420
+ * + versions (content-addressed backends copy references, zero bytes). Same-scope call is a
421
+ * no-op. ATOMIC-VISIBILITY, same law as snapshot: the WHOLE adopted graph (tracked set, every
422
+ * version incl. base-availability state, every referenced blob) is staged and verified, then
423
+ * published in one step — `toScope` either sees the complete history or none of it. A
424
+ * failed/partial adopt publishes NOTHING; the resume still proceeds (same availability posture as
425
+ * the reference's tolerated partial copy, without the half-visible graph) and the failure is
426
+ * disclosed. EMPTY-TARGET ONLY: adoption is refused (loud, typed `conflict`) once `toScope` has
427
+ * ANY history of its own — adoption's seat is the fork/resume boundary BEFORE the new session's
428
+ * first turn; a retry after the target has begun writing would need a destructive merge
429
+ * (version-number collisions, overwritten target history) that this contract deliberately
430
+ * forbids. Retry while the target is still empty is idempotent and repairs abandoned staging.
431
+ *
432
+ * WHO CALLS IT — THE ENGINE DOES, since design/381 slice 3, and the seat is worth knowing because
433
+ * it decides WHEN this verb runs against your backend. `prepareTask` carries a forked session's
434
+ * history on the first turn it prepares for it, keyed on the LINEAGE FACT
435
+ * (`SessionMetadata.forkedFrom`) rather than on the code path that forked — so the Agent tool's
436
+ * fork route, `warmResume`, and a deployment that forks a session and then runs a task on it all
437
+ * reach the same seat. It runs BEFORE the restore seat, so the very first task on a fork may
438
+ * itself ask to restore files to a pre-fork boundary.
439
+ *
440
+ * The empty-target law is what makes that safe to attempt rather than schedule: a scope that
441
+ * already holds history answers the typed `conflict`, which at that seat MEANS "already carried,
442
+ * or grown its own" and is treated as a no-op. Only a non-conflict failure is disclosed
443
+ * (`onError` phase `"rewind"`), because that one is a carry the deployment expected and did not
444
+ * get. A failed carry never blocks the fork's run.
445
+ *
446
+ * A HOST MAY STILL CALL IT — the engine's seat covers sessions the engine prepares, and a
447
+ * deployment that copies history between scopes for its own reasons (a migration, a scope rename)
448
+ * is calling the same verb under the same law. What a MISSED carry gets is degraded, not broken,
449
+ * and it is worth naming precisely: the new scope starts EMPTY, so nothing the old one tracked is
450
+ * restorable from it. A rewind aimed at a pre-fork boundary answers "no boundary" (honest, typed)
451
+ * instead of converging those files, and the new lineage's own first touch of a path records THAT
452
+ * path's then-current bytes as its pristine v1 — a correct baseline for the new lineage, just not
453
+ * the old one's history. Nothing is silently mis-restored; the carry simply does not happen.
454
+ */
455
+ adoptScope(fromScope: string, toScope: string): Promise<FileHistoryResult>;
456
+ /**
457
+ * Session-sync export/import (2c), versioned envelope — see {@link FileHistoryExport}. Optional.
458
+ * importHistory obeys the SAME atomic-visibility + staged-verify law as adoptScope: every
459
+ * referenced blob fetched and integrity-checked, then the scope's graph published in one step; a
460
+ * mid-import failure publishes nothing. NON-DESTRUCTIVE TARGET LAW (same family as adoptScope): a
461
+ * first import requires an EMPTY target graph; a retry is legal only while the target is still
462
+ * empty OR already holds a graph with the exact same import identity/digest (pure idempotent
463
+ * re-publish). A non-empty target with any other content ⇒ typed `conflict` refusal — atomic
464
+ * visibility prevents half-graphs, this law prevents publishing the WRONG complete graph over
465
+ * locally-grown history.
466
+ */
467
+ /**
468
+ * NO `dispose` / lifecycle verb, and that is a decision rather than an omission (design/381 slice 3,
469
+ * the R-6 review candidate — recorded here because the next reader will otherwise wonder). The case
470
+ * for one: a directly-constructed file backend holds no store lock, so the single-writer premise is
471
+ * enforced only where a deployment assembles the backend (its data-root LOCK), and a `dispose`
472
+ * would let a store hold a lease and release it. The case against, which won:
473
+ * · every verb here is self-contained — a store holds no cross-call resource, so `dispose` would be
474
+ * an obligation on EVERY backend (a SQL twin included) in order to serve one backend's optional
475
+ * locking;
476
+ * · a lock released by a verb nobody is required to call fails WORSE than no lock: a crash leaves a
477
+ * stale lease that blocks a store which today simply works, and the recovery is manual;
478
+ * · the single-writer premise is the deployment's to make. The assembled backend is where it is
479
+ * enforced, and a directly-constructed store is the same "I know what I am doing" path every
480
+ * sibling file store in this family offers.
481
+ * A deployment that needs cross-process exclusion takes the assembled backend, or brings its own.
482
+ */
483
+ exportHistory?(scope: string): Promise<FileHistoryExport | null>;
484
+ importHistory?(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
485
+ }
486
+ /**
487
+ * design/381 §3 + §10 T-5 — the FENCING judge for a boundary-capture attempt, exposed as a
488
+ * direct-callable function (the G6b grey-box seat) and used by every in-tree backend as the single
489
+ * commit gate: an attempt whose signal fired — timeout or abort, no matter how close to completion
490
+ * the capture got — may NEVER publish its mapping. ABSOLUTE, no grace threshold (§10 T-5: "无宽限
491
+ * 期" — the verdict has no time axis at all): a late-completing capture may have read bytes AFTER
492
+ * the lease closed (post-edit bytes), and publishing them under the pre-turn key is exactly the
493
+ * poisoning the fence exists to stop.
494
+ */
495
+ export declare function boundaryPublishVerdict(attempt: {
496
+ aborted: boolean;
497
+ }): {
498
+ publish: true;
499
+ } | {
500
+ publish: false;
501
+ reason: "aborted";
502
+ };
503
+ /** One captured state of one file, as read through the env (the shared capture half). */
504
+ export type FileBackupCapture =
505
+ /** `mode` = the env's POSIX permission bits when it HAS that face (design/381 DV-8); absent when
506
+ * the env cannot express them — never fabricated, so a restore can tell "this mode was recorded
507
+ * and could not be applied" from "there was never a mode to keep". */
508
+ {
509
+ state: "file";
510
+ bytes: Uint8Array;
511
+ hash: string;
512
+ mode?: number;
513
+ parentIdent?: string;
514
+ } | {
515
+ state: "absent";
516
+ parentIdent?: string;
517
+ } | {
518
+ state: "aborted";
519
+ }
520
+ /** Non-ENOENT read failure: the file may exist but its bytes are not capturable right now. */
521
+ | {
522
+ state: "unreadable";
523
+ cause: string;
524
+ };
525
+ /**
526
+ * Capture ONE file's current state through the ExecutionEnv — **the env-operating half every
527
+ * backend MUST reuse** (design/381 §3 分工, the E19 captureManifest condition carried forward:
528
+ * re-implementing it per backend is how symlink-escape/fail-closed bugs come back). Used by
529
+ * `trackEdit` (first-touch pristine) and `snapshot` (per-file re-read). `not_found` is the sole
530
+ * absent signal (the read itself is the authoritative existence probe — a separate stat would only
531
+ * re-open the same race one window later). The parent-identity guard rides the same frame:
532
+ * `canonicalPath(parent)` — root-relativized when under `root` (DV-6: a legal whole-root move must
533
+ * keep verifying; an in-root parent-chain swap must keep refusing) — recorded best-effort (an env
534
+ * that cannot canonicalize omits it; the slice-3 preflight then has nothing to compare, which is
535
+ * the honest degraded form, never a fabricated identity).
536
+ */
537
+ export declare function captureFileBackupViaEnv(env: ExecutionEnv, absPath: string, root: string, signal?: AbortSignal): Promise<FileBackupCapture>;
538
+ /**
539
+ * The retraction PROOF READ (design/381 #491 + adversarial round 2), shared by every backend so the
540
+ * evidence rule cannot drift: does the path still look exactly as the first touch recorded it? An
541
+ * existed-not mint requires the path to still be absent; a captured mint requires the same content
542
+ * hash. Anything else — including an unreadable path — leaves the record standing, because the only
543
+ * failure this arm exists to distinguish is "the write never landed" from "the write may have
544
+ * changed the file". A base-unavailable marker is REFUSED here rather than waved through
545
+ * (adversarial round 3): it carries no bytes to compare, but the marker itself is the durable
546
+ * refusal state that stops a later touch from minting already-modified bytes as pristine — erasing
547
+ * it on an ambiguous failure re-opens exactly the DV-14 hole it exists to close. It stays
548
+ * retractable through the `"proven"` path, which needs no evidence read at all.
549
+ */
550
+ export declare function worldStillMatchesMint(env: ExecutionEnv, absPath: string, root: string, baseAvailable: boolean, mintedBlobHash: string | null, signal?: AbortSignal): Promise<{
551
+ unchanged: true;
552
+ } | {
553
+ unchanged: false;
554
+ reason: string;
555
+ }>;
556
+ /**
557
+ * DV-13's metadata rule applied to the MODE axis (design/381 slice 3), and it is a rule about
558
+ * EVIDENCE rather than about equality: metadata may prove two states DIFFERENT, never the same. Two
559
+ * known-and-unequal modes are a proven difference, so the file is versioned again even though its
560
+ * bytes are identical (the permission bit is part of the state a rewind restores). An UNKNOWN mode
561
+ * on either side proves nothing and must not manufacture a version — which is also what keeps a
562
+ * graph captured before this field existed from re-versioning its whole tracked set on the first
563
+ * boundary after an upgrade.
564
+ */
565
+ export declare function modeProvenDifferent(recorded: number | undefined, current: number | undefined): boolean;
566
+ /** The target one file must converge to (bytes+hash, or "must not exist"). */
567
+ export type FileRestoreTarget = {
568
+ bytes: Uint8Array;
569
+ hash: string;
570
+ } | {
571
+ absent: true;
572
+ };
573
+ /**
574
+ * The recorded facts the slice-3 preflight judges the current world against, plus the abort signal
575
+ * (design/381 §6 slice 3). Every member is OPTIONAL on purpose: a record captured by an env without
576
+ * the mode face, or without canonicalization, carries neither — and a guard with nothing recorded to
577
+ * compare must degrade to "no opinion", never to a fabricated expectation.
578
+ */
579
+ export interface FileRestoreApplyOptions {
580
+ /** The task root the recorded key was re-joined against — the coordinate `parentIdent` lives in. */
581
+ root: string;
582
+ /** The parent identity recorded WITH the version being restored ({@link parentIdentityViaEnv}). */
583
+ parentIdent?: string;
584
+ /** The POSIX mode recorded with the version being restored (design/381 DV-8). */
585
+ mode?: number;
586
+ signal?: AbortSignal;
587
+ }
588
+ export type FileRestoreApplyOutcome =
589
+ /** `modeNotPreserved` rides the two NON-refusal arms: the bytes converged but the recorded mode
590
+ * could not be re-applied (no env mode face, or the env refused/failed the change). A disclosure,
591
+ * never a failure — the file's CONTENT is what the restore promised. */
592
+ {
593
+ outcome: "applied";
594
+ modeNotPreserved?: true;
595
+ } | {
596
+ outcome: "identical";
597
+ modeNotPreserved?: true;
598
+ } | {
599
+ outcome: "refused";
600
+ reason: string;
601
+ } | {
602
+ outcome: "failed";
603
+ reason: string;
604
+ };
605
+ /**
606
+ * Converge ONE file to `target` through the ExecutionEnv — **the env-operating + security half
607
+ * every backend MUST reuse** (design/381 §3 分工). skip-if-identical is content-hash proven (DV-13:
608
+ * metadata may only ever prove INEQUALITY, never equality; mtime is never consulted), so an
609
+ * identical file is untouched (no write, no mtime churn). Deletion markers remove the current file
610
+ * (ENOENT tolerated = already converged).
611
+ *
612
+ * THE PREFLIGHT FAMILY (design/381 slice 3, DV-6 — the env-level approximation of the reference's
613
+ * fd-level `O_NOFOLLOW` + dev/ino re-check, ordered cheapest-refusal-first). Every arm SKIPS the
614
+ * one file and is counted; none of them fails the whole restore:
615
+ * · KIND — the addressed object (no-follow `fileInfo`) must be a regular file or absent. A symlink
616
+ * at the leaf is refused rather than followed: the recorded bytes belong to the file the agent
617
+ * edited, not to whatever a link now points at. A directory/socket/FIFO in its place is the same
618
+ * refusal for the same reason.
619
+ * · PARENT CHAIN — the current canonical parent identity must equal the one recorded with the
620
+ * version ({@link parentIdentityViaEnv}, root-relative under the root). A legal whole-root move
621
+ * keeps it equal (E4 passes); an in-root ancestor swapped for a link out of the tree does not
622
+ * (E5 refuses, and nothing outside the root is written). BOTH must hold at once — relaxing to
623
+ * "moves also refuse" or "swaps also pass" is a mis-fix, not a tuning choice.
624
+ * · The guard is EVIDENCE-BASED, not assumption-based: a record with no parent identity (an env
625
+ * that cannot canonicalize) and a parent that cannot be canonicalized NOW (the ordinary "the
626
+ * directory was deleted and this restore recreates it" case) both mean "nothing to compare", and
627
+ * inventing a refusal there would break the recreate arm rather than close a hole.
628
+ *
629
+ * THE WRITE uses `env.writeFileGuarded` when the env has it — one atomic verify-and-write whose
630
+ * expectation names the addressed path and forbids following a leaf link, so the window between
631
+ * this preflight and the write is closed at the backend. An env WITHOUT it falls back to plain
632
+ * `env.writeFile` and the convergence is then ADVISORY on the TOCTOU axis: the preflight above is a
633
+ * check-then-act, and a sufficiently fast racer can still swap the leaf in between. Stated here
634
+ * because the alternative — a two-step emulation behind an atomic-looking face — is exactly what
635
+ * that capability's own contract forbids.
636
+ *
637
+ * MODE (DV-8): a recorded mode is re-applied through the optional `setFileMode` face after the
638
+ * bytes land (and on an otherwise-identical file whose mode drifted, which IS a convergence). An env
639
+ * without the face — or one that refuses the change — yields `modeNotPreserved`, disclosed by the
640
+ * caller's ledger; it never turns a successful content restore into a failure.
641
+ */
642
+ export declare function applyFileRestoreViaEnv(env: ExecutionEnv, absPath: string, target: FileRestoreTarget, opts: FileRestoreApplyOptions): Promise<FileRestoreApplyOutcome>;
643
+ /**
644
+ * ONE file's contribution to {@link FileHistoryStore.diffStats}, shared by both bundled backends:
645
+ * `undefined` = this file would NOT change (already at the target, or already absent for a deletion
646
+ * marker), otherwise the line arithmetic of the change. Content-hash proven, exactly like the
647
+ * restore's own identical-skip — a preview that used a cheaper test than the operation it previews
648
+ * would disagree with it.
649
+ */
650
+ export declare function previewFileDelta(env: ExecutionEnv, absPath: string, targetHash: string | null, targetBytes: Uint8Array | undefined, targetMode?: number): Promise<{
651
+ insertions: number;
652
+ deletions: number;
653
+ } | undefined>;
654
+ interface FileHistoryVersionRecord {
655
+ version: number;
656
+ /** Content-addressed blob hash, or null = "did not exist at this point" (existed-not marker —
657
+ * serves both the pre-creation v1 arm and later deletion arms). */
658
+ blobHash: string | null;
659
+ mode?: number;
660
+ parentIdent?: string;
661
+ backupTime: string;
662
+ }
663
+ interface TrackedFileState {
664
+ /** DV-14: false ⇒ the pristine base could not be captured; a durable refusal state, not an
665
+ * absence — versions then start ABOVE v1 and v1 is never minted later. */
666
+ baseAvailable: boolean;
667
+ /** The key's self-description (§3 amendment): recorded at the mint so a graph that later crosses
668
+ * machines can be judged instead of guessed. */
669
+ keyDesc: FileHistoryTrackKeyDescriptor;
670
+ /** Set when a LATER trackEdit for this path was served (answered `minted:false`) — that lane is
671
+ * now relying on this record as its own pre-edit baseline, so the original minter may no longer
672
+ * retract it (adversarial round 1: two lanes racing one path, the first failing its write, would
673
+ * otherwise delete the baseline the SECOND lane's successful edit depends on). */
674
+ observedByLaterTouch?: boolean;
675
+ versions: FileHistoryVersionRecord[];
676
+ }
677
+ /** One staged per-file outcome of a capture attempt (commit assigns version numbers atomically). */
678
+ type StagedFileCapture = {
679
+ kind: "unchanged";
680
+ version: number;
681
+ } | {
682
+ kind: "changed";
683
+ blobHash: string | null;
684
+ mode?: number;
685
+ parentIdent?: string;
686
+ };
687
+ /**
688
+ * In-memory, content-addressed reference {@link FileHistoryStore}. Blobs are deduplicated by sha256
689
+ * ACROSS all scopes and versions (a file unchanged between boundaries stores zero new bytes); each
690
+ * tracked file keeps a version chain, each boundary a complete `trackKey → version` mapping over
691
+ * the tracked set as of its mint. Single-process (the default-deps reference; the durable
692
+ * file-backed impl is design/381 slice 2).
693
+ *
694
+ * Grey-box fault seams (design/381 §7 C7 / §10 T-4): `persistFirstTouch` and `publishBoundary` are
695
+ * `protected` COMMIT points — a test subclass overrides one to throw and the public verb answers
696
+ * the typed `{ok:false}` the contract promises (the store itself still never throws).
697
+ *
698
+ * RETENTION is built in (design/381 §片2): each committed boundary is followed by a trim back to
699
+ * {@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP} newest boundaries per scope, overridable through
700
+ * {@link InMemoryFileHistoryStoreOptions.retention}. Even a single-process reference store needs
701
+ * it — a long-lived process IS the unbounded case.
702
+ */
703
+ export interface InMemoryFileHistoryStoreOptions {
704
+ /** design/381 §片2 — the built-in boundary bound and its override. Omitted ⇒ the engine default
705
+ * ({@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP}); a malformed policy is REFUSED here, at the wiring
706
+ * site, rather than silently becoming a different policy inside a GC pass nobody watches. */
707
+ retention?: FileHistoryRetentionPolicy;
708
+ }
709
+ export declare class InMemoryFileHistoryStore implements FileHistoryStore {
710
+ private readonly scopes;
711
+ /** Resolved boundary cap, or `null` for the explicit `"unbounded"` opt-out (host owns the GC). */
712
+ private readonly boundaryKeep;
713
+ private readonly blobs;
714
+ /** Blob hashes written by an in-flight capture BEFORE its commit — a racing reap must treat them
715
+ * as live (same MAJOR-1 posture as the retired whole-tree store). REFCOUNTED, not a set
716
+ * (adversarial round): two concurrent captures can lease the same new hash, and the first
717
+ * attempt's release must not strip the second attempt's liveness. */
718
+ private readonly inFlightBlobs;
719
+ constructor(opts?: InMemoryFileHistoryStoreOptions);
720
+ /**
721
+ * The RETENTION pass, run after a boundary commits (design/381 §片2 — see {@link FileHistoryStore.reap}
722
+ * for why the backend owns this rather than the host). Boundaries are an insertion-ordered map
723
+ * here, so commit order is exact and the selection needs no clock.
724
+ *
725
+ * Never throws and never fails the commit that triggered it: the boundary is already published and
726
+ * a GC that could not run is a graph that is merely larger than its bound — the strictly safer of
727
+ * the two failure directions, and the same posture the reference implementation's own eviction has.
728
+ */
729
+ private applyBoundaryRetention;
730
+ private leaseBlob;
731
+ private releaseBlob;
732
+ private scope;
733
+ /** PROTECTED COMMIT SEAM (fault-injectable): durably record one path's first-touch state.
734
+ * Synchronous single assignment = the "ONE atomic durable record" of DV-14 for this backend. */
735
+ protected persistFirstTouch(scope: string, key: string, state: TrackedFileState): void;
736
+ /** PROTECTED COMMIT SEAM (fault-injectable): publish a captured boundary in ONE synchronous step
737
+ * — version records + mapping become visible together or not at all (atomic visibility). */
738
+ protected publishBoundary(scope: string, entryId: string, staged: Map<string, StagedFileCapture>): void;
739
+ trackEdit(scope: string, absPath: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryTrackResult>;
740
+ annulTrack(scope: string, absPath: string, root: string, verify?: {
741
+ env: ExecutionEnv;
742
+ signal?: AbortSignal;
743
+ }): Promise<FileHistoryResult>;
744
+ snapshot(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryResult>;
745
+ restore(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryRestoreResult>;
746
+ canRestore(scope: string, entryId: string): Promise<boolean>;
747
+ diffStats(scope: string, entryId: string, env: ExecutionEnv, root: string): Promise<FileHistoryDiffStats | null>;
748
+ reap(scope: string, keepEntryIds: string[]): Promise<number>;
749
+ adoptScope(fromScope: string, toScope: string): Promise<FileHistoryResult>;
750
+ exportHistory(scope: string): Promise<FileHistoryExport | null>;
751
+ /** The export projection as a SYNCHRONOUS function — the import's final claim needs to compare a
752
+ * target's identity without yielding (see the publish step). */
753
+ private exportOf;
754
+ importHistory(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
755
+ /** The import body inside the blob lease (see {@link importHistory}). */
756
+ private importStaged;
757
+ }
758
+ /** Test-only window (family-aware path-coordinate pins + the DV-6 track-key coordinate): pure
759
+ * helpers whose behavior carries a fence/restore invariant. Not re-exported from src/index.ts. */
760
+ export declare const __testables: {
761
+ joinUnder: typeof joinUnder;
762
+ relUnder: typeof relUnder;
763
+ stripTrailSep: (p: string) => string;
764
+ trackKeyOf: typeof trackKeyOf;
765
+ resolveTrackKey: typeof resolveTrackKey;
766
+ parentDirOf: typeof parentDirOf;
767
+ };
768
+ export {};