@animalabs/agent-framework 0.22.2 → 0.22.4

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