@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
@@ -67,6 +67,32 @@ export declare function contextEditFrontier(window: number): number;
67
67
  * re-run when the frontier pass is off → in-turn forced compaction behind the adopt seam → trim
68
68
  * demoted to the disaster-only last resort), so the default configuration never hands an
69
69
  * under-20k refusal straight to a message-dropping trim.
70
+ *
71
+ * WHOLE-MACHINE CROSS-CHECK against the 2.1.250 corpus (design/374 slice 4 — the "the knob number
72
+ * was copied, the machine was not" disease is checked by naming the machine's parts, not the one
73
+ * number). CC's four load-bearing parts, each against its 250 coordinate, all verified present here:
74
+ * ① the ≥20000-token minimum-savings gate — {@link MIN_CLEAR_SAVINGS_TOKENS} vs `Mnn`
75
+ * (cli250.js:41714, `BBn`: `if (tokensSaved < Mnn) return null`). CC gates TWICE (execution +
76
+ * before asking the server to reject); sema has one point because the "ask for the rejection"
77
+ * half needs a cooperating server (see ④).
78
+ * ② the nine-member clearable whitelist — {@link COMPACTABLE_TOOLS} vs `eQt` (cli250.js:41714),
79
+ * re-resolved member-by-member off the 250 corpus, byte-identical (see that constant).
80
+ * ③ cleared-but-recoverable — CC replaces the content with a POINTER, not a void
81
+ * (`<persisted-output>Tool result saved to: {path}\n\nUse Read to view</persisted-output>`,
82
+ * cli250.js:40044), which is what makes a keep window of 5 survivable at all. Ours is the same
83
+ * construction in this engine's carrier: the full text is offloaded first and the marker
84
+ * carries the store ref ({@link ContextEditOptions.offload} → {@link clearOneResult}), read
85
+ * back through ReadToolResult instead of a filesystem path (registered divergence D-9). The
86
+ * runner wires it on BOTH arms by default — the frontier pass and the MC-R rejection arm share
87
+ * one persist closure — so the shipped default clears to a pointer, never to a dead end.
88
+ * ④ the TRIGGER — CC's client machine fires ONLY on the server's `context_hint` 422/424 refusal
89
+ * (cli250.js:58375; `tengu_time_based_microcompact` is a historical event name, its `trigger`
90
+ * is the constant `"context_hint"`). Under BYOM there is no such server, so the ask half is
91
+ * unportable and the refusal half is aligned on wire truth (the MC-R arm classifies the
92
+ * provider's own context-overflow refusal) — registered divergence D-3. sema additionally
93
+ * keeps a PROACTIVE frontier trigger CC's client does not have, anchored to Anthropic's
94
+ * platform context-management policy rather than to CC-CLI — registered divergence D-1, and
95
+ * re-stated at {@link clearStaleToolResults}'s ANCHOR paragraph.
70
96
  */
71
97
  export type ContextEditMachine = "legacy" | "cc";
72
98
  /**
@@ -82,8 +108,10 @@ export type ContextEditMachine = "legacy" | "cc";
82
108
  * exactly the terminal-batch shape that needs it. This is why the slice-3 "keep default 3→5"
83
109
  * flip rides the MACHINE default, not this constant: painting 5 onto the opt-out machine would
84
110
  * recreate the probed failure inside the escape hatch.
85
- * - CC machine (the default since slice 3): 5 (2.1.223 `uAp`; same value on CC 88's
86
- * `timeBasedMCConfig` stable across both corpus generations). The #384 adjudication adopted 5
111
+ * - CC machine (the default since slice 3): 5 (2.1.223 `uAp`; 2.1.250 `var k = 5` at the call site
112
+ * cli250.js:58375, and 2.1.245 carries the same value zero drift across 245→250; same value
113
+ * again on CC 88's `timeBasedMCConfig`, i.e. stable across all three corpus generations, and it
114
+ * is a hard-coded constant upstream, not a setting). The #384 adjudication adopted 5
87
115
  * CONDITIONAL on the whole machine coming with it (20k gate + deep clear + re-ordered
88
116
  * backstops); the cc machine carries that condition, and slice 3's backstop re-ordering
89
117
  * completed it — 5 is now the effective default keep, and the 200k probe above is answered by
@@ -92,7 +120,8 @@ export type ContextEditMachine = "legacy" | "cc";
92
120
  */
93
121
  export declare const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
94
122
  export declare const CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS = 5;
95
- /** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773): the cc machine refuses to clear at
123
+ /** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773; 2.1.250 `Mnn` cli250.js:41714, same
124
+ * value): the cc machine refuses to clear at
96
125
  * all unless the structural estimate of what WOULD be cleared reaches this many tokens. Rationale
97
126
  * (independent of "CC does it"): every clear breaks the provider prefix cache once, so a clear that
98
127
  * saves less than the re-prefill it causes is a net loss; the gate prices that trade. The LEGACY
@@ -105,7 +134,18 @@ export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
105
134
  * checklist #54 (CC microCompact.ts:41-50 `COMPACTABLE_TOOLS` parity): the INCLUSION whitelist of
106
135
  * tools whose results may be content-cleared by the stale-result pass. CC's set is exactly
107
136
  * { Read, Bash, PowerShell (SHELL_TOOL_NAMES), Grep, Glob, WebSearch, WebFetch, Edit, Write } —
108
- * cheap-to-regenerate I/O. Everything else is PRESERVED: expensive/decision-bearing results
137
+ * cheap-to-regenerate I/O.
138
+ *
139
+ * RE-VERIFIED against the 2.1.250 corpus (design/374 slice 4, member-by-member — a set is exactly
140
+ * the shape where "the count still matches" is not evidence): `eQt = new Set([wt, ...VD, Yo, Xo,
141
+ * yO, Yr, tn, er])` at cli250.js:41714, whose aliases resolve to `wt`="Read", `VD`=[`Xe`="Bash",
142
+ * `Nt`="PowerShell"], `Yo`="Grep", `Xo`="Glob", `yO`="WebSearch", `Yr`="WebFetch", `tn`="Edit",
143
+ * `er`="Write" — nine members, byte-identical to the set below, zero drift from the 223 reading.
144
+ * The exclusion side is the load-bearing half and is unchanged too: everything NOT here is
145
+ * PRESERVED (Agent/Task/Skill/ToolSearch/TodoWrite/Monitor/SendMessage/NotebookEdit and ALL MCP
146
+ * tools are never content-cleared by this pass, upstream or here).
147
+ *
148
+ * Everything else is PRESERVED: expensive/decision-bearing results
109
149
  * (Agent subagent reports, Task* registry state, TodoWrite acks, Monitor, MCP tools, custom
110
150
  * ToolSpecs) survive to the harder defenses (compaction summarizes them instead of blanking them).
111
151
  * PowerShell is kept for CC fidelity even though sema does not mount it. Overridable per call via
@@ -184,7 +224,9 @@ export interface ContextEditOptions {
184
224
  * Absent ⇒ legacy structural gate + accounting (byte-identical behavior).
185
225
  */
186
226
  anchoredTotalTokens?: number;
187
- /** design/123 D2 — structural coefficient (`Model.charsPerToken`); default 4. */
227
+ /** design/123 D2 — structural coefficient (`Model.charsPerToken`); default 3 (design/374 slice 4;
228
+ * it was 4 before). Note this does NOT reach the cc machine's ≥20k gate, which is CC-exactly
229
+ * hard-coded to 4 — see {@link structuralClearSavings}. */
188
230
  charsPerToken?: number;
189
231
  /**
190
232
  * checklist #54 — which tools' results are clearable. Defaults to {@link COMPACTABLE_TOOLS}
@@ -16,7 +16,7 @@ import type { Model } from "../internal/llm.js";
16
16
  * message AT/BEFORE the anchor reduces it by ZERO (the anchor usage was billed with that content —
17
17
  * a request-view drop only pays off on the NEXT request), so the trim keeps cutting deeper until
18
18
  * the anchored total meets the budget or only the minimal tail is left. Absent ⇒ legacy structural
19
- * behavior, byte-identical. `charsPerToken` (design/123 D2): structural coefficient, default 4.
19
+ * behavior, byte-identical. `charsPerToken` (design/123 D2): structural coefficient, default 3 (design/374 slice 4).
20
20
  */
21
21
  export declare function trimToBudget(messages: AgentMessage[], budgetTokens: number, anchoredTotalTokens?: number, charsPerToken?: number): AgentMessage[];
22
22
  /**
@@ -0,0 +1,106 @@
1
+ /**
2
+ * design/381 §片2 — the engine's OWN bound on file-history growth, and the door its one knob passes
3
+ * through.
4
+ *
5
+ * The hole this closes: `FileHistoryStore.reap` is a complete, contract-tested GC verb that nothing
6
+ * in the engine ever called. Every turn of a wired session commits one more boundary, each boundary
7
+ * is a complete mapping over the tracked set, and no seat anywhere dropped the old ones — so a long
8
+ * session's history grew without bound, silently, which is precisely the shape the bad-value
9
+ * loudness rule exists to forbid (a limit nobody states is still a limit; it is just the disk's).
10
+ * The bundled backends therefore carry the bound THEMSELVES: each one commits a boundary and then
11
+ * trims its scope back to the retention bound, and a host that wants the GC schedule for itself
12
+ * says so (`keep: "unbounded"`) rather than discovering the default by running out of disk.
13
+ *
14
+ * PROVENANCE OF THE DEFAULT (not a taste call, and not a JSDoc-only constraint): the reference
15
+ * implementation caps its own file-history at the newest 100 snapshots and evicts the rest — CC
16
+ * 2.1.250 holds the cap as a module constant (`R1e = 100`) and applies it in the snapshot reducer
17
+ * (`if (E.length > R1e) R = E.slice(-R1e), <evict>(E.slice(0, E.length - R1e), R, …).catch(…)`),
18
+ * i.e. keep the newest N by commit order, hand the evicted ones to a best-effort eviction pass, and
19
+ * never let that pass fail the turn that triggered it. Its eviction unlinks a backup only when no
20
+ * surviving snapshot references it AND its version is not 1 — the same v1-permanence law this
21
+ * store's `reap` already implements. design/381 §片2 carries the number over as "边界上限(默认
22
+ * 100,store 旋钮)", which is the shape below: engine default, store-level, host-overridable.
23
+ *
24
+ * @see {@link import("./retention-policy.js").assertRetentionPolicy} — the sibling row stores' door.
25
+ * This one is separate because its admissible range is different (see {@link refuseRetentionPolicy}).
26
+ */
27
+ export declare const DEFAULT_FILE_HISTORY_BOUNDARY_KEEP = 100;
28
+ /**
29
+ * The file-history retention knob, as a deployment writes it. ONE knob on purpose — the graph has
30
+ * exactly one unbounded dimension (boundaries per scope); version records and blobs are already
31
+ * bounded by it, because `reap` drops every version no surviving boundary references (v1 and the
32
+ * base-unavailable markers excepted, which are per tracked file, not per turn).
33
+ *
34
+ * Absent ⇒ {@link DEFAULT_FILE_HISTORY_BOUNDARY_KEEP}. Every other reading of a badly-formed value
35
+ * is a REFUSAL at construction, never a quiet fallback — see {@link resolveFileHistoryRetention}.
36
+ */
37
+ export interface FileHistoryRetentionPolicy {
38
+ /**
39
+ * How many of a scope's NEWEST boundaries survive each retention pass. A whole number `>= 1`, or
40
+ * the literal `"unbounded"` to switch the store's self-trim OFF and take the GC schedule over
41
+ * (the host then owns calling {@link import("./file-history-store.js").FileHistoryStore.reap};
42
+ * nothing else collects boundaries).
43
+ *
44
+ * `0` is REFUSED rather than accepted as "keep nothing": a boundary is minted by the very commit
45
+ * that would then reap it, so a store configured that way would answer `ok` to every `snapshot`
46
+ * and `false` to every `canRestore` — a rewind seat that is on, reports success, and can never
47
+ * restore anything. `"unbounded"` is the ONE spelling of "do not trim"; omitting the knob is NOT
48
+ * that spelling — it selects the engine default and therefore trims (cross-review round 3 caught
49
+ * this said the opposite here and in the refusal text, which would have sent an operator who
50
+ * wanted everything kept down the path that deletes).
51
+ */
52
+ keep?: number | "unbounded";
53
+ }
54
+ /**
55
+ * The retention door: resolve a deployment's policy to a boundary cap, or REFUSE loudly.
56
+ *
57
+ * Returns the cap (a whole number `>= 1`), or `null` for the explicit `"unbounded"` opt-out. Called
58
+ * at CONSTRUCTION by every bundled backend, so a malformed policy fails where the operator wired it
59
+ * rather than mid-session inside a GC pass they cannot see.
60
+ *
61
+ * The three ways a value can be bad each get their own named exit — none of them falls back to the
62
+ * default, because a retention knob that quietly became a different number is the failure this whole
63
+ * seam exists to remove:
64
+ * 1. GARBAGE — the policy is not a knob bag, a knob is misspelled, or `keep` is not a whole number
65
+ * (NaN from a `Number(cfg.keep)` on a missing field, `Infinity`, `2.5`, `"100"`, `null`).
66
+ * 2. OUT OF RANGE — a negative `keep`. (Reached by the same route as NaN in the sibling stores'
67
+ * history: `slice(Math.max(0, keep))` turns both into `slice(0)` = delete everything.)
68
+ * 3. CONTRADICTORY — `keep: 0`, well-formed and in the range its siblings accept, but self-cancelling
69
+ * here; refused with the ONE spelling of "do not trim" named, and with what omission really does
70
+ * said out loud so the remediation cannot be followed into the opposite outcome.
71
+ *
72
+ * @param label the seam the operator configured, so the refusal names it.
73
+ */
74
+ export declare function resolveFileHistoryRetention(label: string, policy: FileHistoryRetentionPolicy | undefined): number | null;
75
+ /**
76
+ * Which boundaries survive the pass that `justCommitted`'s commit triggered — the SELECTION half,
77
+ * shared so the two bundled backends (and an out-of-tree twin) cannot drift on which boundary dies.
78
+ *
79
+ * Returns `null` when the scope is still within its bound, which is the answer the caller wants
80
+ * cheaply: no `reap` call at all, so a session under the cap pays one directory listing per turn and
81
+ * nothing else. Otherwise returns the keep set for
82
+ * {@link import("./file-history-store.js").FileHistoryStore.reap} — the newest `keep` ids, with
83
+ * `justCommitted` PINNED into it unconditionally.
84
+ *
85
+ * The pin is not belt-and-braces. `orderedOldestFirst` is whatever commit order its backend can
86
+ * PROVE, and the two bundled ones prove different amounts: the in-memory reference reads an
87
+ * insertion-ordered map (exact), while the durable one reads each boundary's own publication stamp,
88
+ * falling back to file mtime for records written before that field or rebuilt from an import, and
89
+ * to the entry id for ties. A clock stepped backwards, or a host minting non-monotonic ids of its
90
+ * own, can still invert two neighbours there.
91
+ * An ordering that is off by one place costs a different-but-equally-legal retention choice; an
92
+ * ordering that put the JUST-COMMITTED boundary at the bottom would cost this turn its rewind seat.
93
+ * Pinning makes the second outcome unreachable by construction rather than by trusting the clock.
94
+ *
95
+ * One more place the order is only as good as its source, said plainly rather than assumed away: an
96
+ * envelope round trip (`exportHistory` → `importHistory`) writes boundaries in CANONICAL — i.e.
97
+ * lexicographic — entry-id order, so a scope rebuilt from one has entry-id order where it used to
98
+ * have commit order. For engine-minted uuidv7 ids those are the same order; for a foreign graph's
99
+ * ids they need not be, and such a scope's oldest-first reading is by id. The consequence is again
100
+ * only WHICH legal boundary is retained, and the just-committed pin still holds.
101
+ *
102
+ * @param orderedOldestFirst every committed boundary id in the scope, oldest first.
103
+ * @param justCommitted the boundary whose commit triggered the pass.
104
+ * @param keep the resolved cap (`>= 1`).
105
+ */
106
+ export declare function fileHistoryBoundariesToKeep(orderedOldestFirst: readonly string[], justCommitted: string, keep: number): string[] | null;
@@ -0,0 +1,36 @@
1
+ import { refuseRetentionPolicy } from "./retention-policy.js";
2
+ export const DEFAULT_FILE_HISTORY_BOUNDARY_KEEP = 100;
3
+ const KNOWN_KNOBS = new Set(["keep"]);
4
+ export function resolveFileHistoryRetention(label, policy) {
5
+ if (policy === undefined)
6
+ return DEFAULT_FILE_HISTORY_BOUNDARY_KEEP;
7
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
8
+ refuseRetentionPolicy(label, "retention", policy, `an object of retention knobs (${[...KNOWN_KNOBS].join(", ")}) or omitted`);
9
+ }
10
+ const unknown = Object.keys(policy).filter((k) => !KNOWN_KNOBS.has(k));
11
+ if (unknown.length > 0) {
12
+ const first = unknown[0];
13
+ refuseRetentionPolicy(label, `retention.${first}`, policy[first], `omitted — ${JSON.stringify(first)} is not a retention knob (the only knob is "keep"), and a misspelled one would leave the engine default in force while the operator believes their own number is`);
14
+ }
15
+ const keep = policy.keep;
16
+ if (keep === undefined)
17
+ return DEFAULT_FILE_HISTORY_BOUNDARY_KEEP;
18
+ if (keep === "unbounded")
19
+ return null;
20
+ if (keep === 0) {
21
+ refuseRetentionPolicy(label, "retention.keep", keep, `at least 1 boundary — a cap of 0 would reap the boundary its own commit just minted, leaving a rewind seat that answers ok to every snapshot and false to every canRestore; write "unbounded" to mean "do not trim" (omitting the knob does NOT mean that — it selects the engine default of ${DEFAULT_FILE_HISTORY_BOUNDARY_KEEP})`);
22
+ }
23
+ if (typeof keep !== "number" || !Number.isInteger(keep) || keep < 1) {
24
+ refuseRetentionPolicy(label, "retention.keep", keep, `a whole number of boundaries, 1 or more, or the literal "unbounded"`);
25
+ }
26
+ return keep;
27
+ }
28
+ export function fileHistoryBoundariesToKeep(orderedOldestFirst, justCommitted, keep) {
29
+ if (orderedOldestFirst.length <= keep)
30
+ return null;
31
+ const survivors = new Set([justCommitted]);
32
+ for (let i = orderedOldestFirst.length - 1; i >= 0 && survivors.size < keep; i--) {
33
+ survivors.add(orderedOldestFirst[i]);
34
+ }
35
+ return [...survivors];
36
+ }