@gajae-code/coding-agent 0.15.5 → 0.16.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 (272) hide show
  1. package/CHANGELOG.md +85 -1
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  5. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  6. package/dist/types/cli/root-flags.d.ts +14 -0
  7. package/dist/types/cli/update-cli.d.ts +15 -0
  8. package/dist/types/cli.d.ts +7 -0
  9. package/dist/types/commands/auth-gateway.d.ts +3 -0
  10. package/dist/types/commands/gc.d.ts +13 -0
  11. package/dist/types/commands/launch.d.ts +10 -0
  12. package/dist/types/commands/model-presets.d.ts +21 -0
  13. package/dist/types/config/autorouting-tier-map.d.ts +120 -0
  14. package/dist/types/config/file-lock.d.ts +54 -2
  15. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  16. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  17. package/dist/types/config/keybindings.d.ts +3 -2
  18. package/dist/types/config/model-preset-registry.d.ts +368 -0
  19. package/dist/types/config/model-profile-activation.d.ts +39 -0
  20. package/dist/types/config/model-profiles.d.ts +5 -3
  21. package/dist/types/config/model-registry.d.ts +17 -5
  22. package/dist/types/config/settings-schema.d.ts +19 -1
  23. package/dist/types/config/settings.d.ts +2 -0
  24. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  25. package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
  26. package/dist/types/crash/record-loader.d.ts +15 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +2 -5
  28. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  29. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  31. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  33. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
  34. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  36. package/dist/types/index.d.ts +1 -0
  37. package/dist/types/main.d.ts +3 -1
  38. package/dist/types/master-mode/context.d.ts +21 -0
  39. package/dist/types/master-mode/first-request.d.ts +34 -0
  40. package/dist/types/master-mode/snapshot.d.ts +32 -0
  41. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  43. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  44. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  45. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  46. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  47. package/dist/types/modes/jobs-observer.d.ts +36 -0
  48. package/dist/types/modes/print-mode.d.ts +17 -0
  49. package/dist/types/modes/ui-language.d.ts +7 -1
  50. package/dist/types/sdk/broker/broker.d.ts +80 -1
  51. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  52. package/dist/types/sdk/broker/index.d.ts +3 -0
  53. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  54. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  56. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  57. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  58. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  59. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  60. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  61. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  62. package/dist/types/sdk/bus/index.d.ts +6 -0
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  65. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  66. package/dist/types/sdk/cli/index.d.ts +1 -0
  67. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  68. package/dist/types/sdk/cli/rows.d.ts +4 -3
  69. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  70. package/dist/types/sdk/client/client.d.ts +3 -2
  71. package/dist/types/sdk/client/discovery.d.ts +1 -0
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/host.d.ts +10 -0
  77. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  78. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  79. package/dist/types/sdk/host/session-runtime.d.ts +67 -1
  80. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  81. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  82. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  83. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  84. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  85. package/dist/types/sdk/router/session-router.d.ts +11 -0
  86. package/dist/types/sdk/session.d.ts +16 -0
  87. package/dist/types/sdk/startup-capability.d.ts +2 -1
  88. package/dist/types/session/account-inventory.d.ts +1 -0
  89. package/dist/types/session/agent-session.d.ts +24 -7
  90. package/dist/types/session/credential-store-errors.d.ts +1 -0
  91. package/dist/types/session/fold-coordinator.d.ts +201 -0
  92. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  93. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  94. package/dist/types/session/session-manager.d.ts +65 -0
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/telemetry/control.d.ts +11 -0
  98. package/dist/types/telemetry/events.d.ts +20 -0
  99. package/dist/types/telemetry/index.d.ts +3 -0
  100. package/dist/types/telemetry/transport.d.ts +22 -0
  101. package/dist/types/tools/bash-interactive.d.ts +23 -0
  102. package/dist/types/tools/bash.d.ts +4 -0
  103. package/dist/types/tools/gh.d.ts +8 -0
  104. package/dist/types/tools/index.d.ts +10 -5
  105. package/dist/types/web/search/providers/base.d.ts +9 -4
  106. package/dist/types/web/search/providers/xai.d.ts +2 -1
  107. package/dist/types/web/search/types.d.ts +14 -0
  108. package/package.json +10 -7
  109. package/scripts/generate-hotkeys-docs.ts +4 -0
  110. package/scripts/generate-sdk-operation-inventory.ts +9 -3
  111. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  112. package/src/async/job-manager.ts +671 -39
  113. package/src/cli/accounts-cli.ts +9 -5
  114. package/src/cli/args.ts +17 -0
  115. package/src/cli/auth-broker-cli.ts +40 -12
  116. package/src/cli/auth-gateway-cli.ts +273 -97
  117. package/src/cli/config-cli.ts +2 -2
  118. package/src/cli/fast-help.ts +4 -0
  119. package/src/cli/model-presets-cli.ts +100 -0
  120. package/src/cli/root-flags.ts +5 -0
  121. package/src/cli/update-cli.ts +117 -16
  122. package/src/cli.ts +6 -2
  123. package/src/commands/auth-gateway.ts +12 -8
  124. package/src/commands/gc.ts +19 -0
  125. package/src/commands/launch.ts +86 -18
  126. package/src/commands/model-presets.ts +24 -0
  127. package/src/commands/sdk.ts +113 -21
  128. package/src/config/autorouting-tier-map.ts +44 -0
  129. package/src/config/file-lock-gc.ts +9 -4
  130. package/src/config/file-lock.ts +890 -99
  131. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  132. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  133. package/src/config/keybindings.ts +6 -2
  134. package/src/config/model-preset-registry.ts +2795 -0
  135. package/src/config/model-profile-activation.ts +115 -28
  136. package/src/config/model-profile-contract.ts +4 -1
  137. package/src/config/model-profiles.ts +56 -14
  138. package/src/config/model-registry.ts +1044 -122
  139. package/src/config/model-resolver.ts +5 -0
  140. package/src/config/settings-schema.ts +19 -1
  141. package/src/config/settings.ts +11 -1
  142. package/src/coordinator-mcp/model-preset.ts +32 -17
  143. package/src/coordinator-mcp/projection-scan.ts +592 -0
  144. package/src/coordinator-mcp/question-state.ts +9 -11
  145. package/src/coordinator-mcp/server.ts +111 -42
  146. package/src/coordinator-mcp/session-reaper.ts +9 -3
  147. package/src/crash/index-store.ts +2 -0
  148. package/src/crash/record-loader.ts +10 -0
  149. package/src/extensibility/extensions/loader.ts +0 -1
  150. package/src/extensibility/extensions/types.ts +2 -3
  151. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  152. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  153. package/src/gjc-runtime/gc-render.ts +29 -0
  154. package/src/gjc-runtime/gc-runtime.ts +174 -11
  155. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  156. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  157. package/src/gjc-runtime/session-state-lock.ts +1321 -147
  158. package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
  159. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  160. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  161. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  162. package/src/hooks/skill-state.ts +6 -0
  163. package/src/index.ts +1 -0
  164. package/src/internal-urls/docs-index.generated.ts +14 -13
  165. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  166. package/src/lsp/config.ts +4 -3
  167. package/src/lsp/path-trust.ts +12 -6
  168. package/src/main.ts +66 -4
  169. package/src/master-mode/context.ts +50 -0
  170. package/src/master-mode/first-request.ts +65 -0
  171. package/src/master-mode/snapshot.ts +205 -0
  172. package/src/modes/acp/acp-agent.ts +616 -161
  173. package/src/modes/components/footer.ts +4 -2
  174. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  175. package/src/modes/components/jobs-overlay-model.ts +99 -16
  176. package/src/modes/components/model-selector.ts +158 -18
  177. package/src/modes/components/oauth-selector.ts +6 -1
  178. package/src/modes/components/settings-selector.ts +1 -0
  179. package/src/modes/components/status-line/model-name.ts +53 -0
  180. package/src/modes/components/status-line/priority-row.ts +107 -0
  181. package/src/modes/components/status-line/segments.ts +29 -10
  182. package/src/modes/components/tool-status-header.ts +151 -15
  183. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  184. package/src/modes/controllers/input-controller.ts +51 -6
  185. package/src/modes/controllers/selector-controller.ts +191 -96
  186. package/src/modes/interactive-mode.ts +14 -1
  187. package/src/modes/jobs-observer.ts +149 -16
  188. package/src/modes/print-mode.ts +64 -0
  189. package/src/modes/runtime-init.ts +1 -1
  190. package/src/modes/ui-language.ts +49 -2
  191. package/src/modes/utils/injected-user-submission.ts +15 -1
  192. package/src/prompts/system/master-mode.md +28 -0
  193. package/src/prompts/tools/bash.md +2 -2
  194. package/src/prompts/tools/fold-receipt.md +3 -0
  195. package/src/prompts/tools/job.md +1 -1
  196. package/src/sdk/acp/adapter.ts +17 -2
  197. package/src/sdk/broker/broker.ts +1671 -34
  198. package/src/sdk/broker/endpoint-authority.ts +86 -0
  199. package/src/sdk/broker/ensure.ts +5 -0
  200. package/src/sdk/broker/index.ts +3 -0
  201. package/src/sdk/broker/lifecycle.ts +217 -46
  202. package/src/sdk/broker/master-capability.ts +232 -0
  203. package/src/sdk/broker/session-index.ts +121 -17
  204. package/src/sdk/broker/session-scope.ts +272 -0
  205. package/src/sdk/broker/spawn-authority.ts +1031 -0
  206. package/src/sdk/broker/spawn-substrate.ts +548 -0
  207. package/src/sdk/broker/state-version.ts +26 -14
  208. package/src/sdk/broker/transport.ts +1 -0
  209. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  210. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  211. package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
  212. package/src/sdk/bus/index.ts +51 -4
  213. package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
  214. package/src/sdk/bus/prompt-reconciliation.ts +28 -4
  215. package/src/sdk/bus/slack-conversation.ts +5 -1
  216. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  217. package/src/sdk/cli/index.ts +1 -0
  218. package/src/sdk/cli/master-cli.ts +259 -0
  219. package/src/sdk/cli/rows.ts +11 -8
  220. package/src/sdk/cli/session-cli.ts +276 -6
  221. package/src/sdk/client/client.ts +14 -8
  222. package/src/sdk/client/discovery.ts +3 -1
  223. package/src/sdk/host/control/dispatch.ts +101 -37
  224. package/src/sdk/host/control/index.ts +2 -0
  225. package/src/sdk/host/control/operations.ts +2 -0
  226. package/src/sdk/host/control/runtime-gate.ts +203 -11
  227. package/src/sdk/host/host.ts +27 -2
  228. package/src/sdk/host/model-pin.ts +185 -25
  229. package/src/sdk/host/sdk-run-capability.ts +1 -0
  230. package/src/sdk/host/session-runtime.test.ts +581 -43
  231. package/src/sdk/host/session-runtime.ts +655 -101
  232. package/src/sdk/lifecycle/broker-client.ts +28 -0
  233. package/src/sdk/lifecycle/service.ts +279 -20
  234. package/src/sdk/lifecycle-session.ts +10 -2
  235. package/src/sdk/prompt-deadline-manager.ts +16 -1
  236. package/src/sdk/protocol/adapter-validation.ts +27 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +53 -2
  238. package/src/sdk/protocol/operation-registry.ts +17 -4
  239. package/src/sdk/router/session-router.ts +294 -154
  240. package/src/sdk/session.ts +440 -167
  241. package/src/sdk/startup-capability.ts +1 -0
  242. package/src/session/account-inventory.ts +27 -11
  243. package/src/session/agent-session.ts +1292 -180
  244. package/src/session/blob-store.ts +15 -5
  245. package/src/session/credential-store-errors.ts +2 -0
  246. package/src/session/fold-coordinator.ts +433 -0
  247. package/src/session/internal/managed-session-storage.ts +153 -78
  248. package/src/session/sdk-run-capability-internal.ts +15 -0
  249. package/src/session/sdk-run-capability.ts +2 -0
  250. package/src/session/session-manager.ts +342 -28
  251. package/src/session/yield-queue.ts +24 -10
  252. package/src/setup/frictionless-onboarding.ts +2 -0
  253. package/src/setup/provider-presets.json +1 -1
  254. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  255. package/src/task/executor.ts +19 -3
  256. package/src/telemetry/control.ts +26 -0
  257. package/src/telemetry/events.ts +1268 -0
  258. package/src/telemetry/index.ts +3 -0
  259. package/src/telemetry/transport.ts +87 -0
  260. package/src/tools/bash-interactive.ts +182 -85
  261. package/src/tools/bash.ts +1028 -109
  262. package/src/tools/gh.ts +104 -48
  263. package/src/tools/github-cache.ts +2 -2
  264. package/src/tools/index.ts +11 -5
  265. package/src/tools/tool-catalog.generated.ts +1 -1
  266. package/src/web/search/provider.ts +120 -11
  267. package/src/web/search/providers/anthropic.ts +89 -17
  268. package/src/web/search/providers/base.ts +9 -4
  269. package/src/web/search/providers/gemini.ts +87 -15
  270. package/src/web/search/providers/openai-compatible.ts +23 -8
  271. package/src/web/search/providers/xai.ts +98 -12
  272. package/src/web/search/types.ts +16 -0
