@animalabs/agent-framework 0.22.1 → 0.22.3

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 (946) hide show
  1. package/README.md +4 -2
  2. package/dist/src/agent.d.ts +266 -28
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +539 -104
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.d.ts +41 -0
  7. package/dist/src/api/mcp-server-core.d.ts.map +1 -0
  8. package/dist/src/api/mcp-server-core.js +593 -0
  9. package/dist/src/api/mcp-server-core.js.map +1 -0
  10. package/dist/src/api/mcp-server.d.ts +2 -36
  11. package/dist/src/api/mcp-server.d.ts.map +1 -1
  12. package/dist/src/api/mcp-server.js +15 -595
  13. package/dist/src/api/mcp-server.js.map +1 -1
  14. package/dist/src/api/server.d.ts +9 -1
  15. package/dist/src/api/server.d.ts.map +1 -1
  16. package/dist/src/api/server.js +55 -27
  17. package/dist/src/api/server.js.map +1 -1
  18. package/dist/src/api/types.d.ts +10 -2
  19. package/dist/src/api/types.d.ts.map +1 -1
  20. package/dist/src/branch-names.d.ts +14 -0
  21. package/dist/src/branch-names.d.ts.map +1 -0
  22. package/dist/src/branch-names.js +22 -0
  23. package/dist/src/branch-names.js.map +1 -0
  24. package/dist/src/closest-names.d.ts +28 -0
  25. package/dist/src/closest-names.d.ts.map +1 -0
  26. package/dist/src/closest-names.js +98 -0
  27. package/dist/src/closest-names.js.map +1 -0
  28. package/dist/src/code-execution/py-runner.d.ts +16 -5
  29. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  30. package/dist/src/code-execution/py-runner.js +186 -74
  31. package/dist/src/code-execution/py-runner.js.map +1 -1
  32. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  33. package/dist/src/code-execution/runtime-py.js +49 -3
  34. package/dist/src/code-execution/runtime-py.js.map +1 -1
  35. package/dist/src/code-execution/tool-definition.d.ts +12 -1
  36. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  37. package/dist/src/code-execution/tool-definition.js +21 -3
  38. package/dist/src/code-execution/tool-definition.js.map +1 -1
  39. package/dist/src/config-checks.d.ts +28 -0
  40. package/dist/src/config-checks.d.ts.map +1 -0
  41. package/dist/src/config-checks.js +52 -0
  42. package/dist/src/config-checks.js.map +1 -0
  43. package/dist/src/deadline-timer.d.ts +21 -0
  44. package/dist/src/deadline-timer.d.ts.map +1 -0
  45. package/dist/src/deadline-timer.js +43 -0
  46. package/dist/src/deadline-timer.js.map +1 -0
  47. package/dist/src/error-message.d.ts +30 -0
  48. package/dist/src/error-message.d.ts.map +1 -0
  49. package/dist/src/error-message.js +50 -0
  50. package/dist/src/error-message.js.map +1 -0
  51. package/dist/src/framework.d.ts +1212 -192
  52. package/dist/src/framework.d.ts.map +1 -1
  53. package/dist/src/framework.js +6686 -1835
  54. package/dist/src/framework.js.map +1 -1
  55. package/dist/src/gate/event-gate.d.ts +8 -0
  56. package/dist/src/gate/event-gate.d.ts.map +1 -1
  57. package/dist/src/gate/event-gate.js +27 -3
  58. package/dist/src/gate/event-gate.js.map +1 -1
  59. package/dist/src/gate/gate-script.d.ts.map +1 -1
  60. package/dist/src/gate/gate-script.js.map +1 -1
  61. package/dist/src/index.d.ts +5 -3
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +5 -3
  64. package/dist/src/index.js.map +1 -1
  65. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  66. package/dist/src/mcpl/channel-registry.d.ts +170 -13
  67. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  68. package/dist/src/mcpl/channel-registry.js +499 -258
  69. package/dist/src/mcpl/channel-registry.js.map +1 -1
  70. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  71. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  72. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  73. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  74. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  75. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  76. package/dist/src/mcpl/conversation-router.js +23 -8
  77. package/dist/src/mcpl/conversation-router.js.map +1 -1
  78. package/dist/src/mcpl/convert-block.d.ts +20 -0
  79. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  80. package/dist/src/mcpl/convert-block.js +89 -0
  81. package/dist/src/mcpl/convert-block.js.map +1 -0
  82. package/dist/src/mcpl/errors.d.ts +13 -0
  83. package/dist/src/mcpl/errors.d.ts.map +1 -1
  84. package/dist/src/mcpl/errors.js +28 -0
  85. package/dist/src/mcpl/errors.js.map +1 -1
  86. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  87. package/dist/src/mcpl/feature-set-manager.js +4 -3
  88. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  89. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  90. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  91. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  92. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  93. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  94. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  95. package/dist/src/mcpl/inbound-source.js +83 -0
  96. package/dist/src/mcpl/inbound-source.js.map +1 -0
  97. package/dist/src/mcpl/index.d.ts +3 -1
  98. package/dist/src/mcpl/index.d.ts.map +1 -1
  99. package/dist/src/mcpl/index.js +3 -1
  100. package/dist/src/mcpl/index.js.map +1 -1
  101. package/dist/src/mcpl/inference-router.d.ts +15 -11
  102. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  103. package/dist/src/mcpl/inference-router.js +153 -84
  104. package/dist/src/mcpl/inference-router.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +54 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -0
  107. package/dist/src/mcpl/log-value.js +109 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -0
  109. package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
  110. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  111. package/dist/src/mcpl/prose-stream-router.js +4 -4
  112. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  113. package/dist/src/mcpl/push-coalescer.d.ts +64 -7
  114. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  115. package/dist/src/mcpl/push-coalescer.js +121 -24
  116. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  117. package/dist/src/mcpl/push-handler.d.ts +38 -8
  118. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  119. package/dist/src/mcpl/push-handler.js +77 -78
  120. package/dist/src/mcpl/push-handler.js.map +1 -1
  121. package/dist/src/mcpl/reference-fetcher.d.ts +14 -2
  122. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  123. package/dist/src/mcpl/reference-fetcher.js +72 -30
  124. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  125. package/dist/src/mcpl/references.d.ts +49 -14
  126. package/dist/src/mcpl/references.d.ts.map +1 -1
  127. package/dist/src/mcpl/references.js +74 -21
  128. package/dist/src/mcpl/references.js.map +1 -1
  129. package/dist/src/mcpl/refusal-lines.d.ts +37 -0
  130. package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
  131. package/dist/src/mcpl/refusal-lines.js +61 -0
  132. package/dist/src/mcpl/refusal-lines.js.map +1 -0
  133. package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
  134. package/dist/src/mcpl/server-connection.d.ts +107 -12
  135. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  136. package/dist/src/mcpl/server-connection.js +470 -139
  137. package/dist/src/mcpl/server-connection.js.map +1 -1
  138. package/dist/src/mcpl/server-registry.d.ts +10 -1
  139. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  140. package/dist/src/mcpl/server-registry.js +14 -3
  141. package/dist/src/mcpl/server-registry.js.map +1 -1
  142. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  143. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  144. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  145. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  146. package/dist/src/mcpl/tool-classes.d.ts +1 -1
  147. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  148. package/dist/src/mcpl/tool-classes.js +3 -2
  149. package/dist/src/mcpl/tool-classes.js.map +1 -1
  150. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  151. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  152. package/dist/src/mcpl/transport.d.ts +74 -7
  153. package/dist/src/mcpl/transport.d.ts.map +1 -1
  154. package/dist/src/mcpl/transport.js +209 -22
  155. package/dist/src/mcpl/transport.js.map +1 -1
  156. package/dist/src/mcpl/types.d.ts +43 -28
  157. package/dist/src/mcpl/types.d.ts.map +1 -1
  158. package/dist/src/mcpl/types.js.map +1 -1
  159. package/dist/src/mcpl/visible-content.d.ts +15 -0
  160. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  161. package/dist/src/mcpl/visible-content.js +25 -0
  162. package/dist/src/mcpl/visible-content.js.map +1 -1
  163. package/dist/src/module-registry.d.ts +19 -1
  164. package/dist/src/module-registry.d.ts.map +1 -1
  165. package/dist/src/module-registry.js +54 -25
  166. package/dist/src/module-registry.js.map +1 -1
  167. package/dist/src/modules/api/index.d.ts.map +1 -1
  168. package/dist/src/modules/api/index.js +1 -1
  169. package/dist/src/modules/api/index.js.map +1 -1
  170. package/dist/src/modules/discord/discord-js-client.d.ts +1 -0
  171. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  172. package/dist/src/modules/discord/discord-js-client.js +25 -9
  173. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  174. package/dist/src/modules/discord/index.d.ts +29 -3
  175. package/dist/src/modules/discord/index.d.ts.map +1 -1
  176. package/dist/src/modules/discord/index.js +383 -145
  177. package/dist/src/modules/discord/index.js.map +1 -1
  178. package/dist/src/modules/discord/types.d.ts +14 -1
  179. package/dist/src/modules/discord/types.d.ts.map +1 -1
  180. package/dist/src/modules/health/index.d.ts.map +1 -1
  181. package/dist/src/modules/health/index.js +2 -1
  182. package/dist/src/modules/health/index.js.map +1 -1
  183. package/dist/src/modules/history/index.d.ts +3 -1
  184. package/dist/src/modules/history/index.d.ts.map +1 -1
  185. package/dist/src/modules/history/index.js +359 -127
  186. package/dist/src/modules/history/index.js.map +1 -1
  187. package/dist/src/modules/history/semantic.d.ts +28 -1
  188. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  189. package/dist/src/modules/history/semantic.js +177 -32
  190. package/dist/src/modules/history/semantic.js.map +1 -1
  191. package/dist/src/modules/unknown-tool.d.ts +10 -0
  192. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  193. package/dist/src/modules/unknown-tool.js +17 -0
  194. package/dist/src/modules/unknown-tool.js.map +1 -0
  195. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  196. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  197. package/dist/src/modules/workspace/disk-agreement.d.ts +54 -6
  198. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  199. package/dist/src/modules/workspace/disk-agreement.js +93 -27
  200. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  201. package/dist/src/modules/workspace/effects.d.ts +64 -6
  202. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  203. package/dist/src/modules/workspace/effects.js +291 -44
  204. package/dist/src/modules/workspace/effects.js.map +1 -1
  205. package/dist/src/modules/workspace/index.d.ts +127 -27
  206. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  207. package/dist/src/modules/workspace/index.js +821 -248
  208. package/dist/src/modules/workspace/index.js.map +1 -1
  209. package/dist/src/modules/workspace/observe.d.ts +117 -15
  210. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  211. package/dist/src/modules/workspace/observe.js +436 -51
  212. package/dist/src/modules/workspace/observe.js.map +1 -1
  213. package/dist/src/modules/workspace/reconcile.d.ts +75 -8
  214. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  215. package/dist/src/modules/workspace/reconcile.js +576 -68
  216. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  217. package/dist/src/modules/workspace/types.d.ts +42 -3
  218. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  219. package/dist/src/modules/workspace/types.js.map +1 -1
  220. package/dist/src/modules/workspace/watcher.d.ts +7 -2
  221. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  222. package/dist/src/modules/workspace/watcher.js +28 -4
  223. package/dist/src/modules/workspace/watcher.js.map +1 -1
  224. package/dist/src/operator-log.d.ts.map +1 -1
  225. package/dist/src/operator-log.js.map +1 -1
  226. package/dist/src/prose-drafts.d.ts +240 -0
  227. package/dist/src/prose-drafts.d.ts.map +1 -0
  228. package/dist/src/prose-drafts.js +362 -0
  229. package/dist/src/prose-drafts.js.map +1 -0
  230. package/dist/src/prose-segments.d.ts +21 -2
  231. package/dist/src/prose-segments.d.ts.map +1 -1
  232. package/dist/src/prose-segments.js +46 -9
  233. package/dist/src/prose-segments.js.map +1 -1
  234. package/dist/src/provider-waits.d.ts.map +1 -1
  235. package/dist/src/provider-waits.js.map +1 -1
  236. package/dist/src/queue.d.ts.map +1 -1
  237. package/dist/src/record-journal.d.ts.map +1 -1
  238. package/dist/src/record-journal.js.map +1 -1
  239. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  240. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  241. package/dist/src/recovery/offline-branch.d.ts +1 -0
  242. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  243. package/dist/src/recovery/offline-branch.js +32 -25
  244. package/dist/src/recovery/offline-branch.js.map +1 -1
  245. package/dist/src/recovery/recover-cli.js.map +1 -1
  246. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
  247. package/dist/src/runtime/liveness-watchdog.js.map +1 -1
  248. package/dist/src/safe-slice.d.ts +5 -1
  249. package/dist/src/safe-slice.d.ts.map +1 -1
  250. package/dist/src/safe-slice.js +9 -1
  251. package/dist/src/safe-slice.js.map +1 -1
  252. package/dist/src/shutdown.d.ts.map +1 -1
  253. package/dist/src/shutdown.js.map +1 -1
  254. package/dist/src/stored-removal.d.ts +73 -0
  255. package/dist/src/stored-removal.d.ts.map +1 -0
  256. package/dist/src/stored-removal.js +100 -0
  257. package/dist/src/stored-removal.js.map +1 -0
  258. package/dist/src/thinking-token-stamp.js.map +1 -1
  259. package/dist/src/tool-image-ledger.d.ts +87 -19
  260. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  261. package/dist/src/tool-image-ledger.js +135 -32
  262. package/dist/src/tool-image-ledger.js.map +1 -1
  263. package/dist/src/tool-presentation.d.ts +9 -0
  264. package/dist/src/tool-presentation.d.ts.map +1 -1
  265. package/dist/src/tool-presentation.js +257 -60
  266. package/dist/src/tool-presentation.js.map +1 -1
  267. package/dist/src/tool-result-guard.d.ts.map +1 -1
  268. package/dist/src/tool-result-guard.js +7 -10
  269. package/dist/src/tool-result-guard.js.map +1 -1
  270. package/dist/src/tool-result-history.d.ts +32 -3
  271. package/dist/src/tool-result-history.d.ts.map +1 -1
  272. package/dist/src/tool-result-history.js +106 -19
  273. package/dist/src/tool-result-history.js.map +1 -1
  274. package/dist/src/tune-out/coordinator.d.ts +25 -1
  275. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  276. package/dist/src/tune-out/coordinator.js +260 -65
  277. package/dist/src/tune-out/coordinator.js.map +1 -1
  278. package/dist/src/tune-out/tools.d.ts +1 -1
  279. package/dist/src/tune-out/tools.d.ts.map +1 -1
  280. package/dist/src/types/agent.d.ts +59 -11
  281. package/dist/src/types/agent.d.ts.map +1 -1
  282. package/dist/src/types/events.d.ts +17 -0
  283. package/dist/src/types/events.d.ts.map +1 -1
  284. package/dist/src/types/framework.d.ts +31 -5
  285. package/dist/src/types/framework.d.ts.map +1 -1
  286. package/dist/src/types/inference-log.d.ts +1 -1
  287. package/dist/src/types/inference-log.d.ts.map +1 -1
  288. package/dist/src/types/module.d.ts +30 -7
  289. package/dist/src/types/module.d.ts.map +1 -1
  290. package/dist/src/types/trace.d.ts +151 -11
  291. package/dist/src/types/trace.d.ts.map +1 -1
  292. package/dist/src/usage/usage-tracker.d.ts +5 -0
  293. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  294. package/dist/src/usage/usage-tracker.js +35 -8
  295. package/dist/src/usage/usage-tracker.js.map +1 -1
  296. package/package.json +10 -9
  297. package/src/agent.ts +572 -105
  298. package/src/api/mcp-server-core.ts +681 -0
  299. package/src/api/mcp-server.ts +16 -685
  300. package/src/api/server.ts +55 -30
  301. package/src/api/types.ts +10 -2
  302. package/src/branch-names.ts +19 -0
  303. package/src/closest-names.ts +94 -0
  304. package/src/code-execution/py-runner.ts +200 -76
  305. package/src/code-execution/runtime-py.ts +49 -3
  306. package/src/code-execution/tool-definition.ts +28 -4
  307. package/src/config-checks.ts +48 -0
  308. package/src/deadline-timer.ts +44 -0
  309. package/src/error-message.ts +48 -0
  310. package/src/framework.ts +7158 -1936
  311. package/src/gate/event-gate.ts +28 -3
  312. package/src/index.ts +12 -3
  313. package/src/mcpl/channel-registry.ts +615 -289
  314. package/src/mcpl/checkpoint-manager.ts +104 -29
  315. package/src/mcpl/conversation-router.ts +28 -8
  316. package/src/mcpl/convert-block.ts +97 -0
  317. package/src/mcpl/errors.ts +28 -0
  318. package/src/mcpl/feature-set-manager.ts +4 -3
  319. package/src/mcpl/hook-orchestrator.ts +84 -77
  320. package/src/mcpl/inbound-source.ts +156 -0
  321. package/src/mcpl/index.ts +10 -1
  322. package/src/mcpl/inference-router.ts +174 -99
  323. package/src/mcpl/log-value.ts +116 -0
  324. package/src/mcpl/prose-stream-router.ts +6 -6
  325. package/src/mcpl/push-coalescer.ts +124 -24
  326. package/src/mcpl/push-handler.ts +86 -84
  327. package/src/mcpl/reference-fetcher.ts +71 -26
  328. package/src/mcpl/references.ts +81 -22
  329. package/src/mcpl/refusal-lines.ts +59 -0
  330. package/src/mcpl/server-connection.ts +496 -142
  331. package/src/mcpl/server-registry.ts +15 -4
  332. package/src/mcpl/skip-reply-wake.ts +73 -0
  333. package/src/mcpl/tool-classes.ts +3 -2
  334. package/src/mcpl/transport.ts +219 -26
  335. package/src/mcpl/types.ts +18 -3
  336. package/src/mcpl/visible-content.ts +27 -0
  337. package/src/module-registry.ts +58 -23
  338. package/src/modules/api/index.ts +1 -1
  339. package/src/modules/discord/discord-js-client.ts +26 -10
  340. package/src/modules/discord/index.ts +395 -160
  341. package/src/modules/discord/types.ts +15 -2
  342. package/src/modules/health/index.ts +2 -1
  343. package/src/modules/history/index.ts +410 -120
  344. package/src/modules/history/semantic.ts +189 -32
  345. package/src/modules/unknown-tool.ts +16 -0
  346. package/src/modules/workspace/disk-agreement.ts +125 -21
  347. package/src/modules/workspace/effects.ts +279 -35
  348. package/src/modules/workspace/index.ts +841 -261
  349. package/src/modules/workspace/observe.ts +456 -56
  350. package/src/modules/workspace/reconcile.ts +688 -76
  351. package/src/modules/workspace/types.ts +41 -2
  352. package/src/modules/workspace/watcher.ts +27 -4
  353. package/src/prose-drafts.ts +481 -0
  354. package/src/prose-segments.ts +51 -8
  355. package/src/recovery/offline-branch.ts +36 -29
  356. package/src/safe-slice.ts +9 -1
  357. package/src/stored-removal.ts +126 -0
  358. package/src/tool-image-ledger.ts +153 -34
  359. package/src/tool-presentation.ts +224 -40
  360. package/src/tool-result-guard.ts +7 -8
  361. package/src/tool-result-history.ts +124 -20
  362. package/src/tune-out/coordinator.ts +259 -65
  363. package/src/types/agent.ts +59 -13
  364. package/src/types/events.ts +17 -0
  365. package/src/types/framework.ts +31 -5
  366. package/src/types/inference-log.ts +1 -1
  367. package/src/types/module.ts +31 -7
  368. package/src/types/trace.ts +158 -12
  369. package/src/usage/usage-tracker.ts +32 -8
  370. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  371. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  372. package/dist/test/activation-request-sanitize.test.js +0 -89
  373. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  374. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  375. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  376. package/dist/test/agent-cache-ttl.test.js +0 -41
  377. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  378. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  379. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  380. package/dist/test/agent-runtime-settings.test.js +0 -462
  381. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  382. package/dist/test/api-admin-gate.test.d.ts +0 -2
  383. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  384. package/dist/test/api-admin-gate.test.js +0 -116
  385. package/dist/test/api-admin-gate.test.js.map +0 -1
  386. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  387. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  388. package/dist/test/assistant-persist-refusal.test.js +0 -63
  389. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  390. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  391. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  392. package/dist/test/batched-wake-locus.test.js +0 -166
  393. package/dist/test/batched-wake-locus.test.js.map +0 -1
  394. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  395. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  396. package/dist/test/busy-agent-scheduler.test.js +0 -92
  397. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  398. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  399. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  400. package/dist/test/channel-incoming-targeting.test.js +0 -81
  401. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  402. package/dist/test/channel-label-history.test.d.ts +0 -2
  403. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  404. package/dist/test/channel-label-history.test.js +0 -424
  405. package/dist/test/channel-label-history.test.js.map +0 -1
  406. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  407. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  408. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  409. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  410. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  411. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  412. package/dist/test/channel-registry-routing.test.js +0 -446
  413. package/dist/test/channel-registry-routing.test.js.map +0 -1
  414. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  415. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  416. package/dist/test/channel-registry-typing.test.js +0 -91
  417. package/dist/test/channel-registry-typing.test.js.map +0 -1
  418. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  419. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  420. package/dist/test/channels-changed-respond-first.test.js +0 -74
  421. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  422. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  423. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  424. package/dist/test/checkpoint-attribution.test.js +0 -200
  425. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  426. package/dist/test/code-execution.test.d.ts +0 -11
  427. package/dist/test/code-execution.test.d.ts.map +0 -1
  428. package/dist/test/code-execution.test.js +0 -1272
  429. package/dist/test/code-execution.test.js.map +0 -1
  430. package/dist/test/component-defaults.test.d.ts +0 -2
  431. package/dist/test/component-defaults.test.d.ts.map +0 -1
  432. package/dist/test/component-defaults.test.js +0 -54
  433. package/dist/test/component-defaults.test.js.map +0 -1
  434. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  435. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  436. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  437. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  438. package/dist/test/conversation-router.test.d.ts +0 -2
  439. package/dist/test/conversation-router.test.d.ts.map +0 -1
  440. package/dist/test/conversation-router.test.js +0 -174
  441. package/dist/test/conversation-router.test.js.map +0 -1
  442. package/dist/test/conversation-routing.test.d.ts +0 -7
  443. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  444. package/dist/test/conversation-routing.test.js +0 -387
  445. package/dist/test/conversation-routing.test.js.map +0 -1
  446. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  447. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  448. package/dist/test/deferred-wake-integrity.test.js +0 -320
  449. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  450. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  451. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  452. package/dist/test/discord-awareness-outbox.test.js +0 -267
  453. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  454. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  455. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  456. package/dist/test/discord-history-delete-window.test.js +0 -81
  457. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  458. package/dist/test/discord-history-sync.test.d.ts +0 -2
  459. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  460. package/dist/test/discord-history-sync.test.js +0 -56
  461. package/dist/test/discord-history-sync.test.js.map +0 -1
  462. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  463. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  464. package/dist/test/ephemeral-settle.test.js +0 -354
  465. package/dist/test/ephemeral-settle.test.js.map +0 -1
  466. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  467. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  468. package/dist/test/event-gate-passthrough.test.js +0 -289
  469. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  470. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  471. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  472. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  473. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  474. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  475. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  476. package/dist/test/event-gate-rate-limit.test.js +0 -627
  477. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  478. package/dist/test/event-gate-script.test.d.ts +0 -2
  479. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  480. package/dist/test/event-gate-script.test.js +0 -95
  481. package/dist/test/event-gate-script.test.js.map +0 -1
  482. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  483. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  484. package/dist/test/event-gate-selfwake.test.js +0 -125
  485. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  486. package/dist/test/event-gate-tags.test.d.ts +0 -2
  487. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  488. package/dist/test/event-gate-tags.test.js +0 -76
  489. package/dist/test/event-gate-tags.test.js.map +0 -1
  490. package/dist/test/event-gate.test.d.ts +0 -2
  491. package/dist/test/event-gate.test.d.ts.map +0 -1
  492. package/dist/test/event-gate.test.js +0 -919
  493. package/dist/test/event-gate.test.js.map +0 -1
  494. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  495. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  496. package/dist/test/explicit-prose-routing.test.js +0 -1394
  497. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  498. package/dist/test/framework.test.d.ts +0 -5
  499. package/dist/test/framework.test.d.ts.map +0 -1
  500. package/dist/test/framework.test.js +0 -1048
  501. package/dist/test/framework.test.js.map +0 -1
  502. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  503. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  504. package/dist/test/gather-context-timeout.test.js +0 -120
  505. package/dist/test/gather-context-timeout.test.js.map +0 -1
  506. package/dist/test/health-snapshot.test.d.ts +0 -2
  507. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  508. package/dist/test/health-snapshot.test.js +0 -74
  509. package/dist/test/health-snapshot.test.js.map +0 -1
  510. package/dist/test/helpers/coalescing-fixture.d.ts +0 -72
  511. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  512. package/dist/test/helpers/coalescing-fixture.js +0 -146
  513. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  514. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  515. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  516. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  517. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  518. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  519. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  520. package/dist/test/helpers/mock-membrane.js +0 -168
  521. package/dist/test/helpers/mock-membrane.js.map +0 -1
  522. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  523. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  524. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  525. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  526. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  527. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  528. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  529. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  530. package/dist/test/history-module-overview.test.d.ts +0 -2
  531. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  532. package/dist/test/history-module-overview.test.js +0 -711
  533. package/dist/test/history-module-overview.test.js.map +0 -1
  534. package/dist/test/history-module-realstore.test.d.ts +0 -2
  535. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  536. package/dist/test/history-module-realstore.test.js +0 -363
  537. package/dist/test/history-module-realstore.test.js.map +0 -1
  538. package/dist/test/history-module-semantic.test.d.ts +0 -2
  539. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  540. package/dist/test/history-module-semantic.test.js +0 -213
  541. package/dist/test/history-module-semantic.test.js.map +0 -1
  542. package/dist/test/history-module-ux.test.d.ts +0 -2
  543. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  544. package/dist/test/history-module-ux.test.js +0 -304
  545. package/dist/test/history-module-ux.test.js.map +0 -1
  546. package/dist/test/history-module.test.d.ts +0 -2
  547. package/dist/test/history-module.test.d.ts.map +0 -1
  548. package/dist/test/history-module.test.js +0 -619
  549. package/dist/test/history-module.test.js.map +0 -1
  550. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  551. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  552. package/dist/test/history-semantic-review-findings.test.js +0 -338
  553. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  554. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  555. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  556. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  557. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  558. package/dist/test/host-quiesce.test.d.ts +0 -2
  559. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  560. package/dist/test/host-quiesce.test.js +0 -863
  561. package/dist/test/host-quiesce.test.js.map +0 -1
  562. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  563. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  564. package/dist/test/inference-exhaustion-rewind.test.js +0 -309
  565. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  566. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  567. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  568. package/dist/test/inference-failure-bounds.test.js +0 -1201
  569. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  570. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  571. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  572. package/dist/test/inference-failure-observability.test.js +0 -84
  573. package/dist/test/inference-failure-observability.test.js.map +0 -1
  574. package/dist/test/journal-tool.test.d.ts +0 -2
  575. package/dist/test/journal-tool.test.d.ts.map +0 -1
  576. package/dist/test/journal-tool.test.js +0 -33
  577. package/dist/test/journal-tool.test.js.map +0 -1
  578. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  579. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  580. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  581. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  582. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  583. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  584. package/dist/test/kv-unified-wiring.test.js +0 -95
  585. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  586. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  587. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  588. package/dist/test/late-events-after-stop.test.js +0 -122
  589. package/dist/test/late-events-after-stop.test.js.map +0 -1
  590. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  591. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  592. package/dist/test/liveness-watchdog.test.js +0 -94
  593. package/dist/test/liveness-watchdog.test.js.map +0 -1
  594. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  595. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  596. package/dist/test/machine-close-provenance.test.js +0 -116
  597. package/dist/test/machine-close-provenance.test.js.map +0 -1
  598. package/dist/test/maintenance-timer.test.d.ts +0 -2
  599. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  600. package/dist/test/maintenance-timer.test.js +0 -265
  601. package/dist/test/maintenance-timer.test.js.map +0 -1
  602. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  603. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  604. package/dist/test/mcpl-access-provider.test.js +0 -20
  605. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  606. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  607. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  608. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  609. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  610. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  611. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  612. package/dist/test/mcpl-capability-grant.test.js +0 -111
  613. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  614. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  615. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  616. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  617. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  618. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  619. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  620. package/dist/test/mcpl-child-env.test.js +0 -158
  621. package/dist/test/mcpl-child-env.test.js.map +0 -1
  622. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  623. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  624. package/dist/test/mcpl-coalescing.test.js +0 -1125
  625. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  626. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  627. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  628. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  629. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  630. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  631. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  632. package/dist/test/mcpl-empty-content.test.js +0 -368
  633. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  634. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  635. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  636. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  637. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  638. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  639. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  640. package/dist/test/mcpl-list-status.test.js +0 -82
  641. package/dist/test/mcpl-list-status.test.js.map +0 -1
  642. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  643. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  644. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  645. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  646. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  647. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  648. package/dist/test/mcpl-push-convert.test.js +0 -42
  649. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  650. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  651. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  652. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  653. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  654. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  655. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  656. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -236
  657. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  658. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  659. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  660. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  661. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  662. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  663. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  664. package/dist/test/mcpl-reference-fields.test.js +0 -149
  665. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  666. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  667. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  668. package/dist/test/mcpl-request-timeout.test.js +0 -138
  669. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  670. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  671. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  672. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  673. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  674. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  675. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  676. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  677. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  678. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  679. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  680. package/dist/test/mcpl-tool-classes.test.js +0 -80
  681. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  682. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  683. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  684. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  685. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  686. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  687. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  688. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  689. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  690. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  691. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  692. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  693. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  694. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  695. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  696. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  697. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  698. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  699. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  700. package/dist/test/mcpl-ws-transport.test.js +0 -258
  701. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  702. package/dist/test/module-context-integration.test.d.ts +0 -18
  703. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  704. package/dist/test/module-context-integration.test.js +0 -181
  705. package/dist/test/module-context-integration.test.js.map +0 -1
  706. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  707. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  708. package/dist/test/offline-recovery-branch.test.js +0 -226
  709. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  710. package/dist/test/operator-surgery.test.d.ts +0 -2
  711. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  712. package/dist/test/operator-surgery.test.js +0 -398
  713. package/dist/test/operator-surgery.test.js.map +0 -1
  714. package/dist/test/ops-alert.test.d.ts +0 -2
  715. package/dist/test/ops-alert.test.d.ts.map +0 -1
  716. package/dist/test/ops-alert.test.js +0 -153
  717. package/dist/test/ops-alert.test.js.map +0 -1
  718. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  719. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  720. package/dist/test/overbudget-drain-breaker.test.js +0 -197
  721. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  722. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  723. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  724. package/dist/test/per-agent-delivery.test.js +0 -108
  725. package/dist/test/per-agent-delivery.test.js.map +0 -1
  726. package/dist/test/physical-window-restart.test.d.ts +0 -14
  727. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  728. package/dist/test/physical-window-restart.test.js +0 -222
  729. package/dist/test/physical-window-restart.test.js.map +0 -1
  730. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  731. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  732. package/dist/test/placed-reaction-baseline.test.js +0 -312
  733. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  734. package/dist/test/present-while-acting.test.d.ts +0 -19
  735. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  736. package/dist/test/present-while-acting.test.js +0 -706
  737. package/dist/test/present-while-acting.test.js.map +0 -1
  738. package/dist/test/prose-segments.test.d.ts +0 -2
  739. package/dist/test/prose-segments.test.d.ts.map +0 -1
  740. package/dist/test/prose-segments.test.js +0 -52
  741. package/dist/test/prose-segments.test.js.map +0 -1
  742. package/dist/test/prose-stream-router.test.d.ts +0 -2
  743. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  744. package/dist/test/prose-stream-router.test.js +0 -171
  745. package/dist/test/prose-stream-router.test.js.map +0 -1
  746. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  747. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  748. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  749. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  750. package/dist/test/provider-waits.test.d.ts +0 -2
  751. package/dist/test/provider-waits.test.d.ts.map +0 -1
  752. package/dist/test/provider-waits.test.js +0 -2196
  753. package/dist/test/provider-waits.test.js.map +0 -1
  754. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  755. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  756. package/dist/test/puppet-tool-call.test.js +0 -224
  757. package/dist/test/puppet-tool-call.test.js.map +0 -1
  758. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  759. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  760. package/dist/test/puppet-turn-reservation.test.js +0 -139
  761. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  762. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  763. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  764. package/dist/test/reaction-baseline-platform.test.js +0 -124
  765. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  766. package/dist/test/record-journal.test.d.ts +0 -6
  767. package/dist/test/record-journal.test.d.ts.map +0 -1
  768. package/dist/test/record-journal.test.js +0 -269
  769. package/dist/test/record-journal.test.js.map +0 -1
  770. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  771. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  772. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  773. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  774. package/dist/test/refusal-rewind.test.d.ts +0 -2
  775. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  776. package/dist/test/refusal-rewind.test.js +0 -108
  777. package/dist/test/refusal-rewind.test.js.map +0 -1
  778. package/dist/test/refusal-single-call.test.d.ts +0 -2
  779. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  780. package/dist/test/refusal-single-call.test.js +0 -177
  781. package/dist/test/refusal-single-call.test.js.map +0 -1
  782. package/dist/test/release-changelog.test.d.ts +0 -2
  783. package/dist/test/release-changelog.test.d.ts.map +0 -1
  784. package/dist/test/release-changelog.test.js +0 -159
  785. package/dist/test/release-changelog.test.js.map +0 -1
  786. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  787. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  788. package/dist/test/same-round-think-policy.test.js +0 -853
  789. package/dist/test/same-round-think-policy.test.js.map +0 -1
  790. package/dist/test/save-recent-image.test.d.ts +0 -26
  791. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  792. package/dist/test/save-recent-image.test.js +0 -974
  793. package/dist/test/save-recent-image.test.js.map +0 -1
  794. package/dist/test/shutdown-errors.test.d.ts +0 -2
  795. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  796. package/dist/test/shutdown-errors.test.js +0 -662
  797. package/dist/test/shutdown-errors.test.js.map +0 -1
  798. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  799. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  800. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  801. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  802. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  803. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  804. package/dist/test/silent-heartbeat.test.js +0 -165
  805. package/dist/test/silent-heartbeat.test.js.map +0 -1
  806. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  807. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  808. package/dist/test/state-scaling-e2e.test.js +0 -206
  809. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  810. package/dist/test/state-scaling.test.d.ts +0 -12
  811. package/dist/test/state-scaling.test.d.ts.map +0 -1
  812. package/dist/test/state-scaling.test.js +0 -251
  813. package/dist/test/state-scaling.test.js.map +0 -1
  814. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  815. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  816. package/dist/test/stop-with-work-in-flight.test.js +0 -672
  817. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  818. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  819. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  820. package/dist/test/surgery-marks-receipt.test.js +0 -540
  821. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  822. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  823. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  824. package/dist/test/thinking-token-stamp.test.js +0 -129
  825. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  826. package/dist/test/timezone.test.d.ts +0 -2
  827. package/dist/test/timezone.test.d.ts.map +0 -1
  828. package/dist/test/timezone.test.js +0 -18
  829. package/dist/test/timezone.test.js.map +0 -1
  830. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  831. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  832. package/dist/test/tool-batch-hook.test.js +0 -275
  833. package/dist/test/tool-batch-hook.test.js.map +0 -1
  834. package/dist/test/tool-policy.test.d.ts +0 -2
  835. package/dist/test/tool-policy.test.d.ts.map +0 -1
  836. package/dist/test/tool-policy.test.js +0 -51
  837. package/dist/test/tool-policy.test.js.map +0 -1
  838. package/dist/test/tool-presentation.test.d.ts +0 -2
  839. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  840. package/dist/test/tool-presentation.test.js +0 -268
  841. package/dist/test/tool-presentation.test.js.map +0 -1
  842. package/dist/test/tool-result-content.test.d.ts +0 -2
  843. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  844. package/dist/test/tool-result-content.test.js +0 -37
  845. package/dist/test/tool-result-content.test.js.map +0 -1
  846. package/dist/test/tool-result-guard.test.d.ts +0 -2
  847. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  848. package/dist/test/tool-result-guard.test.js +0 -3607
  849. package/dist/test/tool-result-guard.test.js.map +0 -1
  850. package/dist/test/tool-result-history.test.d.ts +0 -7
  851. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  852. package/dist/test/tool-result-history.test.js +0 -156
  853. package/dist/test/tool-result-history.test.js.map +0 -1
  854. package/dist/test/tool-result-spill.test.d.ts +0 -13
  855. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  856. package/dist/test/tool-result-spill.test.js +0 -586
  857. package/dist/test/tool-result-spill.test.js.map +0 -1
  858. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  859. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  860. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  861. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  862. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  863. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  864. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  865. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  866. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  867. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  868. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  869. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  870. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  871. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  872. package/dist/test/trunk-channel-routing.test.js +0 -329
  873. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  874. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  875. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  876. package/dist/test/tune-out-e2e.test.js +0 -142
  877. package/dist/test/tune-out-e2e.test.js.map +0 -1
  878. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  879. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  880. package/dist/test/tune-out-gate-composition.test.js +0 -221
  881. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  882. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  883. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  884. package/dist/test/tune-out-lifecycle.test.js +0 -121
  885. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  886. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  887. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  888. package/dist/test/turn-trigger-provenance.test.js +0 -137
  889. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  890. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  891. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  892. package/dist/test/unconvertible-tool-result.test.js +0 -358
  893. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  894. package/dist/test/usage-accounting.test.d.ts +0 -2
  895. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  896. package/dist/test/usage-accounting.test.js +0 -13
  897. package/dist/test/usage-accounting.test.js.map +0 -1
  898. package/dist/test/usage-tracker.test.d.ts +0 -2
  899. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  900. package/dist/test/usage-tracker.test.js +0 -129
  901. package/dist/test/usage-tracker.test.js.map +0 -1
  902. package/dist/test/utils-surface.test.d.ts +0 -2
  903. package/dist/test/utils-surface.test.d.ts.map +0 -1
  904. package/dist/test/utils-surface.test.js +0 -145
  905. package/dist/test/utils-surface.test.js.map +0 -1
  906. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  907. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  908. package/dist/test/workspace-branch-continuation.test.js +0 -183
  909. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  910. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  911. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  912. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  913. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  914. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  915. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  916. package/dist/test/workspace-path-containment.test.js +0 -91
  917. package/dist/test/workspace-path-containment.test.js.map +0 -1
  918. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  919. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  920. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  921. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  922. package/dist/test/workspace-read-image.test.d.ts +0 -2
  923. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  924. package/dist/test/workspace-read-image.test.js +0 -597
  925. package/dist/test/workspace-read-image.test.js.map +0 -1
  926. package/dist/test/workspace-read-text.test.d.ts +0 -2
  927. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  928. package/dist/test/workspace-read-text.test.js +0 -101
  929. package/dist/test/workspace-read-text.test.js.map +0 -1
  930. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  931. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  932. package/dist/test/workspace-restore-order.test.js +0 -140
  933. package/dist/test/workspace-restore-order.test.js.map +0 -1
  934. package/dist/test/workspace-three-way.test.d.ts +0 -12
  935. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  936. package/dist/test/workspace-three-way.test.js +0 -3194
  937. package/dist/test/workspace-three-way.test.js.map +0 -1
  938. package/dist/test/workspace-watcher.test.d.ts +0 -2
  939. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  940. package/dist/test/workspace-watcher.test.js +0 -281
  941. package/dist/test/workspace-watcher.test.js.map +0 -1
  942. package/dist/test/xml-round-persist.test.d.ts +0 -19
  943. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  944. package/dist/test/xml-round-persist.test.js +0 -235
  945. package/dist/test/xml-round-persist.test.js.map +0 -1
  946. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -54,20 +54,26 @@ import {
54
54
  } from './disk-agreement.js';
