@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
@@ -38,8 +38,8 @@
38
38
  * a wrong adoption.
39
39
  */
40
40
  import { candidateOf, sameCandidate, sameRootIdentity, } from './disk-agreement.js';
41
- import { ROOT_REPLACED, fingerprintVouches, locateRoot, looksBinary, observePath, provenAbsent, readHead, readPath, sniffImageMime, walkScope, } from './observe.js';
42
- import { EffectFailed, chainOf, syncDirectories, unlinkContained, writeContained } from './effects.js';
41
+ import { IGNORED, OTHER_MOUNT, ROOT_REPLACED, fingerprintVouches, ignoreBeneath, inOtherMount, locateRoot, looksBinary, observePath, passedOverAt, passedOverBetween, provenAbsent, readHead, readPath, sniffImageMime, walkScope, } from './observe.js';
42
+ import { EffectFailed, chainOf, chmodContained, narrowedTo, syncDirectories, unlinkContained, writeContained } from './effects.js';
43
43
  const ABSENT = { kind: 'absent' };
44
44
  export const WALK_CAP = 5000;
45
45
  function diskCandidate(d) {
@@ -52,7 +52,58 @@ function diskCandidate(d) {
52
52
  function agreedFromDisk(d) {
53
53
  if (d.kind !== 'file')
54
54
  return { kind: 'absent' };
55
- return { kind: 'content', hash: d.hash, size: d.size, ...(d.fp ? { fp: d.fp } : {}) };
55
+ return { kind: 'content', hash: d.hash, size: d.size, ...(d.mode !== undefined ? { mode: d.mode } : {}), ...(d.fp ? { fp: d.fp } : {}) };
56
+ }
57
+ /** The file-type bits git records with a regular file's mode. */
58
+ const REGULAR_FILE = 0o100000;
59
+ /** A tree entry's mode for permission bits: with the regular-file type bits, as git records one. */
60
+ export function treeMode(perm) {
61
+ return REGULAR_FILE | (perm & 0o777);
62
+ }
63
+ /**
64
+ * The mode a tree entry takes for disk's permission bits, or, where disk's
65
+ * aren't observed, the entry's own. With neither, the placeholder an entry
66
+ * without a recorded mode has (see entryMode).
67
+ */
68
+ function treeModeOf(perm, s) {
69
+ if (perm !== undefined)
70
+ return { mode: treeMode(perm) };
71
+ if (s?.mode !== undefined)
72
+ return { mode: treeMode(s.mode) };
73
+ return { mode: 0o644 };
74
+ }
75
+ /**
76
+ * The permission bits a tree entry records, or undefined when it records
77
+ * none. An entry without the file-type bits was written before modes were
78
+ * recorded, which wrote 0o644 whatever disk had: a placeholder, not a mode.
79
+ */
80
+ export function entryMode(mode) {
81
+ return (mode & 0o170000) === REGULAR_FILE ? mode & 0o777 : undefined;
82
+ }
83
+ /** The store's side of a tree entry. */
84
+ export function storeSide(entry) {
85
+ if (!entry)
86
+ return null;
87
+ const mode = entryMode(entry.mode);
88
+ return { hash: entry.blobHash, size: entry.size, ...(mode !== undefined ? { mode } : {}) };
89
+ }
90
+ /**
91
+ * Where disk and the store hold the same bytes, what their modes ask for.
92
+ * A mode the store doesn't record agrees with any, and takes disk's
93
+ * (`fill`). The store's mode goes to disk (`push`) only where the evidence
94
+ * shows disk still has the mode they last agreed on; otherwise disk's mode
95
+ * is taken in as a change (`adopt`): disk changed it, or nothing says which
96
+ * side did, and an unknown never moves disk.
97
+ */
98
+ function modeAction(disk, store, agreed) {
99
+ if (store === undefined)
100
+ return 'fill';
101
+ if (store === disk)
102
+ return undefined;
103
+ return agreed !== undefined && agreed === disk ? 'push' : 'adopt';
104
+ }
105
+ function octal(mode) {
106
+ return `0${mode.toString(8).padStart(3, '0')}`;
56
107
  }
57
108
  function inScope(path, scope) {
58
109
  if (scope.kind === 'paths')
@@ -76,7 +127,7 @@ async function learnDisk(mount, path, tracked, opts) {
76
127
  const p = tracked.p;
77
128
  const trusted = (fp) => mount.ctimeTrustedDev !== null && fp.dev === mount.ctimeTrustedDev;
78
129
  if (!opts.rehash && p?.kind === 'content' && fingerprintVouches(p.fp, seen.fp, trusted(seen.fp))) {
79
- return { kind: 'file', hash: p.hash, size: seen.size, mtimeMs: seen.mtimeMs, fp: p.fp ?? null, ingestible: null };
130
+ return { kind: 'file', hash: p.hash, size: seen.size, mtimeMs: seen.mtimeMs, ...(mount.modesObserved ? { mode: seen.mode } : {}), fp: p.fp ?? null, ingestible: null };
80
131
  }
81
132
  const nothingTracked = p === undefined && !tracked.hasStore && !tracked.hasIntent;
82
133
  if (nothingTracked && seen.size > mount.view.maxFileSize) {
@@ -96,6 +147,7 @@ async function learnDisk(mount, path, tracked, opts) {
96
147
  hash: read.hash,
97
148
  size: read.size,
98
149
  mtimeMs: read.mtimeMs,
150
+ ...(mount.modesObserved ? { mode: read.mode } : {}),
99
151
  // A fingerprint is recorded only where it can later vouch for the bytes.
100
152
  fp: trusted(read.fp) ? read.fp : null,
101
153
  ingestible,
@@ -129,10 +181,66 @@ function keepStoreSide(hasStore, bi) {
129
181
  function settleAdoption(p, s) {
130
182
  const sc = s ? { kind: 'content', hash: s.hash } : ABSENT;
131
183
  if (sameCandidate(sc, p.expect)) {
132
- return p.expect.kind === 'absent' ? { kind: 'absent' } : { kind: 'content', hash: p.expect.hash, size: s.size };
184
+ // Where modes are observed, the adoption took disk's mode with its bytes,
185
+ // so the entry's mode is the one they agreed on, but for an adoption that
186
+ // took the mode a stopped push of ours meant the old bytes to have: its
187
+ // entry holds that mode while disk holds the push's narrowed one, which a
188
+ // later sync then takes in. Where modes aren't observed, the entry's mode
189
+ // is the store's own, and the evidence records none.
190
+ return p.expect.kind === 'absent'
191
+ ? { kind: 'absent' }
192
+ : { kind: 'content', hash: p.expect.hash, size: s.size, ...(s.mode !== undefined ? { mode: s.mode } : {}) };
133
193
  }
134
194
  return p.prior ?? 'forget';
135
195
  }
196
+ /**
197
+ * What's in the way, and the way out, when disk's shape keeps a push from a
198
+ * path: no push writes a file over a directory or unlinks one, unlinks
199
+ * anything but a regular file or a followed symlink to one, or writes below
200
+ * a file or a symlink the walk won't take (`writeContained`,
201
+ * `unlinkContained`), forced or not. Null for any other reason, which is
202
+ * passed on as it is.
203
+ */
204
+ function inTheWay(reason, deletion) {
205
+ // Observation's words for what a deletion never unlinks, which the unlink
206
+ // refuses in the same words.
207
+ if (deletion && (reason === 'not a regular file' || reason === 'a symlink to a directory, not walked')) {
208
+ const what = reason === 'not a regular file'
209
+ ? 'a pipe, socket or device at this path, or a symlink to one'
210
+ : 'a symlink to a directory at this path';
211
+ return `disk has ${what}, and no push unlinks it, forced or not — ask whoever runs this host if it should go`;
212
+ }
213
+ if (reason === 'a directory') {
214
+ return deletion
215
+ ? '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'
216
+ : 'disk has a directory at this path, and no push writes a file over one, forced or not — ' +
217
+ 'move or remove that directory on disk and materialize again, or write the file under another path';
218
+ }
219
+ // A deletion below a file finds nothing there to unlink, so only a write
220
+ // meets this one.
221
+ if (reason === 'a parent path is not a directory') {
222
+ return 'disk has a file or a symlink where this path needs a directory, and no push writes below one, forced or not — ' +
223
+ 'move or remove it on disk and materialize again, or write the file under another path';
224
+ }
225
+ return null;
226
+ }
227
+ /**
228
+ * The forced way out the refusal of a disk copy that couldn't be verified
229
+ * names, if any. A forced push skips the verification, so it gets past the
230
+ * note only where its effect can go on. A file the module can't read (EACCES)
231
+ * is overwritten, or unlinked, where disk lets the workspace write it, or
232
+ * unlink it, which a read's EACCES doesn't show: force is named on that
233
+ * condition. No other note names force: a forced push stops at some (a
234
+ * symlink the mount doesn't follow, what isn't a regular file, a parent it
235
+ * can't use); another writer still at the file ("the file changed while it
236
+ * was read") is answered by materializing again; and the rest aren't known
237
+ * to pass.
238
+ */
239
+ function forcedWay(note, deletion, forced) {
240
+ if (note === 'cannot open (EACCES)')
241
+ return `, or, if disk lets the workspace ${deletion ? 'unlink' : 'write'} the file, ${forced}`;
242
+ return '';
243
+ }
136
244
  /**
137
245
  * The synchronous precondition of every workspace mutation: evidence owed a
138
246
  * barrier is made durable first, and an adoption left pending on the
@@ -151,13 +259,32 @@ export function settleAdoptionBeforeMutation(store, agreement, mount, path) {
151
259
  if (p?.kind !== 'pending' || p.effect !== 'adopt' || p.branchId !== store.currentBranch().id)
152
260
  return;
153
261
  const entry = store.treeGet(mount.treeStateId, path);
154
- const settled = settleAdoption(p, entry ? { hash: entry.blobHash, size: entry.size } : null);
262
+ const settled = settleAdoption(p, entry ? { hash: entry.blobHash, size: entry.size, ...(entryMode(entry.mode) !== undefined ? { mode: entryMode(entry.mode) } : {}) } : null);
155
263
  if (settled === 'forget')
156
264
  agreement.forget(mount.name, path);
157
265
  else
158
266
  agreement.set(mount.name, path, settled, { afterCommittedState: true });
159
267
  agreement.barrier();
160
268
  }
269
+ /**
270
+ * What a push of ours that stopped partway left on disk, read by its own
271
+ * modes (Pending's `modes`): the new bytes at `from & to` or `to` were meant
272
+ * to have `to`; the old bytes at `from & to`, narrower than `from`, still
273
+ * have `from` as theirs. New bytes at `from` are a push's only where
274
+ * `from & to` is `from`: a push that narrows does so before its bytes, so
275
+ * otherwise they were given `from` since. Undefined where disk shows
276
+ * anything else: no push of ours, or a mode set since it stopped.
277
+ */
278
+ function pushedMode(p, dc, d) {
279
+ const m = p.modes;
280
+ if (p.effect !== 'disk' || m === undefined || d.kind !== 'file' || d.mode === undefined)
281
+ return undefined;
282
+ const narrowed = m.from & m.to;
283
+ if (sameCandidate(dc, p.expect))
284
+ return d.mode === narrowed || d.mode === m.to ? m.to : undefined;
285
+ const prior = p.prior ? candidateOf(p.prior) : null;
286
+ return prior && sameCandidate(dc, prior) && narrowed !== m.from && d.mode === narrowed ? m.from : undefined;
287
+ }
161
288
  /**
162
289
  * Resolve a pending intent against what was observed. Returns the evidence
163
290
  * the rule should use, and whether that resolution should be recorded.
@@ -169,11 +296,24 @@ function resolvePending(p, dc, s, d, branchId) {
169
296
  if (sameCandidate(dc, p.expect)) {
170
297
  // Our own effect shows on disk, but nothing proves it durable: decided
171
298
  // on, never recorded. Only a push whose barriers succeed completes it.
299
+ // Where disk's mode is one of the push's own, the push that completes
300
+ // it gives the store's mode there.
172
301
  const shown = p.expect.kind === 'absent' ? { kind: 'absent' } : agreedFromDisk(d);
173
- return { p: shown, unconfirmed: true };
302
+ return { p: shown, unconfirmed: true, ...(pushedMode(p, dc, d) !== undefined ? { ownMode: true } : {}) };
174
303
  }
175
- if (priorCandidate && sameCandidate(dc, priorCandidate))
304
+ if (priorCandidate && sameCandidate(dc, priorCandidate)) {
305
+ // The push narrowed the mode and stopped before its bytes: disk's mode
306
+ // is the push's, so the rule decides from it, and the next push writes
307
+ // the bytes at it and then widens to the store's. The old bytes are
308
+ // never given the store's mode. Nothing is recorded: the intent, with
309
+ // the modes that map disk's narrowed one back to the old bytes' own,
310
+ // stays, carried by a push that goes on with it (pushPaths), until a
311
+ // push completes it or a sync adopts disk's copy.
312
+ if (prior.kind === 'content' && d.kind === 'file' && pushedMode(p, dc, d) !== undefined) {
313
+ return { p: { ...prior, mode: d.mode } };
314
+ }
176
315
  return { p: prior, record: prior };
316
+ }
177
317
  // A write whose prior was unknown left no file: it never created one (a
178
318
  // partial write leaves a file behind), so P is as unknown as before.
179
319
  if (!prior && dc.kind === 'absent' && p.expect.kind === 'content')
@@ -210,9 +350,12 @@ function adoptDisk(d, s) {
210
350
  return { state: 'synced', ...(s ? { adopt: 'remove', op: 'deleted' } : {}), p: { kind: 'absent' }, intent: {} };
211
351
  }
212
352
  // Disk agreeing with the store is disk's state already, whatever the file
213
- // is: a binary or oversize file the workspace holds stays held.
214
- if (s && s.hash === d.hash)
215
- return { state: 'synced', p: agreedFromDisk(d), intent: {} };
353
+ // is: a binary or oversize file the workspace holds stays held. Its mode
354
+ // is disk's too.
355
+ if (s && s.hash === d.hash) {
356
+ const mode = d.mode === undefined ? undefined : s.mode === undefined ? 'fill' : s.mode !== d.mode ? 'adopt' : undefined;
357
+ return { state: 'synced', p: agreedFromDisk(d), intent: {}, ...(mode ? { mode } : {}), ...(mode === 'adopt' ? { op: 'modified' } : {}) };
358
+ }
216
359
  if (d.ingestible)
217
360
  return { state: 'synced', adopt: 'ingest', op: s ? 'modified' : 'created', p: agreedFromDisk(d), intent: {} };
218
361
  return { state: 'disk-only', ...(s ? { adopt: 'drop', op: 'deleted' } : {}), p: 'forget', intent: {} };
@@ -243,12 +386,22 @@ function decide(d, s, pIn, bi, branchId, adopt) {
243
386
  if (adopt) {
244
387
  // What a path sync gives up is exactly what a sync without a path would
245
388
  // have kept of the workspace's own (a draft, a deletion, a conflict's
246
- // workspace side), and the verdict says which.
247
- const taken = adoptDisk(d, s);
389
+ // workspace side), and the verdict says which. Where a push of ours
390
+ // stopped partway, disk's mode is the push's: the store takes the one
391
+ // the push meant those bytes to have, and the evidence keeps disk's, so
392
+ // a later push moves disk there.
393
+ const meant = pIn?.kind === 'pending' ? pushedMode(pIn, dc, d) : undefined;
394
+ const taken = adoptDisk(meant === undefined ? d : { ...d, mode: meant }, s);
395
+ if (meant !== undefined) {
396
+ if (taken.p !== undefined && taken.p !== 'forget' && taken.p.kind === 'content')
397
+ taken.p = agreedFromDisk(d);
398
+ if (taken.adopt === 'ingest' || taken.mode !== undefined)
399
+ taken.diskMode = meant;
400
+ }
248
401
  // A give-up is an adoption, or the intent it clears: a deletion or a
249
402
  // conflict given up for a disk copy the workspace can't hold changes no
250
403
  // tree entry, and is listed all the same.
251
- if (taken.adopt || (bi && taken.intent !== undefined)) {
404
+ if (taken.adopt || taken.mode === 'adopt' || (bi && taken.intent !== undefined)) {
252
405
  const kept = decide(d, s, pIn, bi, branchId, false);
253
406
  if (WORKSPACE_KEPT.has(kept.state))
254
407
  taken.discards = kept.state;
@@ -261,6 +414,9 @@ function decide(d, s, pIn, bi, branchId, adopt) {
261
414
  // verdict below records newer evidence.
262
415
  let p = pIn;
263
416
  let resolution;
417
+ // Evidence a resolution decides from without recording it: a stopped
418
+ // push's narrowing, whose intent stays (resolvePending).
419
+ let unrecorded;
264
420
  let uncaptured = false;
265
421
  if (p?.kind === 'pending') {
266
422
  const resolved = resolvePending(p, dc, s, d, branchId);
@@ -269,13 +425,19 @@ function decide(d, s, pIn, bi, branchId, adopt) {
269
425
  // follows from that, and nothing is recorded — no completion, no
270
426
  // agreement, no branch-intent change — until a push confirms it.
271
427
  const state = sameCandidate(dc, sc) ? 'synced' : bi?.conflict ? 'conflict' : keepStoreSide(s !== null, bi);
272
- return { state, note: UNCONFIRMED_NOTE, unconfirmed: true };
428
+ return {
429
+ state, note: UNCONFIRMED_NOTE, unconfirmed: true,
430
+ ...(resolved.ownMode ? { ownMode: true } : {}),
431
+ };
273
432
  }
274
433
  p = resolved.p;
275
434
  resolution = resolved.record;
435
+ if (resolution === undefined && p !== undefined && (p.kind === 'content' || p.kind === 'absent'))
436
+ unrecorded = p;
276
437
  uncaptured = resolved.uncaptured === true;
277
438
  }
278
- const verdict = (v) => (resolution && v.p === undefined ? { ...v, p: resolution } : v);
439
+ const verdict = (v) => (resolution && v.p === undefined ? { ...v, p: resolution }
440
+ : unrecorded && v.p === undefined && v.prior === undefined ? { ...v, prior: unrecorded } : v);
279
441
  // Agreement settles everything, a recorded conflict included.
280
442
  if (sameCandidate(dc, sc)) {
281
443
  const settles = bi?.tombstone || bi?.conflict || bi?.origin ? { intent: {} } : {};
@@ -291,11 +453,23 @@ function decide(d, s, pIn, bi, branchId, adopt) {
291
453
  const recorded = p?.kind === 'content' ? p : null;
292
454
  // Re-record only when P says something else, or a fresh fingerprint can
293
455
  // replace one that no longer vouches. A fresh one exists only where it's
294
- // trusted, so both are judged as trusted here.
295
- const stale = !recorded || recorded.hash !== agreed.hash || recorded.size !== agreed.size ||
456
+ // trusted, so both are judged as trusted here. Evidence a resolution left
457
+ // unrecorded, a stopped push's narrowing, is stale too: the agreement
458
+ // replaces that push's intent, and the mode rule gives the store's mode.
459
+ const stale = unrecorded !== undefined || !recorded || recorded.hash !== agreed.hash || recorded.size !== agreed.size || recorded.mode !== agreed.mode ||
296
460
  (agreed.fp !== undefined && fingerprintVouches(agreed.fp, agreed.fp, true) &&
297
461
  !fingerprintVouches(recorded.fp, agreed.fp, true));
298
- return verdict({ state: 'synced', ...(stale ? { p: agreed } : {}), ...settles });
462
+ const mode = agreed.mode === undefined ? undefined : modeAction(agreed.mode, s.mode, recorded?.mode);
463
+ if (mode === 'push') {
464
+ return verdict({
465
+ state: 'workspace-draft',
466
+ mode,
467
+ note: `the workspace's mode (${octal(s.mode)}) differs from disk's (${octal(agreed.mode)}): materialize to apply it`,
468
+ ...(stale ? { p: agreed } : {}),
469
+ ...settles,
470
+ });
471
+ }
472
+ return verdict({ state: 'synced', ...(mode ? { mode } : {}), ...(mode === 'adopt' ? { op: 'modified' } : {}), ...(stale ? { p: agreed } : {}), ...settles });
299
473
  }
300
474
  // A recorded conflict stays until it is resolved explicitly or disk and store converge.
301
475
  if (bi?.conflict)
@@ -411,13 +585,19 @@ async function gather(store, agreement, mount, scope, opts) {
411
585
  if (inScope(p, scope))
412
586
  candidates.add(p);
413
587
  if (rootUnavailable === null) {
414
- walk = await walkScope(mount.view, mount.rootReal, scope.dir, scope.recursive, opts.cap ?? WALK_CAP, { named: opts.named });
588
+ const resumeAfter = opts.resume ? agreement.walkResumesAfter(mount.name, scope.dir) : undefined;
589
+ walk = await walkScope(mount.view, mount.rootReal, scope.dir, scope.recursive, opts.cap ?? mount.walkCap, {
590
+ named: opts.named,
591
+ ...(resumeAfter !== undefined ? { resumeAfter } : {}),
592
+ });
415
593
  for (const f of walk.files)
416
594
  if (inScope(f, scope))
417
595
  candidates.add(f);
418
- // Ignored names are listed only when something tracks them (above).
419
- for (const [p, why] of walk.others)
420
- if (why !== 'ignored by the mount' && inScope(p, scope))
596
+ // The other entries it took (a link, a socket…). Ignored names, and the
597
+ // entries the cap left, are listed only when something tracks them
598
+ // (above).
599
+ for (const p of walk.othersTaken)
600
+ if (inScope(p, scope))
421
601
  candidates.add(p);
422
602
  for (const d of walk.dirs)
423
603
  if (inScope(d, { kind: 'dir', dir: scope.dir, recursive: false }))
@@ -431,18 +611,59 @@ async function gather(store, agreement, mount, scope, opts) {
431
611
  gathered.facts.set(path, { kind: 'unobserved', reason: rootUnavailable });
432
612
  continue;
433
613
  }
614
+ // What the mount passes over by design stays unobserved by a pass that
615
+ // names no path, however it reaches it, so no listing's absence proves a
616
+ // copy there gone. In another mount's directory, no pass of this mount
617
+ // observes anything, named or not.
618
+ // Beneath a directory a by-design rule covers, a pass that isn't named
619
+ // observes nothing. A path a rule covers by its own name is observed as a
620
+ // walk observes it: passed over where the walk lists it, and absent where
621
+ // the walk lists its directory without it; a pass that reaches it by its
622
+ // path alone, as a listing, glob or grep naming it does, passes over it.
623
+ // A pass that names its scope, as a sync naming a path and a file tool's
624
+ // pass on the path it acts on do, sets aside only what covers the scope,
625
+ // at it or above it, as its walk does (#276): a directory passed over by
626
+ // design beneath a named directory stays passed over, and so does what
627
+ // lies beneath it.
628
+ let byDesign;
629
+ if (inOtherMount(path, mount.view.others)) {
630
+ byDesign = OTHER_MOUNT;
631
+ }
632
+ else if (!opts.named) {
633
+ const slash = path.lastIndexOf('/');
634
+ const above = slash < 0 ? undefined : passedOverAt(path.slice(0, slash), mount.view.ignore, mount.view.others);
635
+ const own = above === undefined && !walk ? passedOverAt(path, mount.view.ignore, mount.view.others) : undefined;
636
+ byDesign = above ?? own;
637
+ }
638
+ else if (scope.kind === 'dir') {
639
+ byDesign = passedOverBetween(scope.dir, path, ignoreBeneath(scope.dir, mount.view.ignore), mount.view.others);
640
+ }
641
+ if (byDesign !== undefined) {
642
+ gathered.facts.set(path, { kind: 'unobserved', reason: byDesign });
643
+ continue;
644
+ }
434
645
  if (walk && provenAbsent(walk, path)) {
435
646
  gathered.facts.set(path, { kind: 'absent' });
436
647
  continue;
437
648
  }
438
- if (walk && !walk.files.has(path) && !walk.others.has(path)) {
439
- // Beneath a region the walk didn't reach: unproven either way.
440
- const region = walk.incomplete.find((r) => r.path === '' || path === r.path || path.startsWith(r.path + '/'));
441
- gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
649
+ if (walk?.others.get(path) === IGNORED) {
650
+ gathered.facts.set(path, { kind: 'unobserved', reason: IGNORED });
442
651
  continue;
443
652
  }
444
- if (walk?.others.get(path) === 'ignored by the mount') {
445
- gathered.facts.set(path, { kind: 'unobserved', reason: 'ignored by the mount' });
653
+ if (walk && !walk.files.has(path) && !walk.othersTaken.has(path)) {
654
+ // Not taken by the walk, or beneath a region it didn't reach: unproven
655
+ // either way. The nearest region holding it says why.
656
+ let region;
657
+ for (const r of walk.incomplete) {
658
+ if (r.path !== '' && path !== r.path && !path.startsWith(r.path + '/'))
659
+ continue;
660
+ // A link's own region is about what lies beneath it, not the link.
661
+ if (r.kind === 'symlink' && r.path === path)
662
+ continue;
663
+ if (region === undefined || r.path.length > region.path.length)
664
+ region = r;
665
+ }
666
+ gathered.facts.set(path, { kind: 'unobserved', reason: region ? region.reason : 'not reached by the walk' });
446
667
  continue;
447
668
  }
448
669
  const p = agreement.get(mount.name, path);
@@ -511,8 +732,7 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
511
732
  result.branchId = branchId;
512
733
  const planned = [];
513
734
  for (const [path, d] of facts) {
514
- const entry = store.treeGet(mount.treeStateId, path);
515
- const s = entry ? { hash: entry.blobHash, size: entry.size } : null;
735
+ const s = storeSide(store.treeGet(mount.treeStateId, path));
516
736
  const p = agreement.get(mount.name, path);
517
737
  const bi = mount.intents.get(path);
518
738
  const verdict = decide(d, s, p, bi, branchId, opts.adopt?.(path) === true);
@@ -545,13 +765,20 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
545
765
  const v = x.verdict;
546
766
  if (v.adopt === 'ingest' && x.d.kind === 'file' && x.d.ingestible) {
547
767
  const blobHash = store.storeBlob(x.d.ingestible, 'text/plain');
548
- store.treeSet(mount.treeStateId, x.path, { blobHash, size: x.d.ingestible.byteLength, mode: 0o644 });
768
+ store.treeSet(mount.treeStateId, x.path, { blobHash, size: x.d.ingestible.byteLength, ...treeModeOf(v.diskMode ?? x.d.mode, x.s) });
549
769
  committed = true;
550
770
  }
551
771
  else if ((v.adopt === 'remove' || v.adopt === 'drop') && x.s) {
552
772
  store.treeRemove(mount.treeStateId, x.path);
553
773
  committed = true;
554
774
  }
775
+ else if ((v.mode === 'fill' || v.mode === 'adopt') && x.d.kind === 'file' && x.s) {
776
+ // The same bytes, with disk's mode. A fill records a mode where the
777
+ // entry had none, which is no change: an undo or a branch whose entry
778
+ // still has none agrees with any mode, so neither reads it as one.
779
+ store.treeSet(mount.treeStateId, x.path, { blobHash: x.s.hash, size: x.s.size, ...treeModeOf(v.diskMode ?? x.d.mode, x.s) });
780
+ committed = true;
781
+ }
555
782
  if (v.conflict) {
556
783
  const record = { kind: v.conflict, at: Date.now(), disk: counterpartOf(store, x.d) };
557
784
  mount.intents.update(x.path, (cur) => ({ ...cur, conflict: record }));
@@ -562,10 +789,11 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
562
789
  mount.intents.put(x.path, v.intent);
563
790
  committed = true;
564
791
  }
565
- if (v.op && v.adopt)
566
- result.ops.push({ path: x.path, op: v.op });
792
+ const changed = v.adopt !== undefined || v.mode === 'adopt';
793
+ if (v.op && changed)
794
+ result.ops.push({ path: x.path, op: v.op, ...(v.adopt === undefined ? { modeOnly: true } : {}) });
567
795
  if (v.discards)
568
- result.discarded.push({ path: x.path, was: v.discards, ...(v.op && v.adopt ? { op: v.op } : {}) });
796
+ result.discarded.push({ path: x.path, was: v.discards, ...(v.op && changed ? { op: v.op } : {}) });
569
797
  }
570
798
  // 4. Completions and agreements, after the commits they assert are synced —
571
799
  // this pass's, and any earlier tool write's an agreement rests on.
@@ -585,6 +813,17 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
585
813
  agreement.set(mount.name, x.path, next, { afterCommittedState: first });
586
814
  first = false;
587
815
  }
816
+ // Where the walk stopped, once what it observed is decided: the next pass
817
+ // of this scope resumes after it, or starts afresh once this one covered
818
+ // the scope. A walk that couldn't list its scope's own directory covered
819
+ // none of it, and leaves the place as it was. Unlike evidence, a place
820
+ // binds no root: it only says where the next walk starts, and that walk
821
+ // takes its turn after it in whatever directory is the root by then.
822
+ let resumeAfter;
823
+ if (opts.resume && walk && scope.kind === 'dir' && scope.recursive && walk.complete.has(scope.dir)) {
824
+ resumeAfter = walk.stoppedAfter ?? null;
825
+ agreement.recordWalk(mount.name, scope.dir, resumeAfter);
826
+ }
588
827
  // 5. Durable before returning. An agreement advance (D = S) has no pending
589
828
  // intent to recover from, so losing its append would leave an older P that
590
829
  // another branch would misread as a disk edit.
@@ -614,27 +853,55 @@ export async function reconcilePass(store, agreement, mount, scope, opts = {}) {
614
853
  }
615
854
  if (walk) {
616
855
  const tracked = [...result.reports.values()].filter((r) => r.state === 'unverified').map((r) => r.path);
856
+ if (typeof resumeAfter === 'string' && scope.kind === 'dir') {
857
+ // A watched mount's catch-up scan takes turns with its whole-mount
858
+ // syncs; nothing else moves a place.
859
+ const next = scope.dir !== ''
860
+ ? 'the next sync of this directory'
861
+ : mount.catchUpScan
862
+ ? 'the next sync of the mount, or its catch-up scan if that comes first,'
863
+ : 'the next sync of the mount';
864
+ result.incomplete.push({
865
+ path: scope.dir,
866
+ reason: `the walk cap (${opts.cap ?? mount.walkCap}) was reached: ${next} resumes after ${mount.name}/${resumeAfter}`,
867
+ });
868
+ }
617
869
  for (const region of walk.incomplete) {
618
- const hides = tracked.some((p) => p === region.path || p.startsWith(region.path + '/'));
619
- if (region.kind === 'error' || region.kind === 'cap' || hides)
870
+ // A link's own region hides only what lies beneath it: the link itself
871
+ // is reported by path, with its own reason.
872
+ const hides = tracked.some((p) => (region.kind !== 'symlink' && p === region.path) || p.startsWith(region.path + '/'));
873
+ // A scope passed over whole is the answer to the pass that asked for
874
+ // it, so it's always named. Another mount's directory met beneath the
875
+ // scope is passed over by design even where this mount's tree holds
876
+ // copies there: no tool of this mount syncs, pushes or clears them
877
+ // (listings show them, naming their holder), so naming the directory
878
+ // as unreached would never clear.
879
+ const wholeScope = scope.kind === 'dir' && scope.dir !== '' && region.path === scope.dir;
880
+ const unreached = hides && region.reason !== OTHER_MOUNT;
881
+ if (region.kind === 'error' || region.kind === 'cap' || unreached || wholeScope)
620
882
  result.incomplete.push({ path: region.path, reason: region.reason });
621
- // An ignored name that hides nothing tracked is reported nowhere else:
883
+ // A name passed over by design that hides nothing tracked is reported nowhere else:
622
884
  // counted, so a sync that says 0 also says what it didn't look at
623
- // (agent-framework #276). A symlink region isn't counted: the link
624
- // itself is a candidate the pass reports by path, taken through the
885
+ // (agent-framework #276). A symlink region isn't counted: a link the
886
+ // walk took is a candidate the pass reports by path, taken through the
625
887
  // link when it's a file on a mount that follows symlinks, and
626
- // otherwise listed under `skipped`.
888
+ // otherwise listed under `skipped`; a tracked link the cap left is
889
+ // unverified for the cap.
627
890
  else if (region.kind === 'ignored')
628
891
  result.passedOver.set(region.reason, (result.passedOver.get(region.reason) ?? 0) + 1);
629
892
  }
630
893
  }
631
894
  return result;
632
895
  }
896
+ /** Why a push skips a path in another mount's directory nested in its own. */
897
+ export const NOT_PUSHED_IN_OTHER_MOUNT = "it lies in another mount's directory, which no push of this mount writes";
633
898
  /**
634
899
  * Make disk hold the store's version of each path, under the same rule:
635
900
  * drafts are written, agreement is left alone, conflicts and unverifiable
636
901
  * disk copies are refused unless `force`, and a workspace deletion reaches
637
902
  * disk only with `applyDeletions` (with `force` too when disk changed since).
903
+ * Forced or not, nothing but a regular file, or a followed symlink to one, is
904
+ * written over or unlinked (effects.ts).
638
905
  * A path nothing has evidence about is written only when `named`.
639
906
  * A path whose earlier push shows on disk unconfirmed is pushed again, so its
640
907
  * completion rests on barriers that succeeded.
@@ -653,7 +920,7 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
653
920
  agreement.ensureReconciled();
654
921
  if (agreement.needsBarrier)
655
922
  agreement.barrier(); // as a pass does: nothing decided from unsynced evidence
656
- const result = { written: [], unchanged: [], deleted: [], skipped: [], pendingDeletions: [] };
923
+ const result = { written: [], unchanged: [], deleted: [], skipped: [], modesKept: [], pendingDeletions: [] };
657
924
  if (mount.readOnly)
658
925
  return result;
659
926
  // A root that isn't available takes no writes, forced or not: restoring the
@@ -665,6 +932,20 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
665
932
  result.skipped.push({ path, reason: root.reason });
666
933
  return result;
667
934
  }
935
+ // Another mount's directory nested in this one is that mount's: no push
936
+ // of this mount writes or deletes a path in it, forced or not. (On a mount
937
+ // that follows links, a link of this mount's that leads there is read as
938
+ // this mount's own path; on one that doesn't, the link is refused.)
939
+ if (mount.view.others.length > 0) {
940
+ const own = [];
941
+ for (const path of paths) {
942
+ if (inOtherMount(path, mount.view.others))
943
+ result.skipped.push({ path, reason: NOT_PUSHED_IN_OTHER_MOUNT });
944
+ else
945
+ own.push(path);
946
+ }
947
+ paths = own;
948
+ }
668
949
  const plans = [];
669
950
  // Observe disk.
670
951
  const facts = new Map();
@@ -694,11 +975,20 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
694
975
  result.sequence = store.currentSequence();
695
976
  let recorded = false;
696
977
  for (const [path, d] of facts) {
697
- const entry = store.treeGet(mount.treeStateId, path);
698
- const s = entry ? { hash: entry.blobHash, size: entry.size } : null;
978
+ const s = storeSide(store.treeGet(mount.treeStateId, path));
699
979
  const p = agreement.get(mount.name, path);
700
980
  const bi = mount.intents.get(path);
701
981
  const v = decide(d, s, p, bi, branchId, false);
982
+ // The ways out a refusal names, as materialize takes them: a deletion
983
+ // reaches disk only with applyDeletions.
984
+ const again = s ? 'materialize again' : 'materialize again with applyDeletions';
985
+ // Where disk has deleted a file the workspace holds, a sync takes the
986
+ // deletion, which removes the workspace's copy, and a forced push writes
987
+ // the workspace's copy back: there is no disk copy to adopt or overwrite.
988
+ const gone = s !== null && d.kind === 'absent';
989
+ const forced = !s ? 'materialize it with force and applyDeletions to delete it'
990
+ : gone ? "materialize it with force to write the workspace's copy back" : 'materialize it with force to overwrite it';
991
+ 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';
702
992
  // What the effect may find: what this observation saw, unless forced.
703
993
  const expect = opts.force || d.kind === 'unobserved' || d.kind === 'unhashed'
704
994
  ? { kind: 'any' }
@@ -713,18 +1003,51 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
713
1003
  result.skipped.push({
714
1004
  path,
715
1005
  reason: 'stale copy: disk changed since it last agreed with the workspace, which did not — ' +
716
- 'sync this path to adopt the disk version, or pass force to overwrite it',
1006
+ `${takeDisk}, or ${forced}`,
717
1007
  });
718
1008
  continue;
719
1009
  }
720
1010
  const priorP = p && (p.kind === 'absent' || p.kind === 'content') ? p : null;
721
1011
  const blob = store.getBlob(s.hash);
722
- if (blob)
723
- plans.push({ path, kind: 'write', blob, hash: s.hash, prior: priorP, before: agreement.get(mount.name, path), expect });
1012
+ const forcedMode = mount.modesObserved && s.mode !== undefined && s.mode !== (d.kind === 'file' ? d.mode : undefined) ? s.mode : undefined;
1013
+ const agreedMode = priorP?.kind === 'content' ? priorP.mode : undefined;
1014
+ if (blob) {
1015
+ plans.push({
1016
+ path, kind: 'write', blob, hash: s.hash, prior: priorP, before: agreement.get(mount.name, path), expect,
1017
+ ...(forcedMode !== undefined ? { apply: forcedMode } : {}),
1018
+ ...(forcedMode !== undefined && d.kind === 'file' && agreedMode !== d.mode ? { overDisk: true } : {}),
1019
+ ...(s.mode !== undefined ? { storeMode: s.mode } : {}),
1020
+ // Refused over disk's mode, the completion records what an unforced
1021
+ // write would: the mode disk last agreed with.
1022
+ ...(agreedMode !== undefined ? { priorMode: agreedMode } : {}),
1023
+ ...(d.kind === 'file' && d.mode !== undefined ? { seenMode: d.mode } : {}),
1024
+ });
1025
+ }
724
1026
  else
725
1027
  result.skipped.push({ path, reason: 'the workspace copy is missing from the store' });
726
1028
  continue;
727
1029
  }
1030
+ if (v.mode === 'adopt' && s && d.kind === 'file') {
1031
+ // Disk's mode changed since it last agreed, on the bytes the store
1032
+ // holds, and the store's didn't. A sync takes it in. Recording disk's
1033
+ // mode here would read as the store's change, which the next push
1034
+ // would revert; forced, disk takes the store's mode, as forced bytes do.
1035
+ if (!opts.force) {
1036
+ // modeAction adopts where disk changed the mode since they agreed, and
1037
+ // where nothing records a mode they agreed on.
1038
+ const agreedMode = p?.kind === 'content' ? p.mode : undefined;
1039
+ result.skipped.push({
1040
+ path,
1041
+ reason: (agreedMode === undefined
1042
+ ? `disk's mode (${octal(d.mode)}) differs from the workspace's (${octal(s.mode)}), and nothing records which side changed it`
1043
+ : `disk's mode (${octal(d.mode)}) changed since it last agreed with the workspace's (${octal(s.mode)})`) +
1044
+ ' — sync this path to take disk\'s mode in, or materialize this path with force to apply the workspace\'s',
1045
+ });
1046
+ continue;
1047
+ }
1048
+ plans.push({ path, kind: 'chmod', mode: s.mode, hash: s.hash, size: s.size, prior: null, before: p, expect, overDisk: true });
1049
+ continue;
1050
+ }
728
1051
  // Whatever the verdict learned about disk (an agreement, a resolved
729
1052
  // intent) is recorded before anything is pushed over it, and the root it
730
1053
  // was learned under before that (once per mount), as a pass does.
@@ -739,8 +1062,22 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
739
1062
  // The prior a new intent carries is the last confirmed evidence: for a
740
1063
  // path whose earlier push is unconfirmed, that push's own prior, never
741
1064
  // the bytes it wrote.
742
- const known = v.unconfirmed ? p.prior ?? undefined : v.p === 'forget' ? undefined : v.p !== undefined ? v.p : p;
1065
+ const known = v.unconfirmed ? p.prior ?? undefined : v.p === 'forget' ? undefined : v.p !== undefined ? v.p : v.prior ?? p;
743
1066
  const prior = known && (known.kind === 'absent' || known.kind === 'content') ? known : null;
1067
+ // A push that re-plans over our own narrowing push, stopped before its
1068
+ // bytes, plans from disk's narrowed mode (the verdict's prior, which
1069
+ // resolvePending leaves unrecorded), as above, and goes on as that push:
1070
+ // its intent carries the stopped push's prior, the old bytes at their own
1071
+ // mode, never the mode it narrowed them to, and modes from that mode, so a
1072
+ // stop of this push too reads disk's narrowed mode as the push's own.
1073
+ // Past the adoptions handled above, a verdict carries a prior with P still
1074
+ // pending only from that narrowing, whose push recorded its modes.
1075
+ const stopped = v.prior !== undefined && p?.kind === 'pending' ? p : undefined;
1076
+ if (v.mode === 'push' && s && d.kind === 'file') {
1077
+ // Forced, disk takes the store's mode whatever it holds now, as it takes its bytes.
1078
+ 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 });
1079
+ continue;
1080
+ }
744
1081
  if (v.state === 'synced' && !v.unconfirmed) {
745
1082
  if (v.intent !== undefined) {
746
1083
  mount.intents.put(path, v.intent);
@@ -752,7 +1089,25 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
752
1089
  }
753
1090
  if (v.state === 'unverified') {
754
1091
  if (!opts.force) {
755
- result.skipped.push({ path, reason: `cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and materialize again, or pass force to overwrite it` });
1092
+ // An intent that can't be read is the one note fixing doesn't
1093
+ // answer: nothing is decided over it until a sync of the path
1094
+ // replaces it (decide()), and a sync replaces it only where it sees
1095
+ // disk's copy, so a copy it can't see is named first. For the rest,
1096
+ // fixing what the note names holds, and forcedWay says where force
1097
+ // does too. Where disk has deleted the file, that sync takes the
1098
+ // deletion.
1099
+ const sync = gone
1100
+ ? "sync this path to take disk's deletion, which removes the workspace's copy and replaces the intent"
1101
+ : 'sync this path to adopt the disk version, which replaces the intent';
1102
+ result.skipped.push({
1103
+ path,
1104
+ reason: bi?.unreadable !== undefined
1105
+ ? d.kind === 'absent' || d.kind === 'file'
1106
+ ? `${v.note} — ${sync}`
1107
+ : `${v.note}, and disk's copy can't be verified (${d.kind === 'unobserved' ? d.reason : 'not hashed'}) — once it can be, ${sync}`
1108
+ : inTheWay(v.note, !s) ??
1109
+ `cannot verify disk copy (${v.note ?? 'not observed'}) — fix it and ${again}${forcedWay(v.note, !s, forced)}`,
1110
+ });
756
1111
  continue;
757
1112
  }
758
1113
  }
@@ -772,15 +1127,20 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
772
1127
  }
773
1128
  result.skipped.push({
774
1129
  path,
775
- reason: noEvidence
776
- ? `disk holds a different copy, and nothing records which is newer (conflict: ${kind}) — ` +
777
- 'sync this path to adopt the disk version, or materialize this path with force to overwrite it'
778
- : kind === 'interrupted'
779
- ? 'disk holds bytes that match neither what was being written there nor what disk last agreed with ' +
780
- '(conflict: interrupted), as a write that failed or was cut off partway leaves — check the file, then ' +
781
- 'pass force to write the workspace copy over it, or sync this path to adopt the disk copy as it is'
782
- : `stale copy: disk changed since it last agreed with the workspace (conflict: ${kind}) — ` +
783
- 'sync this path to adopt the disk version, or pass force to overwrite it',
1130
+ // Whatever was recorded, a disk with no copy now is answered for what
1131
+ // it holds; a path with no evidence still needs its push named.
1132
+ reason: gone
1133
+ ? `disk has no copy of this file (conflict: ${kind}) — ${takeDisk}, or ` +
1134
+ `materialize ${noEvidence ? 'this path' : 'it'} with force to write the workspace's copy back`
1135
+ : noEvidence
1136
+ ? `disk holds a different copy, and nothing records which is newer (conflict: ${kind}) — ` +
1137
+ `sync this path to adopt the disk version, or materialize this path with force ${s ? 'to overwrite it' : 'and applyDeletions to delete it'}`
1138
+ : kind === 'interrupted'
1139
+ ? 'disk holds bytes that match neither what was being written there nor what disk last agreed with ' +
1140
+ '(conflict: interrupted), as a write that failed or was cut off partway leaves — check the file, then ' +
1141
+ `${forced}, or sync this path to adopt the disk copy as it is`
1142
+ : `stale copy: disk changed since it last agreed with the workspace (conflict: ${kind}) — ` +
1143
+ `sync this path to adopt the disk version, or ${forced}`,
784
1144
  });
785
1145
  continue;
786
1146
  }
@@ -810,7 +1170,14 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
810
1170
  continue;
811
1171
  }
812
1172
  if (!opts.applyDeletions) {
813
- result.pendingDeletions.push(path);
1173
+ // A deletion no push makes, forced or not (inTheWay), is refused for
1174
+ // that, as an unforced push refuses it, rather than left for
1175
+ // applyDeletions, which couldn't make it either.
1176
+ const lasting = inTheWay(v.note, true);
1177
+ if (lasting !== null)
1178
+ result.skipped.push({ path, reason: lasting });
1179
+ else
1180
+ result.pendingDeletions.push(path);
814
1181
  continue;
815
1182
  }
816
1183
  if (d.kind === 'absent')
@@ -823,18 +1190,61 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
823
1190
  result.skipped.push({ path, reason: 'the workspace copy is missing from the store' });
824
1191
  continue;
825
1192
  }
826
- plans.push({ path, kind: 'write', blob, hash: s.hash, prior, before: agreement.get(mount.name, path), expect });
1193
+ // The store's mode goes to disk with its bytes where the file is new,
1194
+ // the push is forced, or disk still has the mode they last agreed on. A
1195
+ // push that completes our own unconfirmed one, whose bytes landed, is a
1196
+ // push like any other: it gives the store's mode where disk shows one of
1197
+ // the stopped push's own (pushedMode) or the one they last agreed on, and
1198
+ // keeps any other, set since it stopped (Ada-1017, room-203 #131062,
1199
+ // #131518). A mode disk changed since stays, and so does the evidence
1200
+ // that it did, up to the moment of the write.
1201
+ const dm = d.kind === 'file' ? d.mode : undefined;
1202
+ const priorMode = prior?.kind === 'content' ? prior.mode : undefined;
1203
+ const own = v.unconfirmed === true && v.ownMode === true;
1204
+ const apply = mount.modesObserved && s.mode !== undefined && s.mode !== dm &&
1205
+ (dm === undefined || opts.force === true || own || priorMode === dm) ? s.mode : undefined;
1206
+ plans.push({
1207
+ path, kind: 'write', blob, hash: s.hash, prior: stopped ? stopped.prior : prior, before: agreement.get(mount.name, path), expect,
1208
+ // With no mode in the store's entry, its `to` is the mode the push
1209
+ // leaves, the one the stopped push narrowed to: it claims none it lacks.
1210
+ ...(stopped ? { ownModes: { from: stopped.modes.from, to: s.mode ?? (stopped.modes.from & stopped.modes.to) } } : {}),
1211
+ ...(apply !== undefined ? { apply } : {}),
1212
+ ...(apply !== undefined && dm !== undefined && opts.force !== true ? { from: dm } : {}),
1213
+ // Forced over a mode disk changed: without force, the bytes would keep it.
1214
+ ...(apply !== undefined && dm !== undefined && priorMode !== dm && !own ? { overDisk: true } : {}),
1215
+ ...(s.mode !== undefined ? { storeMode: s.mode } : {}),
1216
+ ...(priorMode !== undefined ? { priorMode } : {}),
1217
+ ...(dm !== undefined ? { seenMode: dm } : {}),
1218
+ });
827
1219
  }
828
1220
  // 1. Durable intents before any disk effect, the root they write under first.
1221
+ // A chmod announces none: it leaves the bytes as they are. Stopped before
1222
+ // its completion, it leaves disk at the store's mode or still at the one
1223
+ // the evidence agreed, and the next observation decides from that: synced
1224
+ // and recorded, or pushed again. Nothing is lost either way. A forced
1225
+ // chmod has no guard: stopped before it, disk keeps whatever mode it found,
1226
+ // which the next observation reads as disk's, and forced again, it applies
1227
+ // the store's.
829
1228
  if (plans.length > 0)
830
1229
  agreement.recordRootIdentity(mount.name, root.identity);
831
- plans.forEach((step, i) => {
1230
+ const announced = plans.filter((step) => step.kind !== 'chmod');
1231
+ announced.forEach((step, i) => {
832
1232
  agreement.intend(mount.name, step.path, {
833
1233
  effect: 'disk',
834
1234
  prior: step.prior,
835
1235
  expect: step.kind === 'write' ? { kind: 'content', hash: step.hash } : ABSENT,
836
1236
  branchId,
837
- }, { durable: i === plans.length - 1 });
1237
+ // A write whose plan gives a mode (`apply`) records the modes it may
1238
+ // leave, so a stop is read by them: the mode it saw, or for a file it
1239
+ // creates the one it creates it at, and the one it gives. A file a
1240
+ // forced push creates at its entry's mode with no mode planned records
1241
+ // none. One that goes on as our own stopped narrowing records that
1242
+ // push's, from the old bytes' own mode, whether or not it gives a mode
1243
+ // itself.
1244
+ ...(step.kind === 'write' && step.ownModes !== undefined ? { modes: step.ownModes }
1245
+ : step.kind === 'write' && step.apply !== undefined
1246
+ ? { modes: { from: step.seenMode ?? step.apply, to: step.apply } } : {}),
1247
+ }, { durable: i === announced.length - 1 });
838
1248
  });
839
1249
  // 2. Effects, each bound at its point of use.
840
1250
  const effected = [];
@@ -842,8 +1252,51 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
842
1252
  opts.beforeEffect?.(step.path);
843
1253
  try {
844
1254
  if (step.kind === 'write') {
845
- await writeContained(mount.view, mount.rootReal, step.path, step.blob, step.expect);
846
- effected.push({ step, dirs: chainOf(step.path) });
1255
+ const { perm, widenRefused, modeKept } = await writeContained(mount.view, mount.rootReal, step.path, step.blob, step.expect, step.apply, step.from, step.overDisk === true,
1256
+ // Where modes go unobserved, nothing is applied, a created file's included.
1257
+ mount.modesObserved ? step.storeMode : undefined);
1258
+ effected.push({ step, dirs: chainOf(step.path), mode: perm, widenRefused, modeKept });
1259
+ }
1260
+ else if (step.kind === 'chmod') {
1261
+ // An inode change, made durable by the file's own fsync.
1262
+ const { refused, narrowing } = await chmodContained(mount.view, mount.rootReal, step.path, step.mode, step.expect, step.from);
1263
+ // A chmod announces no intent, so a refused one leaves the evidence
1264
+ // as planning read it, unless it says otherwise below.
1265
+ if (refused && narrowing && !step.overDisk) {
1266
+ // A narrowing of the store's own change that the kernel refused
1267
+ // stays owed, as a write's does: taking disk's wider mode in would
1268
+ // let a later write put new bytes out at it.
1269
+ result.skipped.push({
1270
+ path: step.path,
1271
+ 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, ` +
1272
+ 'so it stays owed: a retry is refused the same way until the file\'s owner or an administrator narrows it',
1273
+ });
1274
+ continue;
1275
+ }
1276
+ if (refused && step.overDisk) {
1277
+ // Forced over a mode disk changed, or one nothing records: the
1278
+ // path is as it was before the push, so a sync takes disk's mode
1279
+ // in, as it would have without force.
1280
+ result.skipped.push({
1281
+ path: step.path,
1282
+ 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`,
1283
+ });
1284
+ continue;
1285
+ }
1286
+ if (refused) {
1287
+ // A widening of the store's own change that the kernel refused:
1288
+ // disk keeps its mode, and the evidence records none, so a sync
1289
+ // takes disk's in by the rule for an unknown rather than owing a
1290
+ // mode this process can never give.
1291
+ agreement.set(mount.name, step.path, { kind: 'content', hash: step.hash, size: step.size });
1292
+ recorded = true;
1293
+ result.skipped.push({
1294
+ path: step.path,
1295
+ reason: `not given the workspace's mode (${octal(step.mode)}): the kernel refused the chmod, and a sync takes disk's mode in`,
1296
+ });
1297
+ continue;
1298
+ }
1299
+ effected.push({ step, dirs: [] });
847
1300
  }
848
1301
  else {
849
1302
  // Its directory is synced whether this unlink removed the entry or
@@ -858,7 +1311,8 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
858
1311
  // exactly what it was, whatever kind that was.
859
1312
  agreement.restore(mount.name, step.path, step.before);
860
1313
  recorded = true;
861
- result.skipped.push({ path: step.path, reason: `not ${step.kind === 'write' ? 'written' : 'unlinked'}: ${failure.message}` });
1314
+ const why = inTheWay(failure.message, step.kind === 'unlink') ?? failure.message;
1315
+ result.skipped.push({ path: step.path, reason: `not ${step.kind === 'unlink' ? 'unlinked' : step.kind === 'chmod' ? 'given the workspace\'s mode' : 'written'}: ${why}` });
862
1316
  }
863
1317
  else {
864
1318
  // No completion: the intent stays pending, and the next observation
@@ -874,7 +1328,8 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
874
1328
  // 4. Completions. P is physical and global. Branch intent belongs to the
875
1329
  // branch this push planned on: it is settled only while that branch is
876
1330
  // still selected, and otherwise by convergence when the branch returns.
877
- for (const { step, dirs } of effected) {
1331
+ for (const { step, dirs, mode: written, widenRefused, modeKept } of effected) {
1332
+ const mode = mount.modesObserved ? written : undefined;
878
1333
  const failed = dirs.find((dir) => unsynced.has(dir));
879
1334
  if (failed !== undefined) {
880
1335
  result.skipped.push({
@@ -884,11 +1339,64 @@ export async function pushPaths(store, agreement, mount, paths, opts = {}) {
884
1339
  });
885
1340
  continue;
886
1341
  }
887
- const value = step.kind === 'write' ? { kind: 'content', hash: step.hash, size: step.blob.byteLength } : { kind: 'absent' };
1342
+ // The mode the evidence records: the one the push gave the file, if the
1343
+ // file has it; disk's own, where the store records none or the same; and
1344
+ // otherwise the one disk last agreed with, so a mode disk changed since
1345
+ // reads as disk's. (Where modes go unobserved, the evidence's writer
1346
+ // records none.)
1347
+ // A widening of the workspace's own change that the kernel refused
1348
+ // records none: a sync takes disk's mode in, rather than owing one this
1349
+ // process can never give. Forced over a mode disk changed, or one nothing
1350
+ // records (overDisk), any refused chmod ends where an unforced write
1351
+ // would: the evidence records the mode disk last agreed with, if any, and
1352
+ // a sync takes disk's mode in.
1353
+ const agreedMode = step.kind === 'chmod'
1354
+ ? step.mode
1355
+ : step.kind === 'write'
1356
+ ? widenRefused && !step.overDisk
1357
+ ? undefined
1358
+ : step.apply !== undefined && step.apply === mode
1359
+ ? mode
1360
+ : step.storeMode === undefined || step.storeMode === mode ? mode : step.priorMode
1361
+ : undefined;
1362
+ const value = step.kind === 'unlink'
1363
+ ? { kind: 'absent' }
1364
+ : { kind: 'content', hash: step.hash, size: step.kind === 'write' ? step.blob.byteLength : step.size, ...(agreedMode !== undefined ? { mode: agreedMode } : {}) };
888
1365
  agreement.set(mount.name, step.path, value);
889
1366
  recorded = true;
1367
+ // Bytes written where the kernel refused the mode, or where disk's mode
1368
+ // changed since it last agreed, keep disk's mode, and the push says so
1369
+ // beside what it wrote: the bytes reached disk, so it's no refusal.
1370
+ if (step.kind === 'write' && widenRefused && mode !== undefined) {
1371
+ result.modesKept.push({
1372
+ path: step.path,
1373
+ reason: `written, its mode left at ${octal(mode)}: the kernel refused the chmod, ` +
1374
+ `so the workspace's (${octal(step.apply ?? step.storeMode ?? mode)}) isn't given, and a sync takes disk's mode in`,
1375
+ });
1376
+ }
1377
+ else if (step.kind === 'write' && modeKept && mode !== undefined) {
1378
+ // Forced over a mode disk changed, and refused: the bytes are written
1379
+ // at it, the evidence records it as an unforced write's would.
1380
+ result.modesKept.push({
1381
+ path: step.path,
1382
+ reason: `written, its mode left at ${octal(mode)}: the kernel refused to narrow it ` +
1383
+ `${narrowedTo(mode & (step.apply ?? mode), step.apply ?? mode)}, so disk keeps its mode, and a sync takes it in`,
1384
+ });
1385
+ }
1386
+ else if (step.kind === 'write' && mode !== undefined && step.storeMode !== undefined && mode !== step.storeMode) {
1387
+ result.modesKept.push({
1388
+ path: step.path,
1389
+ reason: (step.priorMode === undefined
1390
+ ? `written, keeping disk's mode (${octal(mode)}), which differs from the workspace's (${octal(step.storeMode)}), and nothing records which side changed it`
1391
+ : `written, keeping disk's mode (${octal(mode)}), which changed since it last agreed with the workspace's (${octal(step.storeMode)})`) +
1392
+ ' — sync this path to take disk\'s mode in, or materialize this path with force to apply the workspace\'s',
1393
+ });
1394
+ }
890
1395
  const planned = store.currentBranch().id === branchId;
891
- if (step.kind === 'write') {
1396
+ if (step.kind === 'chmod') {
1397
+ result.written.push(step.path);
1398
+ }
1399
+ else if (step.kind === 'write') {
892
1400
  if (planned)
893
1401
  mount.intents.update(step.path, (cur) => { const next = { ...cur }; delete next.conflict; delete next.origin; return next; });
894
1402
  result.written.push(step.path);