@@ -0,0 +1,592 @@
1
+ /**
2
+ * Coordinator directory scans that must not treat native exact-unlink debris
3
+ * as live JSON, and must not make start/status unreadable on a large dirent pile.
4
+ */
5
+ import * as fs from "node:fs/promises";
6
+ import * as path from "node:path";
7
+
8
+ class ProjectionScanRaceError extends Error {
9
+ readonly code = "PROJECTION_SCAN_RACED";
10
+
11
+ constructor(message: string) {
12
+ super(message);
13
+ this.name = "ProjectionScanRaceError";
14
+ }
15
+ }
16
+
17
+ const PROJECTION_SCAN_UNSUPPORTED_CODE = "coordinator_projection_safe_read_unsupported";
18
+
19
+ class ProjectionScanUnsupportedError extends Error {
20
+ readonly code = PROJECTION_SCAN_UNSUPPORTED_CODE;
21
+
22
+ constructor() {
23
+ super(PROJECTION_SCAN_UNSUPPORTED_CODE);
24
+ this.name = "ProjectionScanUnsupportedError";
25
+ }
26
+ }
27
+
28
+ /** Test-only platform seam; production always follows the host platform. */
29
+ export const ProjectionScanTestHooks: {
30
+ platform?: NodeJS.Platform;
31
+ } = {};
32
+
33
+ function projectionPlatform(): NodeJS.Platform {
34
+ return ProjectionScanTestHooks.platform ?? process.platform;
35
+ }
36
+
37
+ /** Post-filter parse-candidate cap. Exhaustion returns an explicit incomplete result. */
38
+ export const COORDINATOR_JSON_SCAN_CAP = 10_000;
39
+ /** Number of fresh authoritative attempts allowed for a scan that observed churn. */
40
+ export const COORDINATOR_JSON_SCAN_RETRY_ATTEMPTS = 3;
41
+
42
+ export interface ProjectionScanStat {
43
+ size: number | bigint;
44
+ dev?: number | bigint;
45
+ ino?: number | bigint;
46
+ nlink?: number | bigint;
47
+ mtimeNs?: number | bigint;
48
+ ctimeNs?: number | bigint;
49
+ isDirectory?(): boolean;
50
+ isFile(): boolean;
51
+ isSymbolicLink(): boolean;
52
+ }
53
+
54
+ export interface ProjectionScanFs {
55
+ readdir(dir: string): Promise<string[]>;
56
+ lstat(file: string): Promise<ProjectionScanStat>;
57
+ readFile(file: string, encoding: "utf8", expected?: ProjectionScanStat): Promise<string>;
58
+ /** Optional descriptor-bound reader used by the production filesystem. */
59
+ readFileSafe?: (file: string, encoding: "utf8", expected?: ProjectionScanStat) => Promise<string>;
60
+ /** Optional pinned directory authority used for enumeration and every child operation. */
61
+ openDirectory?: (dir: string) => Promise<ProjectionScanDirectory>;
62
+ }
63
+
64
+ export interface ProjectionScanDirectory {
65
+ readonly stat: ProjectionScanStat;
66
+ readdir(): Promise<string[]>;
67
+ lstat(entry: string): Promise<ProjectionScanStat>;
68
+ readFile(entry: string, encoding: "utf8", expected?: ProjectionScanStat): Promise<string>;
69
+ close(): Promise<void>;
70
+ }
71
+
72
+ export interface ProjectionScanResult {
73
+ values: unknown[];
74
+ parsed: number;
75
+ capped: boolean;
76
+ skippedDebris: number;
77
+ skippedEmpty: number;
78
+ /** Candidates that changed or disappeared after enumeration. */
79
+ raced: number;
80
+ /** True when the scan cannot be authoritative for its caller. */
81
+ incomplete: boolean;
82
+ }
83
+
84
+ /**
85
+ * Read one discovered candidate without ever following a final-component symlink or
86
+ * blocking on a FIFO. POSIX opens are descriptor-bound by O_NOFOLLOW/O_NONBLOCK. Windows
87
+ * has neither flag, so the path is bracketed by lstat/fstat/lstat identity checks and the
88
+ * bytes still come from the opened handle rather than the mutable pathname.
89
+ */
90
+ function sameProjectionFile(left: ProjectionScanStat, right: ProjectionScanStat): boolean {
91
+ if (!left.isFile() || !right.isFile()) return false;
92
+ if (
93
+ left.dev === undefined ||
94
+ right.dev === undefined ||
95
+ left.ino === undefined ||
96
+ right.ino === undefined ||
97
+ String(left.dev) !== String(right.dev) ||
98
+ String(left.ino) !== String(right.ino) ||
99
+ String(left.size) !== String(right.size)
100
+ )
101
+ return false;
102
+ if (left.nlink !== undefined && right.nlink !== undefined && String(left.nlink) !== String(right.nlink))
103
+ return false;
104
+ if (left.mtimeNs !== undefined && right.mtimeNs !== undefined && String(left.mtimeNs) !== String(right.mtimeNs))
105
+ return false;
106
+ if (left.ctimeNs !== undefined && right.ctimeNs !== undefined && String(left.ctimeNs) !== String(right.ctimeNs))
107
+ return false;
108
+ return true;
109
+ }
110
+
111
+ async function readProjectionFileSafe(file: string, encoding: "utf8", expected?: ProjectionScanStat): Promise<string> {
112
+ if (encoding !== "utf8") throw new TypeError("Coordinator projection reads require utf8.");
113
+ const platform = projectionPlatform();
114
+ const noFollow = fs.constants.O_NOFOLLOW;
115
+ const nonBlock = fs.constants.O_NONBLOCK;
116
+ if (platform !== "win32" && (typeof noFollow !== "number" || typeof nonBlock !== "number"))
117
+ throw new ProjectionScanUnsupportedError();
118
+ const flags = fs.constants.O_RDONLY | (platform === "win32" ? 0 : (nonBlock as number) | (noFollow as number));
119
+ let before: import("node:fs").BigIntStats | undefined;
120
+ if (platform === "win32") {
121
+ before = await fs.lstat(file, { bigint: true });
122
+ if (before.isSymbolicLink() || !before.isFile())
123
+ throw new ProjectionScanRaceError("candidate is not a regular file");
124
+ }
125
+ let handle: fs.FileHandle | undefined;
126
+ try {
127
+ handle = await fs.open(file, flags);
128
+ const opened = await handle.stat({ bigint: true });
129
+ if (!opened.isFile()) throw new ProjectionScanRaceError("candidate changed to a non-regular file");
130
+ if (expected && !sameProjectionFile(expected, opened))
131
+ throw new ProjectionScanRaceError("candidate changed while opening");
132
+ if (before) {
133
+ if (
134
+ before.dev !== opened.dev ||
135
+ before.ino !== opened.ino ||
136
+ before.nlink !== opened.nlink ||
137
+ before.size !== opened.size ||
138
+ before.mtimeNs !== opened.mtimeNs ||
139
+ before.ctimeNs !== opened.ctimeNs
140
+ )
141
+ throw new ProjectionScanRaceError("candidate changed while opening");
142
+ const relinked = await fs.lstat(file, { bigint: true });
143
+ if (
144
+ relinked.isSymbolicLink() ||
145
+ !relinked.isFile() ||
146
+ relinked.dev !== opened.dev ||
147
+ relinked.ino !== opened.ino ||
148
+ relinked.nlink !== opened.nlink ||
149
+ relinked.size !== opened.size ||
150
+ relinked.mtimeNs !== opened.mtimeNs ||
151
+ relinked.ctimeNs !== opened.ctimeNs
152
+ )
153
+ throw new ProjectionScanRaceError("candidate changed before read");
154
+ }
155
+ return await handle.readFile({ encoding: "utf8" });
156
+ } catch (error) {
157
+ if ((error as NodeJS.ErrnoException).code === "ELOOP")
158
+ throw new ProjectionScanRaceError("candidate became a symlink");
159
+ throw error;
160
+ } finally {
161
+ await handle?.close().catch(() => undefined);
162
+ }
163
+ }
164
+
165
+ function sameProjectionRoot(left: ProjectionScanStat, right: ProjectionScanStat): boolean {
166
+ const leftDirectory = left.isDirectory?.();
167
+ const rightDirectory = right.isDirectory?.();
168
+ return (
169
+ leftDirectory !== false &&
170
+ rightDirectory !== false &&
171
+ left.dev !== undefined &&
172
+ right.dev !== undefined &&
173
+ left.ino !== undefined &&
174
+ right.ino !== undefined &&
175
+ String(left.dev) === String(right.dev) &&
176
+ String(left.ino) === String(right.ino)
177
+ );
178
+ }
179
+
180
+ function hasProjectionRootIdentity(stat: ProjectionScanStat): boolean {
181
+ return stat.dev !== undefined && stat.ino !== undefined;
182
+ }
183
+
184
+ function trimProjectionRootPath(dir: string): string {
185
+ const parsedRoot = path.parse(dir).root;
186
+ let trimmed = dir;
187
+ while (trimmed.length > parsedRoot.length && (trimmed.endsWith("/") || trimmed.endsWith("\\")))
188
+ trimmed = trimmed.slice(0, -1);
189
+ while (trimmed.length > parsedRoot.length && (trimmed.endsWith("/.") || trimmed.endsWith("\\."))) {
190
+ trimmed = trimmed.slice(0, -2);
191
+ while (trimmed.length > parsedRoot.length && (trimmed.endsWith("/") || trimmed.endsWith("\\")))
192
+ trimmed = trimmed.slice(0, -1);
193
+ }
194
+ return trimmed || parsedRoot;
195
+ }
196
+
197
+ /**
198
+ * Windows has no descriptor-relative `openat` binding in Node. Keep the root pathname
199
+ * authoritative by checking its identity before and after every mutable-path operation.
200
+ */
201
+ async function openProjectionDirectoryWindowsSafe(dir: string): Promise<ProjectionScanDirectory> {
202
+ const rootPath = trimProjectionRootPath(dir);
203
+ let opened: import("node:fs").BigIntStats;
204
+ try {
205
+ opened = await fs.lstat(rootPath, { bigint: true });
206
+ } catch (error) {
207
+ if ((error as NodeJS.ErrnoException).code === "ELOOP")
208
+ throw new ProjectionScanRaceError("scan root became a reparse point");
209
+ throw error;
210
+ }
211
+ if (opened.isSymbolicLink() || !opened.isDirectory())
212
+ throw new ProjectionScanRaceError("scan root is not a regular directory");
213
+
214
+ const assertRoot = async (): Promise<void> => {
215
+ let settled: import("node:fs").BigIntStats;
216
+ try {
217
+ settled = await fs.lstat(rootPath, { bigint: true });
218
+ } catch (error) {
219
+ const code = (error as NodeJS.ErrnoException).code;
220
+ if (code === "ENOENT" || code === "ELOOP")
221
+ throw new ProjectionScanRaceError("scan root changed during enumeration");
222
+ throw error;
223
+ }
224
+ if (settled.isSymbolicLink() || !settled.isDirectory() || !sameProjectionRoot(opened, settled))
225
+ throw new ProjectionScanRaceError("scan root changed during enumeration");
226
+ };
227
+
228
+ const entryPath = (entry: string): string => {
229
+ // Directory entries come from readdir and are therefore single components on
230
+ // Windows. Keep the invariant explicit so an injected or corrupted entry can
231
+ // never turn a child operation into mutable pathname traversal.
232
+ if (
233
+ entry.length === 0 ||
234
+ entry === "." ||
235
+ entry === ".." ||
236
+ entry.includes("/") ||
237
+ entry.includes("\\") ||
238
+ entry.includes(":")
239
+ )
240
+ throw new ProjectionScanRaceError("scan entry is not a single directory component");
241
+ return path.join(rootPath, entry);
242
+ };
243
+
244
+ const bracketRoot = async <T>(operation: () => Promise<T>): Promise<T> => {
245
+ let value!: T;
246
+ let operationError: unknown;
247
+ let started = false;
248
+ try {
249
+ await assertRoot();
250
+ started = true;
251
+ value = await operation();
252
+ } catch (error) {
253
+ operationError = error;
254
+ }
255
+ if (started) {
256
+ try {
257
+ await assertRoot();
258
+ } catch (error) {
259
+ operationError = error;
260
+ }
261
+ }
262
+ if (operationError !== undefined) {
263
+ const code = (operationError as NodeJS.ErrnoException).code;
264
+ if (code === "ENOENT" || code === "ELOOP")
265
+ throw new ProjectionScanRaceError("scan operation raced with a replacement or reparse point");
266
+ throw operationError;
267
+ }
268
+ return value;
269
+ };
270
+
271
+ return {
272
+ stat: opened,
273
+ readdir: () => bracketRoot(() => fs.readdir(rootPath)),
274
+ lstat: entry =>
275
+ bracketRoot(async () => {
276
+ const stat = await fs.lstat(entryPath(entry), { bigint: true });
277
+ if (stat.isSymbolicLink()) throw new ProjectionScanRaceError("candidate became a reparse point");
278
+ return stat;
279
+ }),
280
+ readFile: (entry, encoding, expected) =>
281
+ bracketRoot(() => readProjectionFileSafe(entryPath(entry), encoding, expected)),
282
+ close: async () => {},
283
+ };
284
+ }
285
+
286
+ /**
287
+ * Open a no-follow directory authority and keep it alive through enumeration, child
288
+ * lstat, and child reads. Node has no `openat` binding, so POSIX uses the proc fd path
289
+ * as the descriptor-relative namespace; a replacement root or parent cannot redirect it.
290
+ */
291
+ async function openProjectionDirectorySafe(dir: string): Promise<ProjectionScanDirectory> {
292
+ const platform = projectionPlatform();
293
+ if (platform === "win32") return openProjectionDirectoryWindowsSafe(dir);
294
+ if (platform !== "linux") throw new ProjectionScanUnsupportedError();
295
+ const noFollow = fs.constants.O_NOFOLLOW;
296
+ const nonBlock = fs.constants.O_NONBLOCK;
297
+ if (typeof noFollow !== "number" || typeof nonBlock !== "number") throw new ProjectionScanUnsupportedError();
298
+ const directoryFlag = typeof fs.constants.O_DIRECTORY === "number" ? fs.constants.O_DIRECTORY : 0;
299
+ const flags = fs.constants.O_RDONLY | (nonBlock as number) | (noFollow as number) | directoryFlag;
300
+ let handle: fs.FileHandle | undefined;
301
+ try {
302
+ handle = await fs.open(dir, flags);
303
+ const opened = await handle.stat({ bigint: true });
304
+ if (!opened.isDirectory()) throw new ProjectionScanRaceError("scan root is not a directory");
305
+ const pinnedPath = `/proc/self/fd/${handle.fd}`;
306
+ const ownedHandle = handle;
307
+ handle = undefined;
308
+ const assertRoot = async (): Promise<void> => {
309
+ const settled = await ownedHandle.stat({ bigint: true });
310
+ if (!sameProjectionRoot(opened, settled)) throw new ProjectionScanRaceError("scan root descriptor changed");
311
+ };
312
+ return {
313
+ stat: opened,
314
+ readdir: async () => {
315
+ const entries = await fs.readdir(pinnedPath);
316
+ await assertRoot();
317
+ return entries;
318
+ },
319
+ lstat: async entry => {
320
+ const stat = await fs.lstat(path.join(pinnedPath, entry), { bigint: true });
321
+ await assertRoot();
322
+ return stat;
323
+ },
324
+ readFile: async (entry, encoding, expected) => {
325
+ const source = await readProjectionFileSafe(path.join(pinnedPath, entry), encoding, expected);
326
+ await assertRoot();
327
+ return source;
328
+ },
329
+ close: async () => {
330
+ await ownedHandle.close().catch(() => undefined);
331
+ },
332
+ };
333
+ } catch (error) {
334
+ if ((error as NodeJS.ErrnoException).code === "ELOOP")
335
+ throw new ProjectionScanRaceError("scan root became a symlink");
336
+ throw error;
337
+ } finally {
338
+ await handle?.close().catch(() => undefined);
339
+ }
340
+ }
341
+
342
+ const failUnpinnedProjectionOperation = async (): Promise<never> => {
343
+ throw new ProjectionScanUnsupportedError();
344
+ };
345
+
346
+ const defaultFs: ProjectionScanFs = {
347
+ // The default implementation never permits an accidental mutable-path fallback;
348
+ // all production operations must go through openProjectionDirectorySafe above.
349
+ readdir: failUnpinnedProjectionOperation,
350
+ lstat: failUnpinnedProjectionOperation,
351
+ readFile: failUnpinnedProjectionOperation,
352
+ readFileSafe: failUnpinnedProjectionOperation,
353
+ openDirectory: openProjectionDirectorySafe,
354
+ };
355
+
356
+ export function isCoordinatorScanDebrisName(name: string): boolean {
357
+ return name.startsWith(".");
358
+ }
359
+
360
+ async function scanCoordinatorJsonFiles(
361
+ dir: string,
362
+ io: ProjectionScanFs = defaultFs,
363
+ cap: number = COORDINATOR_JSON_SCAN_CAP,
364
+ authority?: ProjectionScanDirectory,
365
+ ): Promise<ProjectionScanResult> {
366
+ let rootStat: ProjectionScanStat;
367
+ try {
368
+ rootStat = authority?.stat ?? (await io.lstat(dir));
369
+ } catch (error) {
370
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
371
+ return {
372
+ values: [],
373
+ parsed: 0,
374
+ capped: false,
375
+ skippedDebris: 0,
376
+ skippedEmpty: 0,
377
+ raced: 0,
378
+ incomplete: false,
379
+ };
380
+ }
381
+ throw error;
382
+ }
383
+ if (rootStat.isSymbolicLink() || rootStat.isDirectory?.() === false) {
384
+ return {
385
+ values: [],
386
+ parsed: 0,
387
+ capped: true,
388
+ skippedDebris: 0,
389
+ skippedEmpty: 0,
390
+ raced: 1,
391
+ incomplete: true,
392
+ };
393
+ }
394
+ let entries: string[];
395
+ try {
396
+ entries = authority ? await authority.readdir() : await io.readdir(dir);
397
+ } catch (error) {
398
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
399
+ return {
400
+ values: [],
401
+ parsed: 0,
402
+ capped: true,
403
+ skippedDebris: 0,
404
+ skippedEmpty: 0,
405
+ raced: 1,
406
+ incomplete: true,
407
+ };
408
+ }
409
+ if (error instanceof ProjectionScanRaceError) {
410
+ return {
411
+ values: [],
412
+ parsed: 0,
413
+ capped: true,
414
+ skippedDebris: 0,
415
+ skippedEmpty: 0,
416
+ raced: 1,
417
+ incomplete: true,
418
+ };
419
+ }
420
+ throw error;
421
+ }
422
+
423
+ let skippedDebris = 0;
424
+ let skippedEmpty = 0;
425
+ let raced = 0;
426
+ const parseCandidates: Array<{ entry: string; stat: ProjectionScanStat }> = [];
427
+ for (const entry of entries) {
428
+ if (!entry.endsWith(".json") || isCoordinatorScanDebrisName(entry)) {
429
+ if (entry.endsWith(".json") && isCoordinatorScanDebrisName(entry)) skippedDebris += 1;
430
+ continue;
431
+ }
432
+ const file = path.join(dir, entry);
433
+ let stat: ProjectionScanStat;
434
+ try {
435
+ stat = authority ? await authority.lstat(entry) : await io.lstat(file);
436
+ } catch (error) {
437
+ if (
438
+ (error as NodeJS.ErrnoException).code === "ENOENT" ||
439
+ (error as NodeJS.ErrnoException).code === "ELOOP" ||
440
+ error instanceof ProjectionScanRaceError
441
+ ) {
442
+ raced += 1;
443
+ continue;
444
+ }
445
+ throw error;
446
+ }
447
+ if (stat.isSymbolicLink() || !stat.isFile()) {
448
+ // The candidate was a parseable-looking directory entry when enumeration
449
+ // completed, but its type changed before inspection. Treat that as a
450
+ // raced candidate rather than ordinary empty/non-regular debris: callers
451
+ // must not consume a partial projection set.
452
+ raced += 1;
453
+ continue;
454
+ }
455
+ parseCandidates.push({ entry, stat });
456
+ }
457
+
458
+ const capped = parseCandidates.length > cap;
459
+ const toParse = capped ? parseCandidates.slice(0, cap) : parseCandidates;
460
+ const values: unknown[] = [];
461
+ for (const candidate of toParse) {
462
+ const { entry, stat } = candidate;
463
+ const file = path.join(dir, entry);
464
+ let source: string;
465
+ try {
466
+ source = authority
467
+ ? await authority.readFile(entry, "utf8", stat)
468
+ : await (io.readFileSafe ?? io.readFile)(file, "utf8", stat);
469
+ } catch (error) {
470
+ if (
471
+ (error as NodeJS.ErrnoException).code === "ENOENT" ||
472
+ (error as NodeJS.ErrnoException).code === "ELOOP" ||
473
+ error instanceof ProjectionScanRaceError
474
+ ) {
475
+ raced += 1;
476
+ skippedEmpty += 1;
477
+ continue;
478
+ }
479
+ throw error;
480
+ }
481
+ try {
482
+ values.push(JSON.parse(source));
483
+ } catch (error) {
484
+ throw new Error(
485
+ `invalid coordinator projection ${file}: ${error instanceof Error ? error.message : String(error)}`,
486
+ );
487
+ }
488
+ }
489
+ let finalRoot: ProjectionScanStat;
490
+ try {
491
+ finalRoot = authority?.stat ?? (await io.lstat(dir));
492
+ } catch (error) {
493
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
494
+ return {
495
+ values: [],
496
+ parsed: 0,
497
+ capped: true,
498
+ skippedDebris,
499
+ skippedEmpty,
500
+ raced: raced + 1,
501
+ incomplete: true,
502
+ };
503
+ }
504
+ if (hasProjectionRootIdentity(rootStat) && !sameProjectionRoot(rootStat, finalRoot)) {
505
+ return {
506
+ values: [],
507
+ parsed: 0,
508
+ capped: true,
509
+ skippedDebris,
510
+ skippedEmpty,
511
+ raced: raced + 1,
512
+ incomplete: true,
513
+ };
514
+ }
515
+ return {
516
+ values: values.filter(value => value !== null),
517
+ parsed: values.length,
518
+ // Existing authoritative callers already refuse a capped scan. Treat a raced
519
+ // candidate as the same incomplete boundary so they cannot consume a partial
520
+ // projection set while the result still reports the precise race count below.
521
+ capped: capped || raced > 0,
522
+ skippedDebris,
523
+ skippedEmpty,
524
+ raced,
525
+ incomplete: capped || raced > 0,
526
+ };
527
+ }
528
+
529
+ export async function listCoordinatorJsonFiles(
530
+ dir: string,
531
+ io: ProjectionScanFs = defaultFs,
532
+ cap: number = COORDINATOR_JSON_SCAN_CAP,
533
+ ): Promise<ProjectionScanResult> {
534
+ let authority: ProjectionScanDirectory | undefined;
535
+ try {
536
+ authority = io.openDirectory ? await io.openDirectory(dir) : undefined;
537
+ return await scanCoordinatorJsonFiles(dir, io, cap, authority);
538
+ } catch (error) {
539
+ if (
540
+ error instanceof ProjectionScanRaceError ||
541
+ error instanceof ProjectionScanUnsupportedError ||
542
+ (error as NodeJS.ErrnoException).code === PROJECTION_SCAN_UNSUPPORTED_CODE
543
+ ) {
544
+ return {
545
+ values: [],
546
+ parsed: 0,
547
+ capped: true,
548
+ skippedDebris: 0,
549
+ skippedEmpty: 0,
550
+ raced: 1,
551
+ incomplete: true,
552
+ };
553
+ }
554
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
555
+ // No authority was returned: this is the lazy first-use shape for an
556
+ // otherwise valid projection namespace, not a partial scan. Once an
557
+ // authority exists, ENOENT is handled by scanCoordinatorJsonFiles as a
558
+ // raced/incomplete result and cannot reach this branch.
559
+ return {
560
+ values: [],
561
+ parsed: 0,
562
+ capped: false,
563
+ skippedDebris: 0,
564
+ skippedEmpty: 0,
565
+ raced: 0,
566
+ incomplete: false,
567
+ };
568
+ }
569
+ throw error;
570
+ } finally {
571
+ await authority?.close().catch(() => undefined);
572
+ }
573
+ }
574
+
575
+ /**
576
+ * Retry only scans invalidated by observed directory/file churn. A candidate cap,
577
+ * unsupported authority, parse error, or any other failure remains fail-closed.
578
+ * Each attempt acquires a new authority, so no partial result crosses attempts.
579
+ */
580
+ export async function listCoordinatorJsonFilesWithRetry(
581
+ dir: string,
582
+ io: ProjectionScanFs = defaultFs,
583
+ cap: number = COORDINATOR_JSON_SCAN_CAP,
584
+ maxAttempts: number = COORDINATOR_JSON_SCAN_RETRY_ATTEMPTS,
585
+ ): Promise<ProjectionScanResult> {
586
+ const attempts = Math.max(1, Math.floor(maxAttempts));
587
+ let scan = await listCoordinatorJsonFiles(dir, io, cap);
588
+ for (let attempt = 1; attempt < attempts && scan.incomplete && scan.raced > 0; attempt++) {
589
+ scan = await listCoordinatorJsonFiles(dir, io, cap);
590
+ }
591
+ return scan;
592
+ }
@@ -1029,7 +1029,7 @@ function assertTransaction(transaction: CoordinatorSessionTransactionV1, namespa
1029
1029
  const rejectedHistorical = request.phase === "rejected";
1030
1030
  if (
1031
1031
  rejectedHistorical
1032
- ? !request.safe_receipt || request.safe_receipt.status !== "rejected"
1032
+ ? request.safe_receipt?.status !== "rejected"
1033
1033
  : question.answer_request_id !== request.request_id ||
1034
1034
  question.claim_fence_epoch !== request.claim_fence_epoch
1035
1035
  )
@@ -1083,7 +1083,7 @@ function assertTransaction(transaction: CoordinatorSessionTransactionV1, namespa
1083
1083
  event.transaction_revision > transaction.revision ||
1084
1084
  typeof event.kind !== "string" ||
1085
1085
  event.kind.length === 0 ||
1086
- !["turn", "question", "report", "session", "deletion"].includes(event.entity) ||
1086
+ !isOutboxEntity(event.entity) ||
1087
1087
  typeof event.entity_id !== "string" ||
1088
1088
  event.entity_id.length === 0 ||
1089
1089
  !isRecord(event.payload) ||
@@ -1296,7 +1296,7 @@ export async function listCanonicalActiveSessions(
1296
1296
  paths.registryLock,
1297
1297
  async () => {
1298
1298
  const registry = await readJson<NamespaceRegistryV1>(paths.registry);
1299
- if (!registry || registry.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
1299
+ if (registry?.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
1300
1300
  throw new Error("state_corrupt");
1301
1301
  return Object.values(registry.roster ?? {})
1302
1302
  .filter(entry => entry.active || entry.dirty)
@@ -1333,7 +1333,7 @@ export async function readDeliveryDiscoveryCursor(
1333
1333
  paths.registryLock,
1334
1334
  async () => {
1335
1335
  const registry = await readJson<NamespaceRegistryV1>(paths.registry);
1336
- if (!registry || registry.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
1336
+ if (registry?.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
1337
1337
  throw new Error("state_corrupt");
1338
1338
  return registry.delivery_discovery_cursor ?? "";
1339
1339
  },
@@ -1660,7 +1660,7 @@ export async function startCreationRemote(
1660
1660
  ): Promise<CreationRequestV1> {
1661
1661
  return await withNamespaceRegistry(paths, async registry => {
1662
1662
  const request = registry.creations[keyDigest];
1663
- if (!request || !request.sidecar_verifier) throw new Error("state_corrupt");
1663
+ if (!request?.sidecar_verifier) throw new Error("state_corrupt");
1664
1664
  if (
1665
1665
  request.phase === "claimed" &&
1666
1666
  (request.sidecar_verifier.key_id !== expectedVerifier.key_id ||
@@ -1687,8 +1687,7 @@ export async function reconcileCreationRemoteVerifier(
1687
1687
  ): Promise<CreationRequestV1> {
1688
1688
  return await withNamespaceRegistry(paths, async registry => {
1689
1689
  const request = registry.creations[keyDigest];
1690
- if (!request || !request.sidecar_verifier || request.phase !== "remote_started")
1691
- throw new Error("terminal_uncertain");
1690
+ if (!request?.sidecar_verifier || request.phase !== "remote_started") throw new Error("terminal_uncertain");
1692
1691
  if (!/^[a-f0-9]{64}$/.test(usedKeyId)) throw new Error("terminal_uncertain");
1693
1692
  if (usedKeyId === candidate.key_id) request.sidecar_verifier = candidate;
1694
1693
  else if (usedKeyId !== request.sidecar_verifier.key_id) throw new Error("terminal_uncertain");
@@ -2241,7 +2240,7 @@ export async function enumeratePublicDeliveries(
2241
2240
  paths.registryLock,
2242
2241
  async () => {
2243
2242
  const registry = await readJson<NamespaceRegistryV1>(paths.registry);
2244
- if (!registry || registry.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
2243
+ if (registry?.schema_version !== 1 || registry.namespace_id !== path.basename(paths.root))
2245
2244
  throw new Error("state_corrupt");
2246
2245
  return [...new Set([...Object.keys(registry.roster ?? {}), ...Object.keys(registry.retained_sessions ?? {})])]
2247
2246
  .filter(name => COORDINATOR_SESSION_ID_PATTERN.test(name))
@@ -2526,7 +2525,7 @@ export async function replaceCreationRetirementIntent(
2526
2525
  if (request.phase !== "remote_started" && request.phase !== "uncertain" && request.phase !== "retired")
2527
2526
  throw new Error("retire_not_allowed");
2528
2527
  const staged = request.retirement_intent;
2529
- if (!staged || staged.phase !== "intent" || staged.broker_proof) throw new Error("state_corrupt");
2528
+ if (staged?.phase !== "intent" || staged.broker_proof) throw new Error("state_corrupt");
2530
2529
  if (staged.retirement_key_digest !== retirementKeyDigest) throw new Error("idempotency_conflict");
2531
2530
  assertCreationRetirementProofMatches(request, proof, false);
2532
2531
  staged.phase = "pre_effect_rejected";
@@ -2716,8 +2715,7 @@ export async function removeSessionTransaction(
2716
2715
  // exporter lease expires instead of deleting an undelivered event.
2717
2716
  if (Object.values(transaction.outbox).some(event => event.public_delivery.state !== "acknowledged"))
2718
2717
  return false;
2719
- await fs.rm(file, { force: true });
2720
- await syncCoordinatorDirectory(path.dirname(file));
2718
+ await removeCoordinatorStateFile(file);
2721
2719
  delete registry.roster?.[sessionId];
2722
2720
  delete registry.retained_sessions?.[sessionId];
2723
2721
  return true;