55
55
  import type { BranchIntent, BranchIntents, ConflictKind, ConflictRecord, DiskCounterpart } from './branch-intent.js';
56
56
  import {
57
+ IGNORED,
57
58
  type MountView,
59
+ OTHER_MOUNT,
58
60
  ROOT_REPLACED,
59
61
  type Walk,
60
62
  fingerprintVouches,
63
+ ignoreBeneath,
64
+ inOtherMount,
61
65
  locateRoot,
62
66
  looksBinary,
63
67
  observePath,
68
+ passedOverAt,
69
+ passedOverBetween,
64
70
  provenAbsent,
65
71
  readHead,
66
72
  readPath,
67
73
  sniffImageMime,
68
74
  walkScope,
69
75
  } from './observe.js';
70
- import { EffectFailed, type Expect, chainOf, syncDirectories, unlinkContained, writeContained } from './effects.js';
76
+ import { EffectFailed, type Expect, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
71
77
 
72
78
  export type EntryState =
73
79
  | 'synced'
@@ -106,8 +112,11 @@ export interface PathReport {
106
112
  export type TreeOp = 'created' | 'modified' | 'deleted';
107
113
 
108
114
  export interface PassResult {
109
- /** Tree changes adopted from disk. */
110
- ops: Array<{ path: string; op: TreeOp }>;
115
+ /**
116
+ * Tree changes adopted from disk. `modeOnly`: disk's mode was taken in on
117
+ * the same bytes, which makes no event.
118
+ */
119
+ ops: Array<{ path: string; op: TreeOp; modeOnly?: true }>;
111
120
  /** What a path sync gave up of the workspace's own; `op` is what it did to the
112
121
  * workspace copy, absent when it changed none (a deletion or conflict given
113
122
  * up for a disk copy the workspace can't hold). */
@@ -120,9 +129,11 @@ export interface PassResult {
120
129
  incomplete: Array<{ path: string; reason: string }>;
121
130
  /**
122
131
  * What the walk passed over by design and reported nowhere else, counted by
123
- * reason: entries the mount's ignore list covers, when nothing beneath them
124
- * is tracked (a tracked one is reported under `incomplete`). An ignored
125
- * directory counts once, everything in it included. Only listings the walk
132
+ * reason: another mount's directory nested in this one, entries the mount's
133
+ * ignore list covers, and entries with an editor's swap or backup file
134
+ * name, when nothing beneath them is tracked (a tracked one is reported
135
+ * under `incomplete`). Such a directory counts
136
+ * once, everything in it included. Only listings the walk
126
137
  * kept count: a directory changed during the walk, or one past the file
127
138
  * cap, is reported under `incomplete` instead.
128
139
  */
@@ -152,6 +163,22 @@ export interface MountRuntime {
152
163
  treeStateId: string;
153
164
  intents: BranchIntents;
154
165
  readOnly: boolean;
166
+ /** The most entries one walk of the mount takes, files and the other entries that aren't directories (`MountConfig.walkCap`). */
167
+ walkCap: number;
168
+ /**
169
+ * Whether the mount runs a catch-up scan (`watch: 'always'`, at start and
170
+ * when its watcher reattaches), which takes turns with its whole-mount
171
+ * syncs.
172
+ */
173
+ catchUpScan: boolean;
174
+ /**
175
+ * Whether a file's mode is observed, taken in and applied: the module's,
176
+ * from the platform. Windows reports only 0o666 or 0o444, so there a store
177
+ * written on a POSIX host would lose its executable bits at the first look:
178
+ * modes go unobserved, as before they were recorded, and the store and the
179
+ * evidence keep their own for the next POSIX host.
180
+ */
181
+ modesObserved: boolean;
155
182
  }
156
183
 
157
184
  export type Scope =
@@ -163,11 +190,21 @@ export interface PassOptions {
163
190
  rehash?: boolean;
164
191
  /** Paths whose disk state is adopted explicitly (a path sync). */
165
192
  adopt?: (path: string) => boolean;
166
- /** Walk cap for directory scopes. */
193
+ /** Walk cap for directory scopes; the mount's `walkCap` when absent. */
167
194
  cap?: number;
168
195
  /**
169
- * The scope was named by a path sync: ignore patterns that cover it don't
170
- * apply beneath it (see walkScope). A listing's scope keeps them.
196
+ * A recursive directory pass of a sync or a catch-up scan: its walk resumes
197
+ * after the last entry the previous one of this scope took, and records
198
+ * where it stopped, so repeated passes cover a scope larger than the cap.
199
+ * A listing's pass, and an agent-action scan's, start at their scope's
200
+ * start and record no place.
201
+ */
202
+ resume?: boolean;
203
+ /**
204
+ * The scope was named, by a path sync or by a file tool's pass on the path
205
+ * it acts on: what the mount passes over by design that covers the scope,
206
+ * at it or above it, doesn't apply there (see walkScope). A listing's scope
207
+ * keeps it.
171
208
  */
172
209
  named?: boolean;
173
210
  /**
@@ -190,6 +227,12 @@ type DiskFact =
190
227
  hash: string;
191
228
  size: number;
192
229
  mtimeMs: number;
230
+ /**
231
+ * Permission bits (`st_mode & 0o777`); none where modes go unobserved,
232
+ * as on Windows, which keeps no POSIX modes (MountRuntime's
233
+ * `modesObserved`): nothing there is taken in or applied.
234
+ */
235
+ mode?: number;
193
236
  fp: Fingerprint | null;
194
237
  /** Text within limits, with its bytes: what disk→store sync may store. */
195
238
  ingestible: Buffer | null;
@@ -210,7 +253,63 @@ function diskCandidate(d: DiskFact): Candidate | null {
210
253
 
211
254
  function agreedFromDisk(d: DiskFact): Agreed {
212
255
  if (d.kind !== 'file') return { kind: 'absent' };
213
- return { kind: 'content', hash: d.hash, size: d.size, ...(d.fp ? { fp: d.fp } : {}) };
256
+ return { kind: 'content', hash: d.hash, size: d.size, ...(d.mode !== undefined ? { mode: d.mode } : {}), ...(d.fp ? { fp: d.fp } : {}) };
257
+ }
258
+
259
+ /** The file-type bits git records with a regular file's mode. */
260
+ const REGULAR_FILE = 0o100000;
261
+
262
+ /** A tree entry's mode for permission bits: with the regular-file type bits, as git records one. */
263
+ export function treeMode(perm: number): number {
264
+ return REGULAR_FILE | (perm & 0o777);
265
+ }
266
+
267
+ /**
268
+ * The mode a tree entry takes for disk's permission bits, or, where disk's
269
+ * aren't observed, the entry's own. With neither, the placeholder an entry
270
+ * without a recorded mode has (see entryMode).
271
+ */
272
+ function treeModeOf(perm: number | undefined, s: StoreSide | null): { mode: number } {
273
+ if (perm !== undefined) return { mode: treeMode(perm) };
274
+ if (s?.mode !== undefined) return { mode: treeMode(s.mode) };
275
+ return { mode: 0o644 };
276
+ }
277
+
278
+ /**
279
+ * The permission bits a tree entry records, or undefined when it records
280
+ * none. An entry without the file-type bits was written before modes were
281
+ * recorded, which wrote 0o644 whatever disk had: a placeholder, not a mode.
282
+ */
283
+ export function entryMode(mode: number): number | undefined {
284
+ return (mode & 0o170000) === REGULAR_FILE ? mode & 0o777 : undefined;
285
+ }
286
+
287
+ /** The store's side of a path: its entry's bytes, and its mode when recorded. */
288
+ type StoreSide = { hash: string; size: number; mode?: number };
289
+
290
+ /** The store's side of a tree entry. */
291
+ export function storeSide(entry: { blobHash: string; size: number; mode: number } | null): StoreSide | null {
292
+ if (!entry) return null;
293
+ const mode = entryMode(entry.mode);
294
+ return { hash: entry.blobHash, size: entry.size, ...(mode !== undefined ? { mode } : {}) };
295
+ }
296
+
297
+ /**
298
+ * Where disk and the store hold the same bytes, what their modes ask for.
299
+ * A mode the store doesn't record agrees with any, and takes disk's
300
+ * (`fill`). The store's mode goes to disk (`push`) only where the evidence
301
+ * shows disk still has the mode they last agreed on; otherwise disk's mode
302
+ * is taken in as a change (`adopt`): disk changed it, or nothing says which
303
+ * side did, and an unknown never moves disk.
304
+ */
305
+ function modeAction(disk: number, store: number | undefined, agreed: number | undefined): 'fill' | 'adopt' | 'push' | undefined {
306
+ if (store === undefined) return 'fill';
307
+ if (store === disk) return undefined;
308
+ return agreed !== undefined && agreed === disk ? 'push' : 'adopt';
309
+ }
310
+
311
+ function octal(mode: number): string {
312
+ return `0${mode.toString(8).padStart(3, '0')}`;
214
313
  }
215
314
 
216
315
  function inScope(path: string, scope: Scope): boolean {
@@ -237,7 +336,7 @@ async function learnDisk(
237
336
  const p = tracked.p;
238
337
  const trusted = (fp: { dev: string }) => mount.ctimeTrustedDev !== null && fp.dev === mount.ctimeTrustedDev;
239
338
  if (!opts.rehash && p?.kind === 'content' && fingerprintVouches(p.fp, seen.fp, trusted(seen.fp))) {
240
- return { kind: 'file', hash: p.hash, size: seen.size, mtimeMs: seen.mtimeMs, fp: p.fp ?? null, ingestible: null };
339
+ return { kind: 'file', hash: p.hash, size: seen.size, mtimeMs: seen.mtimeMs, ...(mount.modesObserved ? { mode: seen.mode } : {}), fp: p.fp ?? null, ingestible: null };
241
340
  }
242
341
  const nothingTracked = p === undefined && !tracked.hasStore && !tracked.hasIntent;
243
342
  if (nothingTracked && seen.size > mount.view.maxFileSize) {
@@ -255,6 +354,7 @@ async function learnDisk(
255
354
  hash: read.hash,
256
355
  size: read.size,
257
356
  mtimeMs: read.mtimeMs,
357
+ ...(mount.modesObserved ? { mode: read.mode } : {}),
258
358
  // A fingerprint is recorded only where it can later vouch for the bytes.
259
359
  fp: trusted(read.fp) ? read.fp : null,
260
360
  ingestible,
@@ -280,12 +380,40 @@ interface Verdict {
280
380
  * is decided from it, and nothing at all is recorded (see module doc).
281
381
  */
282
382
  unconfirmed?: true;
383
+ /**
384
+ * With `unconfirmed`: disk's mode is one of the stopped push's own
385
+ * (pushedMode), so the push that completes it gives the store's mode
386
+ * there, as it would where disk has the mode they last agreed on.
387
+ */
388
+ ownMode?: true;
389
+ /**
390
+ * A path sync's adoption over a push of ours that stopped partway: the
391
+ * mode the tree takes, which is the one the push meant the bytes on disk
392
+ * to have, not the one it left them at. The evidence keeps disk's actual
393
+ * mode, so a later push moves disk to the tree's.
394
+ */
395
+ diskMode?: number;
283
396
  /** A path sync's adoption gives up this workspace state (see decide). */
284
397
  discards?: EntryState;
398
+ /**
399
+ * Disk and the store hold the same bytes, and the store's mode is unknown
400
+ * or differs (see modeAction): `fill` and `adopt` record disk's mode on
401
+ * the store's entry, `adopt` as a change; `push` leaves it for a push.
402
+ */
403
+ mode?: 'fill' | 'adopt' | 'push';
285
404
  /**
286
405
  * The last confirmed evidence decide resolved for the path, which an
287
406
  * adoption's intent carries as its prior: a settled same-branch adoption's
288
407
  * outcome, where the recorded P is still that adoption's pending record.
408
+ * Or the evidence a push's narrowing left when it stopped before its
409
+ * bytes, which decide uses without recording it, so the intent stays: a
410
+ * push plans from it and goes on as the stopped push, its intent carrying
411
+ * that push's prior and modes (pushPaths), and a sync that adopts disk's
412
+ * copy carries it as its adoption's prior. An adoption that doesn't land
413
+ * returns to it, as every adoption returns to its prior: the store's
414
+ * change is owed again, and the next push completes it at the store's
415
+ * mode. The old bytes' own mode doesn't survive that, since the adoption's
416
+ * intent replaced the push's.
289
417
  */
290
418
  prior?: Agreed;
291
419
  }
@@ -316,14 +444,71 @@ function keepStoreSide(hasStore: boolean, bi: BranchIntent | null): EntryState {
316
444
  * mutation boundary): a later write of the same content can't pass for the
317
445
  * old adoption.
318
446
  */
319
- function settleAdoption(p: Pending, s: { hash: string; size: number } | null): Agreed | 'forget' {
447
+ function settleAdoption(p: Pending, s: StoreSide | null): Agreed | 'forget' {
320
448
  const sc: Candidate = s ? { kind: 'content', hash: s.hash } : ABSENT;
321
449
  if (sameCandidate(sc, p.expect)) {
322
- return p.expect.kind === 'absent' ? { kind: 'absent' } : { kind: 'content', hash: p.expect.hash, size: s!.size };
450
+ // Where modes are observed, the adoption took disk's mode with its bytes,
451
+ // so the entry's mode is the one they agreed on, but for an adoption that
452
+ // took the mode a stopped push of ours meant the old bytes to have: its
453
+ // entry holds that mode while disk holds the push's narrowed one, which a
454
+ // later sync then takes in. Where modes aren't observed, the entry's mode
455
+ // is the store's own, and the evidence records none.
456
+ return p.expect.kind === 'absent'
457
+ ? { kind: 'absent' }
458
+ : { kind: 'content', hash: p.expect.hash, size: s!.size, ...(s!.mode !== undefined ? { mode: s!.mode } : {}) };
323
459
  }
324
460
  return p.prior ?? 'forget';
325
461
  }
326
462
 
463
+ /**
464
+ * What's in the way, and the way out, when disk's shape keeps a push from a
465
+ * path: no push writes a file over a directory or unlinks one, unlinks
466
+ * anything but a regular file or a followed symlink to one, or writes below
467
+ * a file or a symlink the walk won't take (`writeContained`,
468
+ * `unlinkContained`), forced or not. Null for any other reason, which is
469
+ * passed on as it is.
470
+ */
471
+ function inTheWay(reason: string | undefined, deletion: boolean): string | null {
472
+ // Observation's words for what a deletion never unlinks, which the unlink
473
+ // refuses in the same words.
474
+ if (deletion && (reason === 'not a regular file' || reason === 'a symlink to a directory, not walked')) {
475
+ const what = reason === 'not a regular file'
476
+ ? 'a pipe, socket or device at this path, or a symlink to one'
477
+ : 'a symlink to a directory at this path';
478
+ return `disk has ${what}, and no push unlinks it, forced or not — ask whoever runs this host if it should go`;
479
+ }
480
+ if (reason === 'a directory') {
481
+ return deletion
482
+ ? 'disk has a directory at this path, and no push unlinks one, forced or not — if it should go, move or remove it on disk'
483
+ : 'disk has a directory at this path, and no push writes a file over one, forced or not — ' +
484
+ 'move or remove that directory on disk and materialize again, or write the file under another path';
485
+ }
486
+ // A deletion below a file finds nothing there to unlink, so only a write
487
+ // meets this one.
488
+ if (reason === 'a parent path is not a directory') {
489
+ return 'disk has a file or a symlink where this path needs a directory, and no push writes below one, forced or not — ' +
490
+ 'move or remove it on disk and materialize again, or write the file under another path';
491
+ }
492
+ return null;
493
+ }
494
+
495
+ /**
496
+ * The forced way out the refusal of a disk copy that couldn't be verified
497
+ * names, if any. A forced push skips the verification, so it gets past the
498
+ * note only where its effect can go on. A file the module can't read (EACCES)
499
+ * is overwritten, or unlinked, where disk lets the workspace write it, or
500
+ * unlink it, which a read's EACCES doesn't show: force is named on that
501
+ * condition. No other note names force: a forced push stops at some (a
502
+ * symlink the mount doesn't follow, what isn't a regular file, a parent it
503
+ * can't use); another writer still at the file ("the file changed while it
504
+ * was read") is answered by materializing again; and the rest aren't known
505
+ * to pass.
506
+ */
507
+ function forcedWay(note: string | undefined, deletion: boolean, forced: string): string {
508
+ if (note === 'cannot open (EACCES)') return `, or, if disk lets the workspace ${deletion ? 'unlink' : 'write'} the file, ${forced}`;
509
+ return '';
510
+ }
511
+
327
512
  /**
328
513
  * The synchronous precondition of every workspace mutation: evidence owed a
329
514
  * barrier is made durable first, and an adoption left pending on the
@@ -345,12 +530,30 @@ export function settleAdoptionBeforeMutation(
345
530
  const p = agreement.get(mount.name, path);
346
531
  if (p?.kind !== 'pending' || p.effect !== 'adopt' || p.branchId !== store.currentBranch().id) return;
347
532
  const entry = store.treeGet(mount.treeStateId, path);
348
- const settled = settleAdoption(p, entry ? { hash: entry.blobHash, size: entry.size } : null);
533
+ const settled = settleAdoption(p, entry ? { hash: entry.blobHash, size: entry.size, ...(entryMode(entry.mode) !== undefined ? { mode: entryMode(entry.mode) } : {}) } : null);
349
534
  if (settled === 'forget') agreement.forget(mount.name, path);
350
535
  else agreement.set(mount.name, path, settled, { afterCommittedState: true });
351
536
  agreement.barrier();
352
537
  }
353
538
 
539
+ /**
540
+ * What a push of ours that stopped partway left on disk, read by its own
541
+ * modes (Pending's `modes`): the new bytes at `from & to` or `to` were meant
542
+ * to have `to`; the old bytes at `from & to`, narrower than `from`, still
543
+ * have `from` as theirs. New bytes at `from` are a push's only where
544
+ * `from & to` is `from`: a push that narrows does so before its bytes, so
545
+ * otherwise they were given `from` since. Undefined where disk shows
546
+ * anything else: no push of ours, or a mode set since it stopped.
547
+ */
548
+ function pushedMode(p: Pending, dc: Candidate, d: DiskFact): number | undefined {
549
+ const m = p.modes;
550
+ if (p.effect !== 'disk' || m === undefined || d.kind !== 'file' || d.mode === undefined) return undefined;
551
+ const narrowed = m.from & m.to;
552
+ if (sameCandidate(dc, p.expect)) return d.mode === narrowed || d.mode === m.to ? m.to : undefined;
553
+ const prior = p.prior ? candidateOf(p.prior) : null;
554
+ return prior && sameCandidate(dc, prior) && narrowed !== m.from && d.mode === narrowed ? m.from : undefined;
555
+ }
556
+
354
557
  /**
355
558
  * Resolve a pending intent against what was observed. Returns the evidence
356
559
  * the rule should use, and whether that resolution should be recorded.
@@ -358,20 +561,34 @@ export function settleAdoptionBeforeMutation(
358
561
  function resolvePending(
359
562
  p: Pending,
360
563
  dc: Candidate,
361
- s: { hash: string; size: number } | null,
564
+ s: StoreSide | null,
362
565
  d: DiskFact,
363
566
  branchId: string,
364
- ): { p: Physical | undefined; record?: Agreed | Interrupted | 'forget'; uncaptured?: true; unconfirmed?: true } {
567
+ ): { p: Physical | undefined; record?: Agreed | Interrupted | 'forget'; uncaptured?: true; unconfirmed?: true; ownMode?: true } {
365
568
  const prior = p.prior;
366
569
  const priorCandidate = prior ? candidateOf(prior) : null;
367
570
  if (p.effect === 'disk') {
368
571
  if (sameCandidate(dc, p.expect)) {
369
572
  // Our own effect shows on disk, but nothing proves it durable: decided
370
573
  // on, never recorded. Only a push whose barriers succeed completes it.
574
+ // Where disk's mode is one of the push's own, the push that completes
575
+ // it gives the store's mode there.
371
576
  const shown = p.expect.kind === 'absent' ? ({ kind: 'absent' } as Agreed) : agreedFromDisk(d);
372
- return { p: shown, unconfirmed: true };
577
+ return { p: shown, unconfirmed: true, ...(pushedMode(p, dc, d) !== undefined ? { ownMode: true as const } : {}) };
578
+ }
579
+ if (priorCandidate && sameCandidate(dc, priorCandidate)) {
580
+ // The push narrowed the mode and stopped before its bytes: disk's mode
581
+ // is the push's, so the rule decides from it, and the next push writes
582
+ // the bytes at it and then widens to the store's. The old bytes are
583
+ // never given the store's mode. Nothing is recorded: the intent, with
584
+ // the modes that map disk's narrowed one back to the old bytes' own,
585
+ // stays, carried by a push that goes on with it (pushPaths), until a
586
+ // push completes it or a sync adopts disk's copy.
587
+ if (prior!.kind === 'content' && d.kind === 'file' && pushedMode(p, dc, d) !== undefined) {
588
+ return { p: { ...prior!, mode: d.mode } };
589
+ }
590
+ return { p: prior!, record: prior! };
373
591
  }
374
- if (priorCandidate && sameCandidate(dc, priorCandidate)) return { p: prior!, record: prior! };
375
592
  // A write whose prior was unknown left no file: it never created one (a
376
593
  // partial write leaves a file behind), so P is as unknown as before.
377
594
  if (!prior && dc.kind === 'absent' && p.expect.kind === 'content') return { p: undefined, record: 'forget' };
@@ -402,13 +619,17 @@ function resolvePending(
402
619
  * An explicit path sync: the store takes disk's state, whatever the evidence
403
620
  * — a pending push's included. That chooses disk; it doesn't certify the push.
404
621
  */
405
- function adoptDisk(d: Extract<DiskFact, { kind: 'absent' | 'file' }>, s: { hash: string; size: number } | null): Verdict {
622
+ function adoptDisk(d: Extract<DiskFact, { kind: 'absent' | 'file' }>, s: StoreSide | null): Verdict {
406
623
  if (d.kind === 'absent') {
407
624
  return { state: 'synced', ...(s ? { adopt: 'remove' as const, op: 'deleted' as const } : {}), p: { kind: 'absent' }, intent: {} };
408
625
  }
409
626
  // Disk agreeing with the store is disk's state already, whatever the file
410
- // is: a binary or oversize file the workspace holds stays held.
411
- if (s && s.hash === d.hash) return { state: 'synced', p: agreedFromDisk(d), intent: {} };
627
+ // is: a binary or oversize file the workspace holds stays held. Its mode
628
+ // is disk's too.
629
+ if (s && s.hash === d.hash) {
630
+ const mode = d.mode === undefined ? undefined : s.mode === undefined ? 'fill' as const : s.mode !== d.mode ? 'adopt' as const : undefined;
631
+ return { state: 'synced', p: agreedFromDisk(d), intent: {}, ...(mode ? { mode } : {}), ...(mode === 'adopt' ? { op: 'modified' as const } : {}) };
632
+ }
412
633
  if (d.ingestible) return { state: 'synced', adopt: 'ingest', op: s ? 'modified' : 'created', p: agreedFromDisk(d), intent: {} };
413
634
  return { state: 'disk-only', ...(s ? { adopt: 'drop' as const, op: 'deleted' as const } : {}), p: 'forget', intent: {} };
414
635
  }
@@ -416,7 +637,7 @@ function adoptDisk(d: Extract<DiskFact, { kind: 'absent' | 'file' }>, s: { hash:
416
637
  /** The rule (see module doc). Pure. */
417
638
  function decide(
418
639
  d: DiskFact,
419
- s: { hash: string; size: number } | null,
640
+ s: StoreSide | null,
420
641
  pIn: Physical | undefined,
421
642
  bi: BranchIntent | null,
422
643
  branchId: string,
@@ -445,12 +666,20 @@ function decide(
445
666
  if (adopt) {
446
667
  // What a path sync gives up is exactly what a sync without a path would
447
668
  // have kept of the workspace's own (a draft, a deletion, a conflict's
448
- // workspace side), and the verdict says which.
449
- const taken = adoptDisk(d, s);
669
+ // workspace side), and the verdict says which. Where a push of ours
670
+ // stopped partway, disk's mode is the push's: the store takes the one
671
+ // the push meant those bytes to have, and the evidence keeps disk's, so
672
+ // a later push moves disk there.
673
+ const meant = pIn?.kind === 'pending' ? pushedMode(pIn, dc, d) : undefined;
674
+ const taken = adoptDisk(meant === undefined ? d : { ...d, mode: meant } as typeof d, s);
675
+ if (meant !== undefined) {
676
+ if (taken.p !== undefined && taken.p !== 'forget' && taken.p.kind === 'content') taken.p = agreedFromDisk(d);
677
+ if (taken.adopt === 'ingest' || taken.mode !== undefined) taken.diskMode = meant;
678
+ }
450
679
  // A give-up is an adoption, or the intent it clears: a deletion or a
451
680
  // conflict given up for a disk copy the workspace can't hold changes no
452
681
  // tree entry, and is listed all the same.
453
- if (taken.adopt || (bi && taken.intent !== undefined)) {
682
+ if (taken.adopt || taken.mode === 'adopt' || (bi && taken.intent !== undefined)) {
454
683
  const kept = decide(d, s, pIn, bi, branchId, false);
455
684
  if (WORKSPACE_KEPT.has(kept.state)) taken.discards = kept.state;
456
685
  if (taken.adopt && kept.prior) taken.prior = kept.prior;
@@ -462,6 +691,9 @@ function decide(
462
691
  // verdict below records newer evidence.
463
692
  let p = pIn;
464
693
  let resolution: Agreed | Interrupted | 'forget' | undefined;
694
+ // Evidence a resolution decides from without recording it: a stopped
695
+ // push's narrowing, whose intent stays (resolvePending).
696
+ let unrecorded: Agreed | undefined;
465
697
  let uncaptured = false;
466
698
  if (p?.kind === 'pending') {
467
699
  const resolved = resolvePending(p, dc, s, d, branchId);
@@ -470,13 +702,18 @@ function decide(
470
702
  // follows from that, and nothing is recorded — no completion, no
471
703
  // agreement, no branch-intent change — until a push confirms it.
472
704
  const state: EntryState = sameCandidate(dc, sc) ? 'synced' : bi?.conflict ? 'conflict' : keepStoreSide(s !== null, bi);
473
- return { state, note: UNCONFIRMED_NOTE, unconfirmed: true };
705
+ return {
706
+ state, note: UNCONFIRMED_NOTE, unconfirmed: true,
707
+ ...(resolved.ownMode ? { ownMode: true as const } : {}),
708
+ };
474
709
  }
475
710
  p = resolved.p;
476
711
  resolution = resolved.record;
712
+ if (resolution === undefined && p !== undefined && (p.kind === 'content' || p.kind === 'absent')) unrecorded = p;
477
713
  uncaptured = resolved.uncaptured === true;
478
714
  }
479
- const verdict = (v: Verdict): Verdict => (resolution && v.p === undefined ? { ...v, p: resolution } : v);
715
+ const verdict = (v: Verdict): Verdict => (resolution && v.p === undefined ? { ...v, p: resolution }
716
+ : unrecorded && v.p === undefined && v.prior === undefined ? { ...v, prior: unrecorded } : v);
480
717
 
481
718
  // Agreement settles everything, a recorded conflict included.
482
719
  if (sameCandidate(dc, sc)) {
@@ -492,11 +729,23 @@ function decide(
492
729
  const recorded = p?.kind === 'content' ? p : null;
493
730
  // Re-record only when P says something else, or a fresh fingerprint can
494
731
  // replace one that no longer vouches. A fresh one exists only where it's
495
- // trusted, so both are judged as trusted here.
496
- const stale = !recorded || recorded.hash !== agreed.hash || recorded.size !== agreed.size ||
732
+ // trusted, so both are judged as trusted here. Evidence a resolution left
733
+ // unrecorded, a stopped push's narrowing, is stale too: the agreement
734
+ // replaces that push's intent, and the mode rule gives the store's mode.
735
+ const stale = unrecorded !== undefined || !recorded || recorded.hash !== agreed.hash || recorded.size !== agreed.size || recorded.mode !== agreed.mode ||
497
736
  (agreed.fp !== undefined && fingerprintVouches(agreed.fp, agreed.fp, true) &&
498
737
  !fingerprintVouches(recorded.fp, agreed.fp, true));
499
- return verdict({ state: 'synced', ...(stale ? { p: agreed } : {}), ...settles });
738
+ const mode = agreed.mode === undefined ? undefined : modeAction(agreed.mode, s!.mode, recorded?.mode);
739
+ if (mode === 'push') {
740
+ return verdict({
741
+ state: 'workspace-draft',
742
+ mode,
743
+ note: `the workspace's mode (${octal(s!.mode!)}) differs from disk's (${octal(agreed.mode!)}): materialize to apply it`,
744
+ ...(stale ? { p: agreed } : {}),
745
+ ...settles,
746
+ });
747
+ }
748
+ return verdict({ state: 'synced', ...(mode ? { mode } : {}), ...(mode === 'adopt' ? { op: 'modified' as const } : {}), ...(stale ? { p: agreed } : {}), ...settles });
500
749
  }
501
750
 
502
751
  // A recorded conflict stays until it is resolved explicitly or disk and store converge.
@@ -618,10 +867,16 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
618
867
  for (const [p] of agreement.paths(mount.name, scope.dir)) if (inScope(p, scope)) candidates.add(p);
619
868
  for (const [p] of mount.intents.list(scope.dir)) if (inScope(p, scope)) candidates.add(p);
620
869
  if (rootUnavailable === null) {
621
- walk = await walkScope(mount.view, mount.rootReal, scope.dir, scope.recursive, opts.cap ?? WALK_CAP, { named: opts.named });
870
+ const resumeAfter = opts.resume ? agreement.walkResumesAfter(mount.name, scope.dir) : undefined;
871
+ walk = await walkScope(mount.view, mount.rootReal, scope.dir, scope.recursive, opts.cap ?? mount.walkCap, {
872
+ named: opts.named,
873
+ ...(resumeAfter !== undefined ? { resumeAfter } : {}),
874
+ });
622
875
  for (const f of walk.files) if (inScope(f, scope)) candidates.add(f);
623
- // Ignored names are listed only when something tracks them (above).
624
- for (const [p, why] of walk.others) if (why !== 'ignored by the mount' && inScope(p, scope)) candidates.add(p);
876
+ // The other entries it took (a link, a socket…). Ignored names, and the
877
+ // entries the cap left, are listed only when something tracks them
878
+ // (above).
879
+ for (const p of walk.othersTaken) if (inScope(p, scope)) candidates.add(p);
625
880
  for (const d of walk.dirs) if (inScope(d, { kind: 'dir', dir: scope.dir, recursive: false })) gathered.dirs.push(d);
626
881
  }
627
882
  }
@@ -633,18 +888,54 @@ async function gather(store: JsStore, agreement: DiskAgreement, mount: MountRunt
633
888
  gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable });
634
889
  continue;
635
890
  }
891
+ // What the mount passes over by design stays unobserved by a pass that
892
+ // names no path, however it reaches it, so no listing's absence proves a
893
+ // copy there gone. In another mount's directory, no pass of this mount
894
+ // observes anything, named or not.
895
+ // Beneath a directory a by-design rule covers, a pass that isn't named
896
+ // observes nothing. A path a rule covers by its own name is observed as a
897
+ // walk observes it: passed over where the walk lists it, and absent where
898
+ // the walk lists its directory without it; a pass that reaches it by its
899
+ // path alone, as a listing, glob or grep naming it does, passes over it.
900
+ // A pass that names its scope, as a sync naming a path and a file tool's
901
+ // pass on the path it acts on do, sets aside only what covers the scope,
902
+ // at it or above it, as its walk does (#276): a directory passed over by
903
+ // design beneath a named directory stays passed over, and so does what
904
+ // lies beneath it.
905
+ let byDesign: string | undefined;
906
+ if (inOtherMount(path, mount.view.others)) {
907
+ byDesign = OTHER_MOUNT;
908
+ } else if (!opts.named) {
909
+ const slash = path.lastIndexOf('/');
910
+ const above = slash < 0 ? undefined : passedOverAt(path.slice(0, slash), mount.view.ignore, mount.view.others);
911
+ const own = above === undefined && !walk ? passedOverAt(path, mount.view.ignore, mount.view.others) : undefined;
912
+ byDesign = above ?? own;
913
+ } else if (scope.kind === 'dir') {
914
+ byDesign = passedOverBetween(scope.dir, path, ignoreBeneath(scope.dir, mount.view.ignore), mount.view.others);
915
+ }
916
+ if (byDesign !== undefined) {
917
+ gathered.facts.set(path, { kind: 'unobserved', reason: byDesign });
918
+ continue;
919
+ }
636
920
  if (walk && provenAbsent(walk, path)) {
637
921
  gathered.facts.set(path, { kind: 'absent' });
638
922
  continue;
639
923
  }
640
- if (walk && !walk.files.has(path) && !walk.others.has(path)) {
641
- // Beneath a region the walk didn't reach: unproven either way.
642
- const region = walk.incomplete.find((r) => r.path === '' || path === r.path || path.startsWith(r.path + '/'));
643
- gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
924
+ if (walk?.others.get(path) === IGNORED) {
925
+ gathered.facts.set(path, { kind: 'unobserved', reason: IGNORED });
644
926
  continue;
645
927
  }
646
- if (walk?.others.get(path) === 'ignored by the mount') {
647
- gathered.facts.set(path, { kind: 'unobserved', reason: 'ignored by the mount' });
928
+ if (walk && !walk.files.has(path) && !walk.othersTaken.has(path)) {
929
+ // Not taken by the walk, or beneath a region it didn't reach: unproven
930
+ // either way. The nearest region holding it says why.
931
+ let region: Walk['incomplete'][number] | undefined;
932
+ for (const r of walk.incomplete) {
933
+ if (r.path !== '' && path !== r.path && !path.startsWith(r.path + '/')) continue;
934
+ // A link's own region is about what lies beneath it, not the link.
935
+ if (r.kind === 'symlink' && r.path === path) continue;
936
+ if (region === undefined || r.path.length > region.path.length) region = r;
937
+ }
938
+ gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
648
939
  continue;
649
940
  }
650
941
  const p = agreement.get(mount.name, path);
@@ -719,11 +1010,10 @@ export async function reconcilePass(
719
1010
  // Decide and apply — synchronously, so nothing interleaves.
720
1011
  const branchId = store.currentBranch().id;
721
1012
  result.branchId = branchId;
722
- type Planned = { path: string; verdict: Verdict; d: DiskFact; s: { hash: string; size: number } | null; p: Physical | undefined; bi: BranchIntent | null };
1013
+ type Planned = { path: string; verdict: Verdict; d: DiskFact; s: StoreSide | null; p: Physical | undefined; bi: BranchIntent | null };
723
1014
  const planned: Planned[] = [];
724
1015
  for (const [path, d] of facts) {
725
- const entry = store.treeGet(mount.treeStateId, path);
726
- const s = entry ? { hash: entry.blobHash, size: entry.size } : null;
1016
+ const s = storeSide(store.treeGet(mount.treeStateId, path));
727
1017
  const p = agreement.get(mount.name, path);
728
1018
  const bi = mount.intents.get(path);
729
1019
  const verdict = decide(d, s, p, bi, branchId, opts.adopt?.(path) === true);
@@ -758,11 +1048,17 @@ export async function reconcilePass(
758
1048
  const v = x.verdict;
759
1049
  if (v.adopt === 'ingest' && x.d.kind === 'file' && x.d.ingestible) {
760
1050
  const blobHash = store.storeBlob(x.d.ingestible, 'text/plain');
761
- store.treeSet(mount.treeStateId, x.path, { blobHash, size: x.d.ingestible.byteLength, mode: 0o644 });
1051
+ store.treeSet(mount.treeStateId, x.path, { blobHash, size: x.d.ingestible.byteLength, ...treeModeOf(v.diskMode ?? x.d.mode, x.s) });
762
1052
  committed = true;
763
1053
  } else if ((v.adopt === 'remove' || v.adopt === 'drop') && x.s) {
764
1054
  store.treeRemove(mount.treeStateId, x.path);
765
1055
  committed = true;
1056
+ } else if ((v.mode === 'fill' || v.mode === 'adopt') && x.d.kind === 'file' && x.s) {
1057
+ // The same bytes, with disk's mode. A fill records a mode where the
1058
+ // entry had none, which is no change: an undo or a branch whose entry
1059
+ // still has none agrees with any mode, so neither reads it as one.
1060
+ store.treeSet(mount.treeStateId, x.path, { blobHash: x.s.hash, size: x.s.size, ...treeModeOf(v.diskMode ?? x.d.mode, x.s) });
1061
+ committed = true;
766
1062
  }
767
1063
  if (v.conflict) {
768
1064
  const record: ConflictRecord = { kind: v.conflict, at: Date.now(), disk: counterpartOf(store, x.d) };
@@ -773,8 +1069,9 @@ export async function reconcilePass(
773
1069
  mount.intents.put(x.path, v.intent);
774
1070
  committed = true;
775
1071
  }
776
- if (v.op && v.adopt) result.ops.push({ path: x.path, op: v.op });
777
- if (v.discards) result.discarded.push({ path: x.path, was: v.discards, ...(v.op && v.adopt ? { op: v.op } : {}) });
1072
+ const changed = v.adopt !== undefined || v.mode === 'adopt';
1073
+ if (v.op && changed) result.ops.push({ path: x.path, op: v.op, ...(v.adopt === undefined ? { modeOnly: true as const } : {}) });
1074
+ if (v.discards) result.discarded.push({ path: x.path, was: v.discards, ...(v.op && changed ? { op: v.op } : {}) });
778
1075
  }
779
1076
 
780
1077
  // 4. Completions and agreements, after the commits they assert are synced —
@@ -793,6 +1090,18 @@ export async function reconcilePass(
793
1090
  agreement.set(mount.name, x.path, next, { afterCommittedState: first });
794
1091
  first = false;
795
1092
  }
1093
+ // Where the walk stopped, once what it observed is decided: the next pass
1094
+ // of this scope resumes after it, or starts afresh once this one covered
1095
+ // the scope. A walk that couldn't list its scope's own directory covered
1096
+ // none of it, and leaves the place as it was. Unlike evidence, a place
1097
+ // binds no root: it only says where the next walk starts, and that walk
1098
+ // takes its turn after it in whatever directory is the root by then.
1099
+ let resumeAfter: string | null | undefined;
1100
+ if (opts.resume && walk && scope.kind === 'dir' && scope.recursive && walk.complete.has(scope.dir)) {
1101
+ resumeAfter = walk.stoppedAfter ?? null;
1102
+ agreement.recordWalk(mount.name, scope.dir, resumeAfter);
1103
+ }
1104
+
796
1105
  // 5. Durable before returning. An agreement advance (D = S) has no pending
797
1106
  // intent to recover from, so losing its append would leave an older P that
798
1107
  // another branch would misread as a disk edit.
@@ -816,15 +1125,39 @@ export async function reconcilePass(
816
1125
  }
817
1126
  if (walk) {
818
1127
  const tracked = [...result.reports.values()].filter((r) => r.state === 'unverified').map((r) => r.path);
1128
+ if (typeof resumeAfter === 'string' && scope.kind === 'dir') {
1129
+ // A watched mount's catch-up scan takes turns with its whole-mount
1130
+ // syncs; nothing else moves a place.
1131
+ const next = scope.dir !== ''
1132
+ ? 'the next sync of this directory'
1133
+ : mount.catchUpScan
1134
+ ? 'the next sync of the mount, or its catch-up scan if that comes first,'
1135
+ : 'the next sync of the mount';
1136
+ result.incomplete.push({
1137
+ path: scope.dir,
1138
+ reason: `the walk cap (${opts.cap ?? mount.walkCap}) was reached: ${next} resumes after ${mount.name}/${resumeAfter}`,
1139
+ });
1140
+ }
819
1141
  for (const region of walk.incomplete) {
820
- const hides = tracked.some((p) => p === region.path || p.startsWith(region.path + '/'));
821
- if (region.kind === 'error' || region.kind === 'cap' || hides) result.incomplete.push({ path: region.path, reason: region.reason });
822
- // An ignored name that hides nothing tracked is reported nowhere else:
1142
+ // A link's own region hides only what lies beneath it: the link itself
1143
+ // is reported by path, with its own reason.
1144
+ const hides = tracked.some((p) => (region.kind !== 'symlink' && p === region.path) || p.startsWith(region.path + '/'));
1145
+ // A scope passed over whole is the answer to the pass that asked for
1146
+ // it, so it's always named. Another mount's directory met beneath the
1147
+ // scope is passed over by design even where this mount's tree holds
1148
+ // copies there: no tool of this mount syncs, pushes or clears them
1149
+ // (listings show them, naming their holder), so naming the directory
1150
+ // as unreached would never clear.
1151
+ const wholeScope = scope.kind === 'dir' && scope.dir !== '' && region.path === scope.dir;
1152
+ const unreached = hides && region.reason !== OTHER_MOUNT;
1153
+ if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope) result.incomplete.push({ path: region.path, reason: region.reason });
1154
+ // A name passed over by design that hides nothing tracked is reported nowhere else:
823
1155
  // counted, so a sync that says 0 also says what it didn't look at
824
- // (agent-framework #276). A symlink region isn't counted: the link
825
- // itself is a candidate the pass reports by path, taken through the
1156
+ // (agent-framework #276). A symlink region isn't counted: a link the
1157
+ // walk took is a candidate the pass reports by path, taken through the
826
1158
  // link when it's a file on a mount that follows symlinks, and
827
- // otherwise listed under `skipped`.
1159
+ // otherwise listed under `skipped`; a tracked link the cap left is
1160
+ // unverified for the cap.
828
1161
  else if (region.kind === 'ignored') result.passedOver.set(region.reason, (result.passedOver.get(region.reason) ?? 0) + 1);
829
1162
  }
830
1163
  }
@@ -839,7 +1172,9 @@ export interface PushOptions {
839
1172
  /**
840
1173
  * Overwrite (or unlink) a disk copy in conflict, or one that can't be
841
1174
  * verified. A disk copy nothing has evidence about is overwritten only when
842
- * `named` too: a bare push can't tell it from a newer copy.
1175
+ * `named` too: a bare push can't tell it from a newer copy. Forced or not,
1176
+ * nothing but a regular file, or a followed symlink to one, is written over
1177
+ * or unlinked (effects.ts).
843
1178
  */
844
1179
  force?: boolean;
845
1180
  /**
@@ -867,6 +1202,11 @@ export interface PushResult {
867
1202
  deleted: string[];
868
1203
  /** Refused paths, each with its reason. */
869
1204
  skipped: Array<{ path: string; reason: string }>;
1205
+ /**
1206
+ * Written paths whose mode stayed as disk had it, each with why: their
1207
+ * bytes reached disk, and the workspace's mode didn't.
1208
+ */
1209
+ modesKept: Array<{ path: string; reason: string }>;
870
1210
  /** Workspace deletions left on disk because applyDeletions wasn't given. */
871
1211
  pendingDeletions: string[];
872
1212
  /** The branch and sequence the push planned on: what disk now reflects.
@@ -877,11 +1217,16 @@ export interface PushResult {
877
1217
  sequence?: number;
878
1218
  }
879
1219
 
1220
+ /** Why a push skips a path in another mount's directory nested in its own. */
1221
+ export const NOT_PUSHED_IN_OTHER_MOUNT = "it lies in another mount's directory, which no push of this mount writes";
1222
+
880
1223
  /**
881
1224
  * Make disk hold the store's version of each path, under the same rule:
882
1225
  * drafts are written, agreement is left alone, conflicts and unverifiable
883
1226
  * disk copies are refused unless `force`, and a workspace deletion reaches
884
1227
  * disk only with `applyDeletions` (with `force` too when disk changed since).
1228
+ * Forced or not, nothing but a regular file, or a followed symlink to one, is
1229
+ * written over or unlinked (effects.ts).
885
1230
  * A path nothing has evidence about is written only when `named`.
886
1231
  * A path whose earlier push shows on disk unconfirmed is pushed again, so its
887
1232
  * completion rests on barriers that succeeded.
@@ -905,7 +1250,7 @@ export async function pushPaths(
905
1250
  ): Promise<PushResult> {
906
1251
  agreement.ensureReconciled();
907
1252
  if (agreement.needsBarrier) agreement.barrier(); // as a pass does: nothing decided from unsynced evidence
908
- const result: PushResult = { written: [], unchanged: [], deleted: [], skipped: [], pendingDeletions: [] };
1253
+ const result: PushResult = { written: [], unchanged: [], deleted: [], skipped: [], modesKept: [], pendingDeletions: [] };
909
1254
  if (mount.readOnly) return result;
910
1255
  // A root that isn't available takes no writes, forced or not: restoring the
911
1256
  // store's files into an empty mountpoint would fill the disk the drive was
@@ -915,6 +1260,18 @@ export async function pushPaths(
915
1260
  for (const path of paths) result.skipped.push({ path, reason: root.reason });
916
1261
  return result;
917
1262
  }
1263
+ // Another mount's directory nested in this one is that mount's: no push
1264
+ // of this mount writes or deletes a path in it, forced or not. (On a mount
1265
+ // that follows links, a link of this mount's that leads there is read as
1266
+ // this mount's own path; on one that doesn't, the link is refused.)
1267
+ if (mount.view.others.length > 0) {
1268
+ const own: string[] = [];
1269
+ for (const path of paths) {
1270
+ if (inOtherMount(path, mount.view.others)) result.skipped.push({ path, reason: NOT_PUSHED_IN_OTHER_MOUNT });
1271
+ else own.push(path);
1272
+ }
1273
+ paths = own;
1274
+ }
918
1275
 
919
1276
  type Plan = {
920
1277
  path: string;
@@ -927,7 +1284,39 @@ export async function pushPaths(
927
1284
  prior: Agreed | null;
928
1285
  /** What the effect may find at the path. */
929
1286
  expect: Expect;
930
- } & ({ kind: 'write'; blob: Buffer; hash: string } | { kind: 'unlink' });
1287
+ } & (
1288
+ | {
1289
+ kind: 'write';
1290
+ blob: Buffer;
1291
+ hash: string;
1292
+ /** Permission bits the write gives the file, if any. */
1293
+ apply?: number;
1294
+ /** The mode disk had when it was checked, which `apply` replaces only if disk still has it. */
1295
+ from?: number;
1296
+ /** The store's mode, and the evidence's before the push. */
1297
+ storeMode?: number;
1298
+ priorMode?: number;
1299
+ /** The mode disk had when it was checked, forced or not: where the push's modes start. */
1300
+ seenMode?: number;
1301
+ /** `apply` is forced over a mode disk changed since it last agreed, or one nothing records. */
1302
+ overDisk?: true;
1303
+ /**
1304
+ * For a push that re-plans over our own narrowing push stopped before
1305
+ * its bytes, the modes its intent carries: from the old bytes' own
1306
+ * mode, the stopped push's `from`, to the store's mode now, or where
1307
+ * the store records none, the mode the stopped push narrowed to.
1308
+ */
1309
+ ownModes?: { from: number; to: number };
1310
+ }
1311
+ | { kind: 'unlink' }
1312
+ /**
1313
+ * The store's mode, applied to bytes disk and the store share; `from`,
1314
+ * when given, the mode disk must still have. `overDisk`: a forced chmod
1315
+ * over a mode disk changed since it last agreed, or one nothing records
1316
+ * which side changed, rather than a push of the store's own change.
1317
+ */
1318
+ | { kind: 'chmod'; mode: number; hash: string; size: number; from?: number; overDisk?: true }
1319
+ );
931
1320
  const plans: Plan[] = [];
932
1321
 
933
1322
  // Observe disk.
@@ -958,11 +1347,20 @@ export async function pushPaths(
958
1347
  result.sequence = store.currentSequence();
959
1348
  let recorded = false;
960
1349
  for (const [path, d] of facts) {
961
- const entry = store.treeGet(mount.treeStateId, path);
962
- const s = entry ? { hash: entry.blobHash, size: entry.size } : null;
1350
+ const s = storeSide(store.treeGet(mount.treeStateId, path));
963
1351
  const p = agreement.get(mount.name, path);
964
1352
  const bi = mount.intents.get(path);
965
1353
  const v = decide(d, s, p, bi, branchId, false);
1354
+ // The ways out a refusal names, as materialize takes them: a deletion
1355
+ // reaches disk only with applyDeletions.
1356
+ const again = s ? 'materialize again' : 'materialize again with applyDeletions';
1357
+ // Where disk has deleted a file the workspace holds, a sync takes the
1358
+ // deletion, which removes the workspace's copy, and a forced push writes
1359
+ // the workspace's copy back: there is no disk copy to adopt or overwrite.
1360
+ const gone = s !== null && d.kind === 'absent';
1361
+ const forced = !s ? 'materialize it with force and applyDeletions to delete it'
1362
+ : gone ? "materialize it with force to write the workspace's copy back" : 'materialize it with force to overwrite it';
1363
+ const takeDisk = gone ? "sync this path to take disk's deletion, which removes the workspace's copy" : 'sync this path to adopt the disk version';
966
1364
  // What the effect may find: what this observation saw, unless forced.
967
1365
  const expect: Expect = opts.force || d.kind === 'unobserved' || d.kind === 'unhashed'
968
1366
  ? { kind: 'any' }
@@ -976,14 +1374,47 @@ export async function pushPaths(
976
1374
  result.skipped.push({
977
1375
  path,
978
1376
  reason: 'stale copy: disk changed since it last agreed with the workspace, which did not — ' +
979
- 'sync this path to adopt the disk version, or pass force to overwrite it',
1377
+ `${takeDisk}, or ${forced}`,
980
1378
  });
981
1379
  continue;
982
1380
  }
983
1381
  const priorP = p && (p.kind === 'absent' || p.kind === 'content') ? p : null;
984
1382
  const blob = store.getBlob(s.hash);
985
- if (blob) plans.push({ path, kind: 'write', blob, hash: s.hash, prior: priorP, before: agreement.get(mount.name, path), expect });
986
- else result.skipped.push({ path, reason: 'the workspace copy is missing from the store' });
1383
+ const forcedMode = mount.modesObserved && s.mode !== undefined && s.mode !== (d.kind === 'file' ? d.mode : undefined) ? s.mode : undefined;
1384
+ const agreedMode = priorP?.kind === 'content' ? priorP.mode : undefined;
1385
+ if (blob) {
1386
+ plans.push({
1387
+ path, kind: 'write', blob, hash: s.hash, prior: priorP, before: agreement.get(mount.name, path), expect,
1388
+ ...(forcedMode !== undefined ? { apply: forcedMode } : {}),
1389
+ ...(forcedMode !== undefined && d.kind === 'file' && agreedMode !== d.mode ? { overDisk: true as const } : {}),
1390
+ ...(s.mode !== undefined ? { storeMode: s.mode } : {}),
1391
+ // Refused over disk's mode, the completion records what an unforced
1392
+ // write would: the mode disk last agreed with.
1393
+ ...(agreedMode !== undefined ? { priorMode: agreedMode } : {}),
1394
+ ...(d.kind === 'file' && d.mode !== undefined ? { seenMode: d.mode } : {}),
1395
+ });
1396
+ } else result.skipped.push({ path, reason: 'the workspace copy is missing from the store' });
1397
+ continue;
1398
+ }
1399
+ if (v.mode === 'adopt' && s && d.kind === 'file') {
1400
+ // Disk's mode changed since it last agreed, on the bytes the store
1401
+ // holds, and the store's didn't. A sync takes it in. Recording disk's
1402
+ // mode here would read as the store's change, which the next push
1403
+ // would revert; forced, disk takes the store's mode, as forced bytes do.
1404
+ if (!opts.force) {
1405
+ // modeAction adopts where disk changed the mode since they agreed, and
1406
+ // where nothing records a mode they agreed on.
1407
+ const agreedMode = p?.kind === 'content' ? p.mode : undefined;
1408
+ result.skipped.push({
1409
+ path,
1410
+ reason: (agreedMode === undefined
1411
+ ? `disk's mode (${octal(d.mode!)}) differs from the workspace's (${octal(s.mode!)}), and nothing records which side changed it`
1412
+ : `disk's mode (${octal(d.mode!)}) changed since it last agreed with the workspace's (${octal(s.mode!)})`) +
1413
+ ' — sync this path to take disk\'s mode in, or materialize this path with force to apply the workspace\'s',
1414
+ });
1415
+ continue;
1416
+ }
1417
+ plans.push({ path, kind: 'chmod', mode: s.mode!, hash: s.hash, size: s.size, prior: null, before: p, expect, overDisk: true });
987
1418
  continue;
988
1419
  }
989
1420
  // Whatever the verdict learned about disk (an agreement, a resolved
@@ -996,8 +1427,22 @@ export async function pushPaths(
996
1427
  // The prior a new intent carries is the last confirmed evidence: for a
997
1428
  // path whose earlier push is unconfirmed, that push's own prior, never
998
1429
  // the bytes it wrote.
999
- const known = v.unconfirmed ? (p as Pending).prior ?? undefined : v.p === 'forget' ? undefined : v.p !== undefined ? v.p : p;
1430
+ const known = v.unconfirmed ? (p as Pending).prior ?? undefined : v.p === 'forget' ? undefined : v.p !== undefined ? v.p : v.prior ?? p;
1000
1431
  const prior = known && (known.kind === 'absent' || known.kind === 'content') ? known : null;
1432
+ // A push that re-plans over our own narrowing push, stopped before its
1433
+ // bytes, plans from disk's narrowed mode (the verdict's prior, which
1434
+ // resolvePending leaves unrecorded), as above, and goes on as that push:
1435
+ // its intent carries the stopped push's prior, the old bytes at their own
1436
+ // mode, never the mode it narrowed them to, and modes from that mode, so a
1437
+ // stop of this push too reads disk's narrowed mode as the push's own.
1438
+ // Past the adoptions handled above, a verdict carries a prior with P still
1439
+ // pending only from that narrowing, whose push recorded its modes.
1440
+ const stopped = v.prior !== undefined && p?.kind === 'pending' ? p : undefined;
1441
+ if (v.mode === 'push' && s && d.kind === 'file') {
1442
+ // Forced, disk takes the store's mode whatever it holds now, as it takes its bytes.
1443
+ plans.push({ path, kind: 'chmod', mode: s.mode!, ...(opts.force ? {} : { from: d.mode }), hash: s.hash, size: s.size, prior, before: agreement.get(mount.name, path), expect });
1444
+ continue;
1445
+ }
1001
1446
  if (v.state === 'synced' && !v.unconfirmed) {
1002
1447
  if (v.intent !== undefined) {
1003
1448
  mount.intents.put(path, v.intent);
@@ -1008,7 +1453,25 @@ export async function pushPaths(
1008
1453
  }
1009
1454
  if (v.state === 'unverified') {
1010
1455
  if (!opts.force) {
1011
- result.skipped.push({ path, reason: `cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and materialize again, or pass force to overwrite it` });
1456
+ // An intent that can't be read is the one note fixing doesn't
1457
+ // answer: nothing is decided over it until a sync of the path
1458
+ // replaces it (decide()), and a sync replaces it only where it sees
1459
+ // disk's copy, so a copy it can't see is named first. For the rest,
1460
+ // fixing what the note names holds, and forcedWay says where force
1461
+ // does too. Where disk has deleted the file, that sync takes the
1462
+ // deletion.
1463
+ const sync = gone
1464
+ ? "sync this path to take disk's deletion, which removes the workspace's copy and replaces the intent"
1465
+ : 'sync this path to adopt the disk version, which replaces the intent';
1466
+ result.skipped.push({
1467
+ path,
1468
+ reason: bi?.unreadable !== undefined
1469
+ ? d.kind === 'absent' || d.kind === 'file'
1470
+ ? `${v.note} — ${sync}`
1471
+ : `${v.note}, and disk's copy can't be verified (${d.kind === 'unobserved' ? d.reason : 'not hashed'}) — once it can be, ${sync}`
1472
+ : inTheWay(v.note, !s) ??
1473
+ `cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and ${again}${forcedWay(v.note, !s, forced)}`,
1474
+ });
1012
1475
  continue;
1013
1476
  }
1014
1477
  }
@@ -1027,15 +1490,20 @@ export async function pushPaths(
1027
1490
  }
1028
1491
  result.skipped.push({
1029
1492
  path,
1030
- reason: noEvidence
1493
+ // Whatever was recorded, a disk with no copy now is answered for what
1494
+ // it holds; a path with no evidence still needs its push named.
1495
+ reason: gone
1496
+ ? `disk has no copy of this file (conflict: ${kind}) — ${takeDisk}, or ` +
1497
+ `materialize ${noEvidence ? 'this path' : 'it'} with force to write the workspace's copy back`
1498
+ : noEvidence
1031
1499
  ? `disk holds a different copy, and nothing records which is newer (conflict: ${kind}) — ` +
1032
- 'sync this path to adopt the disk version, or materialize this path with force to overwrite it'
1500
+ `sync this path to adopt the disk version, or materialize this path with force ${s ? 'to overwrite it' : 'and applyDeletions to delete it'}`
1033
1501
  : kind === 'interrupted'
1034
1502
  ? 'disk holds bytes that match neither what was being written there nor what disk last agreed with ' +
1035
1503
  '(conflict: interrupted), as a write that failed or was cut off partway leaves — check the file, then ' +
1036
- 'pass force to write the workspace copy over it, or sync this path to adopt the disk copy as it is'
1504
+ `${forced}, or sync this path to adopt the disk copy as it is`
1037
1505
  : `stale copy: disk changed since it last agreed with the workspace (conflict: ${kind}) — ` +
1038
- 'sync this path to adopt the disk version, or pass force to overwrite it',
1506
+ `sync this path to adopt the disk version, or ${forced}`,
1039
1507
  });
1040
1508
  continue;
1041
1509
  }
@@ -1064,7 +1532,12 @@ export async function pushPaths(
1064
1532
  continue;
1065
1533
  }
1066
1534
  if (!opts.applyDeletions) {
1067
- result.pendingDeletions.push(path);
1535
+ // A deletion no push makes, forced or not (inTheWay), is refused for
1536
+ // that, as an unforced push refuses it, rather than left for
1537
+ // applyDeletions, which couldn't make it either.
1538
+ const lasting = inTheWay(v.note, true);
1539
+ if (lasting !== null) result.skipped.push({ path, reason: lasting });
1540
+ else result.pendingDeletions.push(path);
1068
1541
  continue;
1069
1542
  }
1070
1543
  if (d.kind === 'absent') continue;
@@ -1076,28 +1549,115 @@ export async function pushPaths(
1076
1549
  result.skipped.push({ path, reason: 'the workspace copy is missing from the store' });
1077
1550
  continue;
1078
1551
  }
1079
- plans.push({ path, kind: 'write', blob, hash: s.hash, prior, before: agreement.get(mount.name, path), expect });
1552
+ // The store's mode goes to disk with its bytes where the file is new,
1553
+ // the push is forced, or disk still has the mode they last agreed on. A
1554
+ // push that completes our own unconfirmed one, whose bytes landed, is a
1555
+ // push like any other: it gives the store's mode where disk shows one of
1556
+ // the stopped push's own (pushedMode) or the one they last agreed on, and
1557
+ // keeps any other, set since it stopped (Ada-1017, room-203 #131062,
1558
+ // #131518). A mode disk changed since stays, and so does the evidence
1559
+ // that it did, up to the moment of the write.
1560
+ const dm = d.kind === 'file' ? d.mode : undefined;
1561
+ const priorMode = prior?.kind === 'content' ? prior.mode : undefined;
1562
+ const own = v.unconfirmed === true && v.ownMode === true;
1563
+ const apply = mount.modesObserved && s.mode !== undefined && s.mode !== dm &&
1564
+ (dm === undefined || opts.force === true || own || priorMode === dm) ? s.mode : undefined;
1565
+ plans.push({
1566
+ path, kind: 'write', blob, hash: s.hash, prior: stopped ? stopped.prior : prior, before: agreement.get(mount.name, path), expect,
1567
+ // With no mode in the store's entry, its `to` is the mode the push
1568
+ // leaves, the one the stopped push narrowed to: it claims none it lacks.
1569
+ ...(stopped ? { ownModes: { from: stopped.modes!.from, to: s.mode ?? (stopped.modes!.from & stopped.modes!.to) } } : {}),
1570
+ ...(apply !== undefined ? { apply } : {}),
1571
+ ...(apply !== undefined && dm !== undefined && opts.force !== true ? { from: dm } : {}),
1572
+ // Forced over a mode disk changed: without force, the bytes would keep it.
1573
+ ...(apply !== undefined && dm !== undefined && priorMode !== dm && !own ? { overDisk: true as const } : {}),
1574
+ ...(s.mode !== undefined ? { storeMode: s.mode } : {}),
1575
+ ...(priorMode !== undefined ? { priorMode } : {}),
1576
+ ...(dm !== undefined ? { seenMode: dm } : {}),
1577
+ });
1080
1578
  }
1081
1579
 
1082
1580
  // 1. Durable intents before any disk effect, the root they write under first.
1581
+ // A chmod announces none: it leaves the bytes as they are. Stopped before
1582
+ // its completion, it leaves disk at the store's mode or still at the one
1583
+ // the evidence agreed, and the next observation decides from that: synced
1584
+ // and recorded, or pushed again. Nothing is lost either way. A forced
1585
+ // chmod has no guard: stopped before it, disk keeps whatever mode it found,
1586
+ // which the next observation reads as disk's, and forced again, it applies
1587
+ // the store's.
1083
1588
  if (plans.length > 0) agreement.recordRootIdentity(mount.name, root.identity);
1084
- plans.forEach((step, i) => {
1589
+ const announced = plans.filter((step) => step.kind !== 'chmod');
1590
+ announced.forEach((step, i) => {
1085
1591
  agreement.intend(mount.name, step.path, {
1086
1592
  effect: 'disk',
1087
1593
  prior: step.prior,
1088
1594
  expect: step.kind === 'write' ? { kind: 'content', hash: step.hash } : ABSENT,
1089
1595
  branchId,
1090
- }, { durable: i === plans.length - 1 });
1596
+ // A write whose plan gives a mode (`apply`) records the modes it may
1597
+ // leave, so a stop is read by them: the mode it saw, or for a file it
1598
+ // creates the one it creates it at, and the one it gives. A file a
1599
+ // forced push creates at its entry's mode with no mode planned records
1600
+ // none. One that goes on as our own stopped narrowing records that
1601
+ // push's, from the old bytes' own mode, whether or not it gives a mode
1602
+ // itself.
1603
+ ...(step.kind === 'write' && step.ownModes !== undefined ? { modes: step.ownModes }
1604
+ : step.kind === 'write' && step.apply !== undefined
1605
+ ? { modes: { from: step.seenMode ?? step.apply, to: step.apply } } : {}),
1606
+ }, { durable: i === announced.length - 1 });
1091
1607
  });
1092
1608
 
1093
1609
  // 2. Effects, each bound at its point of use.
1094
- const effected: Array<{ step: Plan; dirs: string[] }> = [];
1610
+ const effected: Array<{ step: Plan; dirs: string[]; mode?: number; widenRefused?: boolean; modeKept?: boolean }> = [];
1095
1611
  for (const step of plans) {
1096
1612
  opts.beforeEffect?.(step.path);
1097
1613
  try {
1098
1614
  if (step.kind === 'write') {
1099
- await writeContained(mount.view, mount.rootReal, step.path, step.blob, step.expect);
1100
- effected.push({ step, dirs: chainOf(step.path) });
1615
+ const { perm, widenRefused, modeKept } = await writeContained(
1616
+ mount.view, mount.rootReal, step.path, step.blob, step.expect, step.apply, step.from, step.overDisk === true,
1617
+ // Where modes go unobserved, nothing is applied, a created file's included.
1618
+ mount.modesObserved ? step.storeMode : undefined,
1619
+ );
1620
+ effected.push({ step, dirs: chainOf(step.path), mode: perm, widenRefused, modeKept });
1621
+ } else if (step.kind === 'chmod') {
1622
+ // An inode change, made durable by the file's own fsync.
1623
+ const { refused, narrowing } = await chmodContained(mount.view, mount.rootReal, step.path, step.mode, step.expect, step.from);
1624
+ // A chmod announces no intent, so a refused one leaves the evidence
1625
+ // as planning read it, unless it says otherwise below.
1626
+ if (refused && narrowing && !step.overDisk) {
1627
+ // A narrowing of the store's own change that the kernel refused
1628
+ // stays owed, as a write's does: taking disk's wider mode in would
1629
+ // let a later write put new bytes out at it.
1630
+ result.skipped.push({
1631
+ path: step.path,
1632
+ reason: `not given the workspace's mode (${octal(step.mode)}): the kernel refused to narrow it, as it does for a file another user owns, ` +
1633
+ 'so it stays owed: a retry is refused the same way until the file\'s owner or an administrator narrows it',
1634
+ });
1635
+ continue;
1636
+ }
1637
+ if (refused && step.overDisk) {
1638
+ // Forced over a mode disk changed, or one nothing records: the
1639
+ // path is as it was before the push, so a sync takes disk's mode
1640
+ // in, as it would have without force.
1641
+ result.skipped.push({
1642
+ path: step.path,
1643
+ reason: `not given the workspace's mode (${octal(step.mode)}): the kernel refused the chmod, so disk keeps its mode, and a sync takes it in`,
1644
+ });
1645
+ continue;
1646
+ }
1647
+ if (refused) {
1648
+ // A widening of the store's own change that the kernel refused:
1649
+ // disk keeps its mode, and the evidence records none, so a sync
1650
+ // takes disk's in by the rule for an unknown rather than owing a
1651
+ // mode this process can never give.
1652
+ agreement.set(mount.name, step.path, { kind: 'content', hash: step.hash, size: step.size });
1653
+ recorded = true;
1654
+ result.skipped.push({
1655
+ path: step.path,
1656
+ reason: `not given the workspace's mode (${octal(step.mode)}): the kernel refused the chmod, and a sync takes disk's mode in`,
1657
+ });
1658
+ continue;
1659
+ }
1660
+ effected.push({ step, dirs: [] });
1101
1661
  } else {
1102
1662
  // Its directory is synced whether this unlink removed the entry or
1103
1663
  // found it gone: either way the completion claims a durable absence.
@@ -1110,7 +1670,8 @@ export async function pushPaths(
1110
1670
  // exactly what it was, whatever kind that was.
1111
1671
  agreement.restore(mount.name, step.path, step.before);
1112
1672
  recorded = true;
1113
- result.skipped.push({ path: step.path, reason: `not ${step.kind === 'write' ? 'written' : 'unlinked'}: ${failure.message}` });
1673
+ const why = inTheWay(failure.message, step.kind === 'unlink') ?? failure.message;
1674
+ result.skipped.push({ path: step.path, reason: `not ${step.kind === 'unlink' ? 'unlinked' : step.kind === 'chmod' ? 'given the workspace\'s mode' : 'written'}: ${why}` });
1114
1675
  } else {
1115
1676
  // No completion: the intent stays pending, and the next observation
1116
1677
  // resolves it from whatever disk then holds.
@@ -1127,7 +1688,8 @@ export async function pushPaths(
1127
1688
  // 4. Completions. P is physical and global. Branch intent belongs to the
1128
1689
  // branch this push planned on: it is settled only while that branch is
1129
1690
  // still selected, and otherwise by convergence when the branch returns.
1130
- for (const { step, dirs } of effected) {
1691
+ for (const { step, dirs, mode: written, widenRefused, modeKept } of effected) {
1692
+ const mode = mount.modesObserved ? written : undefined;
1131
1693
  const failed = dirs.find((dir) => unsynced.has(dir));
1132
1694
  if (failed !== undefined) {
1133
1695
  result.skipped.push({
@@ -1137,11 +1699,61 @@ export async function pushPaths(
1137
1699
  });
1138
1700
  continue;
1139
1701
  }
1140
- const value: Agreed = step.kind === 'write' ? { kind: 'content', hash: step.hash, size: step.blob.byteLength } : { kind: 'absent' };
1702
+ // The mode the evidence records: the one the push gave the file, if the
1703
+ // file has it; disk's own, where the store records none or the same; and
1704
+ // otherwise the one disk last agreed with, so a mode disk changed since
1705
+ // reads as disk's. (Where modes go unobserved, the evidence's writer
1706
+ // records none.)
1707
+ // A widening of the workspace's own change that the kernel refused
1708
+ // records none: a sync takes disk's mode in, rather than owing one this
1709
+ // process can never give. Forced over a mode disk changed, or one nothing
1710
+ // records (overDisk), any refused chmod ends where an unforced write
1711
+ // would: the evidence records the mode disk last agreed with, if any, and
1712
+ // a sync takes disk's mode in.
1713
+ const agreedMode = step.kind === 'chmod'
1714
+ ? step.mode
1715
+ : step.kind === 'write'
1716
+ ? widenRefused && !step.overDisk
1717
+ ? undefined
1718
+ : step.apply !== undefined && step.apply === mode
1719
+ ? mode
1720
+ : step.storeMode === undefined || step.storeMode === mode ? mode : step.priorMode
1721
+ : undefined;
1722
+ const value: Agreed = step.kind === 'unlink'
1723
+ ? { kind: 'absent' }
1724
+ : { kind: 'content', hash: step.hash, size: step.kind === 'write' ? step.blob.byteLength : step.size, ...(agreedMode !== undefined ? { mode: agreedMode } : {}) };
1141
1725
  agreement.set(mount.name, step.path, value);
1142
1726
  recorded = true;
1727
+ // Bytes written where the kernel refused the mode, or where disk's mode
1728
+ // changed since it last agreed, keep disk's mode, and the push says so
1729
+ // beside what it wrote: the bytes reached disk, so it's no refusal.
1730
+ if (step.kind === 'write' && widenRefused && mode !== undefined) {
1731
+ result.modesKept.push({
1732
+ path: step.path,
1733
+ reason: `written, its mode left at ${octal(mode)}: the kernel refused the chmod, ` +
1734
+ `so the workspace's (${octal(step.apply ?? step.storeMode ?? mode)}) isn't given, and a sync takes disk's mode in`,
1735
+ });
1736
+ } else if (step.kind === 'write' && modeKept && mode !== undefined) {
1737
+ // Forced over a mode disk changed, and refused: the bytes are written
1738
+ // at it, the evidence records it as an unforced write's would.
1739
+ result.modesKept.push({
1740
+ path: step.path,
1741
+ reason: `written, its mode left at ${octal(mode)}: the kernel refused to narrow it ` +
1742
+ `${narrowedTo(mode & (step.apply ?? mode), step.apply ?? mode)}, so disk keeps its mode, and a sync takes it in`,
1743
+ });
1744
+ } else if (step.kind === 'write' && mode !== undefined && step.storeMode !== undefined && mode !== step.storeMode) {
1745
+ result.modesKept.push({
1746
+ path: step.path,
1747
+ reason: (step.priorMode === undefined
1748
+ ? `written, keeping disk's mode (${octal(mode)}), which differs from the workspace's (${octal(step.storeMode)}), and nothing records which side changed it`
1749
+ : `written, keeping disk's mode (${octal(mode)}), which changed since it last agreed with the workspace's (${octal(step.storeMode)})`) +
1750
+ ' — sync this path to take disk\'s mode in, or materialize this path with force to apply the workspace\'s',
1751
+ });
1752
+ }
1143
1753
  const planned = store.currentBranch().id === branchId;
1144
- if (step.kind === 'write') {
1754
+ if (step.kind === 'chmod') {
1755
+ result.written.push(step.path);
1756
+ } else if (step.kind === 'write') {
1145
1757
  if (planned) mount.intents.update(step.path, (cur) => { const next = { ...cur }; delete next.conflict; delete next.origin; return next; });
1146
1758
  result.written.push(step.path);
1147
1759
  } else {