@amalgm/shell 0.1.164-preview.36072132286 → 0.1.164-preview.36809057014

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 (475) hide show
  1. package/PURPOSE.md +642 -128
  2. package/README.md +10 -8
  3. package/dist/accepted-tail.d.ts +26 -0
  4. package/dist/accepted-tail.js +37 -0
  5. package/dist/accepted-tail.js.map +1 -0
  6. package/dist/agent-execution.d.ts +16 -1
  7. package/dist/agent-execution.js +13 -5
  8. package/dist/agent-execution.js.map +1 -1
  9. package/dist/apply-cleanup.d.ts +28 -0
  10. package/dist/apply-cleanup.js +166 -0
  11. package/dist/apply-cleanup.js.map +1 -0
  12. package/dist/apply-recovery.d.ts +47 -0
  13. package/dist/apply-recovery.js +99 -0
  14. package/dist/apply-recovery.js.map +1 -0
  15. package/dist/apply-rows.d.ts +72 -0
  16. package/dist/apply-rows.js +150 -0
  17. package/dist/apply-rows.js.map +1 -0
  18. package/dist/apply-saves.d.ts +85 -0
  19. package/dist/apply-saves.js +238 -0
  20. package/dist/apply-saves.js.map +1 -0
  21. package/dist/boot.js +2 -1
  22. package/dist/boot.js.map +1 -1
  23. package/dist/capture-work.d.ts +8 -0
  24. package/dist/capture-work.js +67 -0
  25. package/dist/capture-work.js.map +1 -0
  26. package/dist/cargo-group.d.ts +45 -0
  27. package/dist/cargo-group.js +88 -0
  28. package/dist/cargo-group.js.map +1 -0
  29. package/dist/channel-session.d.ts +4 -2
  30. package/dist/channel-session.js +4 -3
  31. package/dist/channel-session.js.map +1 -1
  32. package/dist/chat-composition.d.ts +2 -0
  33. package/dist/chat-composition.js +1 -0
  34. package/dist/chat-composition.js.map +1 -1
  35. package/dist/cli.js +20 -1
  36. package/dist/cli.js.map +1 -1
  37. package/dist/construct-apply-host.d.ts +4 -2
  38. package/dist/construct-apply-host.js +8 -3
  39. package/dist/construct-apply-host.js.map +1 -1
  40. package/dist/construct-door.js +1 -1
  41. package/dist/construct-door.js.map +1 -1
  42. package/dist/construct-ledger.d.ts +6 -1
  43. package/dist/construct-ledger.js +21 -33
  44. package/dist/construct-ledger.js.map +1 -1
  45. package/dist/content-cache-collection.d.ts +18 -0
  46. package/dist/content-cache-collection.js +99 -0
  47. package/dist/content-cache-collection.js.map +1 -0
  48. package/dist/content-cache-host.d.ts +83 -17
  49. package/dist/content-cache-host.js +335 -95
  50. package/dist/content-cache-host.js.map +1 -1
  51. package/dist/content-cache-namespace.d.ts +59 -0
  52. package/dist/content-cache-namespace.js +210 -0
  53. package/dist/content-cache-namespace.js.map +1 -0
  54. package/dist/content-cache-ownership.d.ts +13 -0
  55. package/dist/content-cache-ownership.js +75 -0
  56. package/dist/content-cache-ownership.js.map +1 -0
  57. package/dist/content-cache-retention.d.ts +23 -0
  58. package/dist/content-cache-retention.js +195 -0
  59. package/dist/content-cache-retention.js.map +1 -0
  60. package/dist/content-capture-pool.d.ts +287 -0
  61. package/dist/content-capture-pool.js +461 -0
  62. package/dist/content-capture-pool.js.map +1 -0
  63. package/dist/content-capture-worker.d.ts +8 -0
  64. package/dist/content-capture-worker.js +109 -0
  65. package/dist/content-capture-worker.js.map +1 -0
  66. package/dist/content-clock.d.ts +42 -0
  67. package/dist/content-clock.js +166 -0
  68. package/dist/content-clock.js.map +1 -0
  69. package/dist/content-direct.d.ts +2 -0
  70. package/dist/content-direct.js +12 -0
  71. package/dist/content-direct.js.map +1 -1
  72. package/dist/content-failure.d.ts +18 -0
  73. package/dist/content-failure.js +38 -0
  74. package/dist/content-failure.js.map +1 -0
  75. package/dist/content-stream-thread.d.ts +60 -0
  76. package/dist/content-stream-thread.js +95 -0
  77. package/dist/content-stream-thread.js.map +1 -0
  78. package/dist/content-stream-worker.d.ts +7 -0
  79. package/dist/content-stream-worker.js +52 -0
  80. package/dist/content-stream-worker.js.map +1 -0
  81. package/dist/declaration-absorption.d.ts +37 -0
  82. package/dist/declaration-absorption.js +119 -0
  83. package/dist/declaration-absorption.js.map +1 -0
  84. package/dist/declaration-chain.d.ts +36 -0
  85. package/dist/declaration-chain.js +96 -0
  86. package/dist/declaration-chain.js.map +1 -0
  87. package/dist/declaration-commit.d.ts +31 -0
  88. package/dist/declaration-commit.js +49 -0
  89. package/dist/declaration-commit.js.map +1 -0
  90. package/dist/declaration-hold.d.ts +22 -0
  91. package/dist/declaration-hold.js +24 -0
  92. package/dist/declaration-hold.js.map +1 -0
  93. package/dist/declaration-journal.d.ts +62 -0
  94. package/dist/declaration-journal.js +99 -0
  95. package/dist/declaration-journal.js.map +1 -0
  96. package/dist/declaration-save.d.ts +31 -0
  97. package/dist/declaration-save.js +134 -0
  98. package/dist/declaration-save.js.map +1 -0
  99. package/dist/detection/accepted-result.d.ts +19 -1
  100. package/dist/detection/accepted-result.js +36 -5
  101. package/dist/detection/accepted-result.js.map +1 -1
  102. package/dist/detection/admission-save.d.ts +55 -0
  103. package/dist/detection/admission-save.js +48 -0
  104. package/dist/detection/admission-save.js.map +1 -0
  105. package/dist/detection/admission-units.d.ts +66 -0
  106. package/dist/detection/admission-units.js +322 -0
  107. package/dist/detection/admission-units.js.map +1 -0
  108. package/dist/detection/alias-discovery.d.ts +12 -0
  109. package/dist/detection/alias-discovery.js +80 -0
  110. package/dist/detection/alias-discovery.js.map +1 -0
  111. package/dist/detection/alias-examination.d.ts +30 -0
  112. package/dist/detection/alias-examination.js +49 -0
  113. package/dist/detection/alias-examination.js.map +1 -0
  114. package/dist/detection/apply-ownership.d.ts +81 -0
  115. package/dist/detection/apply-ownership.js +232 -0
  116. package/dist/detection/apply-ownership.js.map +1 -0
  117. package/dist/detection/basis-advanced.d.ts +11 -0
  118. package/dist/detection/basis-advanced.js +26 -0
  119. package/dist/detection/basis-advanced.js.map +1 -0
  120. package/dist/detection/capture-outcome.d.ts +16 -0
  121. package/dist/detection/capture-outcome.js +92 -0
  122. package/dist/detection/capture-outcome.js.map +1 -0
  123. package/dist/detection/container-membership.d.ts +32 -0
  124. package/dist/detection/container-membership.js +49 -0
  125. package/dist/detection/container-membership.js.map +1 -0
  126. package/dist/detection/detect-cancelled.d.ts +6 -0
  127. package/dist/detection/detect-cancelled.js +10 -0
  128. package/dist/detection/detect-cancelled.js.map +1 -0
  129. package/dist/detection/directory-proof.d.ts +47 -0
  130. package/dist/detection/directory-proof.js +77 -0
  131. package/dist/detection/directory-proof.js.map +1 -0
  132. package/dist/detection/disposition-rows.d.ts +18 -0
  133. package/dist/detection/disposition-rows.js +29 -0
  134. package/dist/detection/disposition-rows.js.map +1 -0
  135. package/dist/detection/dispositions.d.ts +130 -0
  136. package/dist/detection/dispositions.js +248 -0
  137. package/dist/detection/dispositions.js.map +1 -0
  138. package/dist/detection/git-notice.d.ts +88 -0
  139. package/dist/detection/git-notice.js +444 -0
  140. package/dist/detection/git-notice.js.map +1 -0
  141. package/dist/detection/ground-proof.d.ts +130 -0
  142. package/dist/detection/ground-proof.js +274 -0
  143. package/dist/detection/ground-proof.js.map +1 -0
  144. package/dist/detection/ground-reader.d.ts +29 -0
  145. package/dist/detection/ground-reader.js +102 -0
  146. package/dist/detection/ground-reader.js.map +1 -0
  147. package/dist/detection/held-territory.d.ts +51 -0
  148. package/dist/detection/held-territory.js +205 -0
  149. package/dist/detection/held-territory.js.map +1 -0
  150. package/dist/detection/identity-discovery.d.ts +45 -0
  151. package/dist/detection/identity-discovery.js +287 -0
  152. package/dist/detection/identity-discovery.js.map +1 -0
  153. package/dist/detection/maintenance-sweep.d.ts +108 -0
  154. package/dist/detection/maintenance-sweep.js +325 -0
  155. package/dist/detection/maintenance-sweep.js.map +1 -0
  156. package/dist/detection/name-reach.d.ts +12 -0
  157. package/dist/detection/name-reach.js +28 -0
  158. package/dist/detection/name-reach.js.map +1 -0
  159. package/dist/detection/notebook-basis.d.ts +5 -7
  160. package/dist/detection/notebook-basis.js +6 -7
  161. package/dist/detection/notebook-basis.js.map +1 -1
  162. package/dist/detection/owed-selection.d.ts +51 -0
  163. package/dist/detection/owed-selection.js +169 -0
  164. package/dist/detection/owed-selection.js.map +1 -0
  165. package/dist/detection/portable.d.ts +34 -34
  166. package/dist/detection/portable.js +67 -93
  167. package/dist/detection/portable.js.map +1 -1
  168. package/dist/detection/reconciliation-coverage.d.ts +7 -0
  169. package/dist/detection/reconciliation-coverage.js +16 -0
  170. package/dist/detection/reconciliation-coverage.js.map +1 -0
  171. package/dist/detection/repository-publication.d.ts +20 -0
  172. package/dist/detection/repository-publication.js +79 -0
  173. package/dist/detection/repository-publication.js.map +1 -0
  174. package/dist/detection/repository-routes.d.ts +43 -0
  175. package/dist/detection/repository-routes.js +97 -0
  176. package/dist/detection/repository-routes.js.map +1 -0
  177. package/dist/detection/root-examination.d.ts +94 -0
  178. package/dist/detection/root-examination.js +523 -0
  179. package/dist/detection/root-examination.js.map +1 -0
  180. package/dist/detection/runtime.d.ts +131 -64
  181. package/dist/detection/runtime.js +808 -1103
  182. package/dist/detection/runtime.js.map +1 -1
  183. package/dist/detection/scoped-identity-port.d.ts +34 -0
  184. package/dist/detection/scoped-identity-port.js +81 -0
  185. package/dist/detection/scoped-identity-port.js.map +1 -0
  186. package/dist/detection/seal-law.d.ts +66 -0
  187. package/dist/detection/seal-law.js +116 -0
  188. package/dist/detection/seal-law.js.map +1 -0
  189. package/dist/detection/sharing-policy.d.ts +11 -0
  190. package/dist/detection/sharing-policy.js +32 -0
  191. package/dist/detection/sharing-policy.js.map +1 -0
  192. package/dist/detection/thread-database.d.ts +4 -0
  193. package/dist/detection/thread-database.js +30 -0
  194. package/dist/detection/thread-database.js.map +1 -0
  195. package/dist/detection/unreadable.d.ts +11 -0
  196. package/dist/detection/unreadable.js +9 -0
  197. package/dist/detection/unreadable.js.map +1 -0
  198. package/dist/detection/verified-content.d.ts +6 -0
  199. package/dist/detection/verified-content.js +12 -0
  200. package/dist/detection/verified-content.js.map +1 -0
  201. package/dist/detection/walk-law.d.ts +34 -0
  202. package/dist/detection/walk-law.js +55 -0
  203. package/dist/detection/walk-law.js.map +1 -0
  204. package/dist/development/fly-guest.js +2 -1
  205. package/dist/development/fly-guest.js.map +1 -1
  206. package/dist/entity-apply-host.d.ts +29 -13
  207. package/dist/entity-apply-host.js +1426 -461
  208. package/dist/entity-apply-host.js.map +1 -1
  209. package/dist/entity-apply-store.d.ts +77 -30
  210. package/dist/entity-apply-store.js +221 -516
  211. package/dist/entity-apply-store.js.map +1 -1
  212. package/dist/entity-record-observability.js +2 -0
  213. package/dist/entity-record-observability.js.map +1 -1
  214. package/dist/entity-record-saves.d.ts +20 -0
  215. package/dist/entity-record-saves.js +91 -0
  216. package/dist/entity-record-saves.js.map +1 -0
  217. package/dist/entity-record-store.d.ts +15 -7
  218. package/dist/entity-record-store.js +57 -117
  219. package/dist/entity-record-store.js.map +1 -1
  220. package/dist/files-register-host.d.ts +22 -3
  221. package/dist/files-register-host.js +70 -31
  222. package/dist/files-register-host.js.map +1 -1
  223. package/dist/filesystem.js +3 -2
  224. package/dist/filesystem.js.map +1 -1
  225. package/dist/git-binding.d.ts +65 -0
  226. package/dist/git-binding.js +220 -0
  227. package/dist/git-binding.js.map +1 -0
  228. package/dist/git-capture.d.ts +39 -0
  229. package/dist/git-capture.js +86 -0
  230. package/dist/git-capture.js.map +1 -0
  231. package/dist/git-card.d.ts +12 -0
  232. package/dist/git-card.js +123 -0
  233. package/dist/git-card.js.map +1 -0
  234. package/dist/git-checkpoint-construction.d.ts +41 -0
  235. package/dist/git-checkpoint-construction.js +96 -0
  236. package/dist/git-checkpoint-construction.js.map +1 -0
  237. package/dist/git-checkpoint-preparation.d.ts +37 -0
  238. package/dist/git-checkpoint-preparation.js +145 -0
  239. package/dist/git-checkpoint-preparation.js.map +1 -0
  240. package/dist/git-command.d.ts +61 -8
  241. package/dist/git-command.js +217 -63
  242. package/dist/git-command.js.map +1 -1
  243. package/dist/git-configuration.d.ts +40 -0
  244. package/dist/git-configuration.js +217 -0
  245. package/dist/git-configuration.js.map +1 -0
  246. package/dist/git-evidence.d.ts +61 -0
  247. package/dist/git-evidence.js +181 -0
  248. package/dist/git-evidence.js.map +1 -0
  249. package/dist/git-index-entries.d.ts +15 -0
  250. package/dist/git-index-entries.js +44 -0
  251. package/dist/git-index-entries.js.map +1 -0
  252. package/dist/git-index-format.d.ts +35 -0
  253. package/dist/git-index-format.js +293 -0
  254. package/dist/git-index-format.js.map +1 -0
  255. package/dist/git-index-witness.d.ts +33 -0
  256. package/dist/git-index-witness.js +69 -0
  257. package/dist/git-index-witness.js.map +1 -0
  258. package/dist/git-literal-host.d.ts +82 -0
  259. package/dist/git-literal-host.js +172 -0
  260. package/dist/git-literal-host.js.map +1 -0
  261. package/dist/git-literal-read.d.ts +88 -0
  262. package/dist/git-literal-read.js +170 -0
  263. package/dist/git-literal-read.js.map +1 -0
  264. package/dist/git-literal-replay.d.ts +33 -0
  265. package/dist/git-literal-replay.js +268 -0
  266. package/dist/git-literal-replay.js.map +1 -0
  267. package/dist/git-native-file.d.ts +42 -0
  268. package/dist/git-native-file.js +145 -0
  269. package/dist/git-native-file.js.map +1 -0
  270. package/dist/git-native-index.d.ts +21 -0
  271. package/dist/git-native-index.js +44 -0
  272. package/dist/git-native-index.js.map +1 -0
  273. package/dist/git-operation-host.d.ts +48 -0
  274. package/dist/git-operation-host.js +127 -0
  275. package/dist/git-operation-host.js.map +1 -0
  276. package/dist/git-private-index.d.ts +11 -0
  277. package/dist/git-private-index.js +42 -0
  278. package/dist/git-private-index.js.map +1 -0
  279. package/dist/git-private-repository.d.ts +4 -0
  280. package/dist/git-private-repository.js +69 -0
  281. package/dist/git-private-repository.js.map +1 -0
  282. package/dist/git-private-view.d.ts +39 -0
  283. package/dist/git-private-view.js +162 -0
  284. package/dist/git-private-view.js.map +1 -0
  285. package/dist/git-ref-store.d.ts +12 -0
  286. package/dist/git-ref-store.js +68 -0
  287. package/dist/git-ref-store.js.map +1 -0
  288. package/dist/git-registration-host.d.ts +20 -30
  289. package/dist/git-registration-host.js +86 -249
  290. package/dist/git-registration-host.js.map +1 -1
  291. package/dist/git-repository-host.d.ts +148 -22
  292. package/dist/git-repository-host.js +633 -650
  293. package/dist/git-repository-host.js.map +1 -1
  294. package/dist/git-worktree-host.d.ts +2 -0
  295. package/dist/git-worktree-host.js +16 -0
  296. package/dist/git-worktree-host.js.map +1 -0
  297. package/dist/graph-publication.d.ts +75 -0
  298. package/dist/graph-publication.js +203 -0
  299. package/dist/graph-publication.js.map +1 -0
  300. package/dist/ground-basis.d.ts +242 -0
  301. package/dist/ground-basis.js +1023 -0
  302. package/dist/ground-basis.js.map +1 -0
  303. package/dist/ground-checkpoint-worker.d.ts +1 -0
  304. package/dist/ground-checkpoint-worker.js +84 -0
  305. package/dist/ground-checkpoint-worker.js.map +1 -0
  306. package/dist/ground-checkpoints.d.ts +16 -0
  307. package/dist/ground-checkpoints.js +45 -0
  308. package/dist/ground-checkpoints.js.map +1 -0
  309. package/dist/ground-coordination.d.ts +107 -3
  310. package/dist/ground-coordination.js +249 -24
  311. package/dist/ground-coordination.js.map +1 -1
  312. package/dist/ground-database.d.ts +42 -0
  313. package/dist/ground-database.js +290 -0
  314. package/dist/ground-database.js.map +1 -0
  315. package/dist/ground-differences.d.ts +23 -0
  316. package/dist/ground-differences.js +47 -0
  317. package/dist/ground-differences.js.map +1 -0
  318. package/dist/ground-holders.d.ts +4 -0
  319. package/dist/ground-holders.js +15 -0
  320. package/dist/ground-holders.js.map +1 -0
  321. package/dist/ground-initialization-worker.d.ts +1 -0
  322. package/dist/ground-initialization-worker.js +20 -0
  323. package/dist/ground-initialization-worker.js.map +1 -0
  324. package/dist/ground-initialization.d.ts +35 -0
  325. package/dist/ground-initialization.js +102 -0
  326. package/dist/ground-initialization.js.map +1 -0
  327. package/dist/ground-install-saves.d.ts +63 -0
  328. package/dist/ground-install-saves.js +139 -0
  329. package/dist/ground-install-saves.js.map +1 -0
  330. package/dist/ground-inventory-schema.d.ts +18 -0
  331. package/dist/ground-inventory-schema.js +168 -0
  332. package/dist/ground-inventory-schema.js.map +1 -0
  333. package/dist/ground-profile.d.ts +7 -0
  334. package/dist/ground-profile.js +52 -0
  335. package/dist/ground-profile.js.map +1 -0
  336. package/dist/ground-root-index.d.ts +2 -1
  337. package/dist/ground-root-index.js +16 -14
  338. package/dist/ground-root-index.js.map +1 -1
  339. package/dist/ground-row.d.ts +62 -0
  340. package/dist/ground-row.js +70 -0
  341. package/dist/ground-row.js.map +1 -0
  342. package/dist/ground-save-ops.d.ts +44 -0
  343. package/dist/ground-save-ops.js +41 -0
  344. package/dist/ground-save-ops.js.map +1 -0
  345. package/dist/ground-saver-worker.d.ts +1 -0
  346. package/dist/ground-saver-worker.js +156 -0
  347. package/dist/ground-saver-worker.js.map +1 -0
  348. package/dist/ground-saver.d.ts +75 -0
  349. package/dist/ground-saver.js +216 -0
  350. package/dist/ground-saver.js.map +1 -0
  351. package/dist/ground-store.d.ts +84 -0
  352. package/dist/ground-store.js +473 -0
  353. package/dist/ground-store.js.map +1 -0
  354. package/dist/ground-territory.d.ts +130 -0
  355. package/dist/ground-territory.js +511 -0
  356. package/dist/ground-territory.js.map +1 -0
  357. package/dist/index.d.ts +1 -1
  358. package/dist/index.js.map +1 -1
  359. package/dist/installation/ownership.d.ts +0 -2
  360. package/dist/installation/ownership.js +3 -15
  361. package/dist/installation/ownership.js.map +1 -1
  362. package/dist/machine-host.d.ts +3 -2
  363. package/dist/machine-host.js +28 -16
  364. package/dist/machine-host.js.map +1 -1
  365. package/dist/materialized-graph.d.ts +7 -7
  366. package/dist/materialized-graph.js +11 -23
  367. package/dist/materialized-graph.js.map +1 -1
  368. package/dist/module-thread.d.ts +9 -0
  369. package/dist/module-thread.js +10 -0
  370. package/dist/module-thread.js.map +1 -0
  371. package/dist/native/fsevents-stream.node +0 -0
  372. package/dist/physical-path-witness.d.ts +64 -0
  373. package/dist/physical-path-witness.js +168 -0
  374. package/dist/physical-path-witness.js.map +1 -0
  375. package/dist/register-upload-fusion.d.ts +5 -4
  376. package/dist/register-upload-fusion.js +9 -6
  377. package/dist/register-upload-fusion.js.map +1 -1
  378. package/dist/registry-graph.d.ts +3 -2
  379. package/dist/registry-graph.js +9 -6
  380. package/dist/registry-graph.js.map +1 -1
  381. package/dist/registry-page-cache.d.ts +5 -2
  382. package/dist/registry-page-cache.js.map +1 -1
  383. package/dist/repository-identity-steps.d.ts +5 -0
  384. package/dist/repository-identity-steps.js +33 -0
  385. package/dist/repository-identity-steps.js.map +1 -0
  386. package/dist/runtime-node-flags.d.ts +10 -0
  387. package/dist/runtime-node-flags.js +11 -0
  388. package/dist/runtime-node-flags.js.map +1 -0
  389. package/dist/service/adapter.d.ts +1 -1
  390. package/dist/service/adapter.js +1 -1
  391. package/dist/service/adapter.js.map +1 -1
  392. package/dist/service/cli.js +16 -20
  393. package/dist/service/cli.js.map +1 -1
  394. package/dist/service/control.d.ts +2 -2
  395. package/dist/service/keepalive.d.ts +4 -11
  396. package/dist/service/keepalive.js +65 -118
  397. package/dist/service/keepalive.js.map +1 -1
  398. package/dist/service/policy.d.ts +36 -0
  399. package/dist/service/policy.js +25 -0
  400. package/dist/service/policy.js.map +1 -0
  401. package/dist/service/portable.d.ts +1 -1
  402. package/dist/service/portable.js +1 -1
  403. package/dist/service/portable.js.map +1 -1
  404. package/dist/service/recovery.d.ts +10 -9
  405. package/dist/service/recovery.js +29 -19
  406. package/dist/service/recovery.js.map +1 -1
  407. package/dist/service/windows-action.d.ts +5 -3
  408. package/dist/service/windows-action.js +6 -9
  409. package/dist/service/windows-action.js.map +1 -1
  410. package/dist/service/windows-task.js +5 -5
  411. package/dist/service/windows-task.js.map +1 -1
  412. package/dist/sharing-admission.d.ts +17 -0
  413. package/dist/sharing-admission.js +128 -0
  414. package/dist/sharing-admission.js.map +1 -0
  415. package/dist/snapshot-delivery-baseline.d.ts +12 -3
  416. package/dist/snapshot-delivery-baseline.js +41 -42
  417. package/dist/snapshot-delivery-baseline.js.map +1 -1
  418. package/dist/source-proof.d.ts +36 -0
  419. package/dist/source-proof.js +70 -0
  420. package/dist/source-proof.js.map +1 -0
  421. package/dist/source-signature.d.ts +56 -0
  422. package/dist/source-signature.js +56 -0
  423. package/dist/source-signature.js.map +1 -0
  424. package/dist/source-witness.d.ts +15 -0
  425. package/dist/source-witness.js +29 -0
  426. package/dist/source-witness.js.map +1 -0
  427. package/dist/sqlite-durability.d.ts +31 -0
  428. package/dist/sqlite-durability.js +28 -0
  429. package/dist/sqlite-durability.js.map +1 -0
  430. package/dist/sqlite.d.ts +8 -2
  431. package/dist/sqlite.js +27 -4
  432. package/dist/sqlite.js.map +1 -1
  433. package/dist/sync-address.d.ts +19 -0
  434. package/dist/sync-address.js +52 -0
  435. package/dist/sync-address.js.map +1 -0
  436. package/dist/sync-setting.d.ts +15 -0
  437. package/dist/sync-setting.js +13 -0
  438. package/dist/sync-setting.js.map +1 -1
  439. package/dist/turns.d.ts +42 -0
  440. package/dist/turns.js +112 -0
  441. package/dist/turns.js.map +1 -0
  442. package/dist/updates/bootstrap.d.ts +53 -7
  443. package/dist/updates/bootstrap.js +224 -137
  444. package/dist/updates/bootstrap.js.map +1 -1
  445. package/dist/updates/controller.d.ts +8 -0
  446. package/dist/updates/controller.js +27 -3
  447. package/dist/updates/controller.js.map +1 -1
  448. package/dist/updates/store.d.ts +3 -0
  449. package/dist/updates/store.js +12 -0
  450. package/dist/updates/store.js.map +1 -1
  451. package/dist/user-ground-host.d.ts +364 -69
  452. package/dist/user-ground-host.js +5231 -3366
  453. package/dist/user-ground-host.js.map +1 -1
  454. package/dist/watching/fsevents.d.ts +52 -1
  455. package/dist/watching/fsevents.js +60 -25
  456. package/dist/watching/fsevents.js.map +1 -1
  457. package/dist/watching/host.d.ts +129 -5
  458. package/dist/watching/host.js +433 -80
  459. package/dist/watching/host.js.map +1 -1
  460. package/dist/watching/index.d.ts +1 -1
  461. package/dist/wire-binary.d.ts +3 -2
  462. package/dist/wire-binary.js +10 -8
  463. package/dist/wire-binary.js.map +1 -1
  464. package/dist/wire-client.d.ts +3 -4
  465. package/dist/wire-client.js +18 -44
  466. package/dist/wire-client.js.map +1 -1
  467. package/dist/wire-outbox.d.ts +34 -0
  468. package/dist/wire-outbox.js +127 -0
  469. package/dist/wire-outbox.js.map +1 -0
  470. package/dist/workspace-host.d.ts +4 -0
  471. package/dist/workspace-host.js +12 -1
  472. package/dist/workspace-host.js.map +1 -1
  473. package/examinations/fixtures/private-wire-authority.js +133 -10
  474. package/npm-shrinkwrap.json +43 -56
  475. package/package.json +26 -13
@@ -1,809 +1,719 @@
1
+ import { makeRecordContentDurable } from "../content-cache-host.js";
1
2
  /**
2
3
  * The named Detect lane.
3
4
  *
4
5
  * A watcher path that still names enrolled, non-repository ground does not
5
6
  * justify a workspace walk. This runtime holds one SQLite connection,
6
- * resolves indexed paths, widens only to a named new folder's subtree, and
7
- * commits exact Live proposals plus local truth in one transaction. Evidence
8
- * it cannot prove is declined before mutation and goes to reconciliation in
9
- * the owning host.
7
+ * resolves indexed paths and commits exact Live proposals plus local truth
8
+ * in one transaction. Every examination ends in one typed outcome. Evidence it
9
+ * cannot prove is declined before mutation as a `widen` naming the missing
10
+ * proof and the scope that can supply it; a moved source is `retry`; a fault
11
+ * is thrown to the owning host, never converted into a wider scan.
10
12
  */
11
13
  import { createHash, randomUUID } from "node:crypto";
12
- import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs";
13
- import { join, posix, resolve } from "node:path";
14
- import { canonicalVersion, checkContentManifest, classifyFile, createLocalEntityRecord, createUserGroundEnrollmentPolicy, detectedChangeProposal, encodeEntityRecord, indexRepositoryTerritory, membershipHash, materializedSlotNames, portableFileMode, } from "@amalgm/live";
15
- import { openFileDatabase } from "../sqlite.js";
16
- import { captureContentFile, sealCapturedArtifact } from "../content-cache-host.js";
17
- import { captureRepository, hasGitMarker, inspectRepositoryTransportFile, readCachedRepositoryIdentity, sealCachedRepositoryIdentity, } from "../git-repository-host.js";
18
- import { inspectGitRegistration, sameGitIdentity, } from "../git-registration-host.js";
14
+ import { lstatSync, readlinkSync } from "node:fs";
15
+ import { join, posix } from "node:path";
16
+ import { AMALGM_IGNORE_FILE, artifactForRecord, canonicalVersion, childGroupOwed, classifyFile, createLocalEntityRecord, detectedChangeProposal, encodeEntityRecord, observedPortableName, portableFileMode, resolveScopedLeaves, selectPublication, PublicationScopeExceeded, } from "@amalgm/live";
17
+ import { openGroundConnection } from "../ground-database.js";
18
+ import { save } from "../ground-saver.js";
19
+ import { activeUnder, effectivelyActive, GROUND_ROW_PROJECTION, localParentOf, portableGroundRecord as portable } from "../ground-row.js";
20
+ import { groundStore } from "../ground-store.js";
21
+ import { PhysicalPathWitnesses } from "../physical-path-witness.js";
22
+ import { GroundBasisChangedError } from "../ground-basis.js";
23
+ import { GroundEffectCrossedError, GroundFootprintExceededError, PhysicalKeySet } from "../ground-coordination.js";
24
+ import { physicalKey, sourceFingerprint, sourceReusable } from "../source-signature.js";
25
+ import { closePhysically } from "../source-proof.js";
26
+ import { leafBoundary, stampContentClock } from "../content-clock.js";
27
+ import { repositoryGroundCones } from "../git-binding.js";
28
+ import { eachInTurns, settleInTurns, turnBoundary } from "../turns.js";
29
+ import { CargoGroup } from "../cargo-group.js";
30
+ import { CaptureSourceChangedError } from "../content-cache-host.js";
31
+ import { captureContent, contentManifest, readOwedAddresses, replayCachedContent, sealContent } from "../content-capture-pool.js";
32
+ import { hasGitMarker } from "../git-repository-host.js";
33
+ import { referenceWorkspaceId } from "../files-register-host.js";
19
34
  import { AcceptedInboxResultIndex } from "./accepted-result.js";
20
- import { sameNotebookBasisRow } from "./notebook-basis.js";
21
- import { exactTextReplay } from "./portable.js";
22
- const NAMED_WALK_YIELD_ENTRIES = 256;
23
- const ROW_SELECT = `
24
- SELECT resource_id AS resourceId, root_uuid AS rootUUID,
25
- uuid, type, parent_uuid AS parentUUID, name, status, version,
26
- payload_version AS payloadVersion, transport_version AS transportVersion,
27
- relative_path AS relativePath, absolute_path AS absolutePath,
28
- device_number AS deviceNumber, inode,
29
- byte_size AS byteSize, modified_time_ms AS modifiedTimeMs,
30
- changed_time_ms AS changedTimeMs, filesystem_mode AS filesystemMode,
31
- content_verified_at_ms AS contentVerifiedAtMs
32
- FROM detection_notebook`;
33
- const ROW_COLUMNS = [
34
- "uuid", "resource_id", "root_uuid", "type", "parent_uuid", "name", "status", "version",
35
- "payload_version", "transport_version", "relative_path", "absolute_path", "device_number",
36
- "inode", "byte_size", "modified_time_ms", "changed_time_ms", "filesystem_mode",
37
- "content_verified_at_ms",
38
- ].join(", ");
39
- const rowValues = (row) => [
40
- row.uuid, row.resourceId, row.rootUUID, row.type, row.parentUUID, row.name, row.status,
41
- row.version, row.payloadVersion, row.transportVersion, row.relativePath, row.absolutePath,
42
- row.deviceNumber, row.inode, row.byteSize, row.modifiedTimeMs, row.changedTimeMs,
43
- row.filesystemMode, row.contentVerifiedAtMs,
44
- ];
45
- const upsert = (database, table) => database.prepare(`
46
- INSERT INTO ${table}(${ROW_COLUMNS})
47
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
48
- ON CONFLICT(uuid) DO UPDATE SET
49
- resource_id = excluded.resource_id,
50
- root_uuid = excluded.root_uuid,
51
- type = excluded.type,
52
- parent_uuid = excluded.parent_uuid,
53
- name = excluded.name,
54
- status = excluded.status,
55
- version = excluded.version,
56
- payload_version = excluded.payload_version,
57
- transport_version = excluded.transport_version,
58
- relative_path = excluded.relative_path,
59
- absolute_path = excluded.absolute_path,
60
- device_number = excluded.device_number,
61
- inode = excluded.inode,
62
- byte_size = excluded.byte_size,
63
- modified_time_ms = excluded.modified_time_ms,
64
- changed_time_ms = excluded.changed_time_ms,
65
- filesystem_mode = excluded.filesystem_mode,
66
- content_verified_at_ms = excluded.content_verified_at_ms
67
- `);
68
- const portable = (row) => {
69
- const derived = row.type === "workspace" || row.type === "folder";
70
- return {
71
- uuid: row.uuid,
72
- type: row.type,
73
- parentUUID: row.parentUUID,
74
- name: row.name,
75
- status: row.status,
76
- version: row.version,
77
- payloadVersion: derived ? null : row.payloadVersion,
78
- fileMode: portableFileMode(row.type, row.filesystemMode),
79
- transportVersion: derived ? null : row.transportVersion,
80
- };
35
+ import { aliasExaminations } from "./alias-examination.js";
36
+ import { NamedDirectoryProof } from "./directory-proof.js";
37
+ import { finalizeContainers, membershipParents } from "./container-membership.js";
38
+ import { scopedIdentityPort } from "./scoped-identity-port.js";
39
+ import { retainContentVerification } from "./verified-content.js";
40
+ import { NamedGroundReader } from "./ground-reader.js";
41
+ import { readSharingPolicy } from "./sharing-policy.js";
42
+ import { GitNoticeBindings, GitNoticeChanged } from "./git-notice.js";
43
+ import { repositoryRoutes } from "./repository-routes.js";
44
+ import { ApplyAttributionDeferred, ApplyOwnershipSnapshot, ownedObject, sqliteApplyOwnership } from "./apply-ownership.js";
45
+ import { admissionRejection } from "./dispositions.js";
46
+ import { denied } from "./unreadable.js";
47
+ /** One named attempt's phases, in order: reader and ownership snapshot,
48
+ * repository membership, Git routing and directory proofs, address checks
49
+ * and byte reads, cache durability, the semantic close, the in-process ready
50
+ * registration, the admission transaction, and what a changed policy newly
51
+ * includes. */
52
+ const NAMED_PHASES = ["open", "members", "route", "collect", "durable", "close", "ready", "admit", "include"];
53
+ const nothing = { evidence: [], observedEntityIds: [] };
54
+ const widen = (proof, roots, detail) => ({ ...nothing, kind: "widen", proof, roots, ...(detail ? { detail } : {}) });
55
+ const retry = (reason, after = "timer") => ({ ...nothing, kind: "retry", reason, after });
56
+ const LEAF_TYPES = new Set(["file.text", "file.binary", "link"]);
57
+ const parentPath = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
58
+ /** Where an escaped context member lives, for the widen receipt. */
59
+ const contextPath = (context, uuid) => [...context.observed, ...context.before].find(row => row.uuid === uuid)?.relativePath ?? uuid;
60
+ const required = (row, uuid) => {
61
+ if (!row)
62
+ throw new Error(`named Detect has no row for proved parent ${uuid}`);
63
+ return row;
81
64
  };
65
+ /** Evidence from one committed examination decides its settled kind. */
66
+ function settled(evidence, observedEntityIds, excluded) {
67
+ const kind = evidence.some(item => item.records > 0) ? "recorded" : excluded ? "excluded" : "unchanged";
68
+ return { kind, evidence, observedEntityIds };
69
+ }
70
+ const ROW_SELECT = `SELECT ${GROUND_ROW_PROJECTION} FROM entities`;
82
71
  const sha256Hex = (input) => createHash("sha256").update(input).digest("hex");
83
- const sameFingerprint = (row, stats) => row.deviceNumber === stats.dev
84
- && row.inode === stats.ino
85
- && row.byteSize === stats.size
86
- && row.modifiedTimeMs === stats.mtimeMs
87
- && row.changedTimeMs === stats.ctimeMs
88
- && row.filesystemMode === stats.mode;
89
- /** One persistent, prepared Detect store. It owns no Watch or Send behavior. */
72
+ /** One persistent, prepared Detect store. It owns no Watch or Send behavior;
73
+ * attempts on different roots run concurrently, each with its own state. */
90
74
  export class NamedDetectRuntime {
75
+ store;
76
+ constructor(databaseFile, cacheDir, bindingDir, onRecordCommit, measureGround = () => false, onDetectCommit,
77
+ /** Rings a checkout whose Git binding was refreshed after its witness moved. */
78
+ rebound) {
79
+ const database = openGroundConnection(databaseFile);
80
+ this.store = {
81
+ database,
82
+ cacheDir,
83
+ bindingDir,
84
+ rowAtAbsolutePath: database.prepare(`${ROW_SELECT} WHERE absolute_path = ? AND status = 'active'`),
85
+ receivedThrough: database.prepare(`
86
+ SELECT received_through AS receivedThrough
87
+ FROM entity_receive_cursors
88
+ WHERE authority_id = ? AND entity_id = ?
89
+ `),
90
+ acceptedInbox: new AcceptedInboxResultIndex(database),
91
+ measureGround,
92
+ ...(onRecordCommit ? { onRecordCommit } : {}),
93
+ ...(onDetectCommit ? { onDetectCommit } : {}),
94
+ gitBindings: new GitNoticeBindings(rebound),
95
+ };
96
+ }
97
+ /** Startup discovery binds every enrolled repository in the background, so
98
+ * no first notice pays for all of them. */
99
+ discover() {
100
+ const { value } = groundStore(this.store.database).readSnapshot(reader => reader.repositoryMembers());
101
+ this.store.gitBindings.discover(value);
102
+ }
103
+ close() {
104
+ this.store.database.close();
105
+ }
106
+ /** The repositories escalated names reach, by this runtime's Git bindings. */
107
+ repositoryRoutes(directory, paths) {
108
+ return repositoryRoutes(this.store.database, this.store.gitBindings, directory, paths);
109
+ }
110
+ /** Named names split by the lane that proves them. A name in Git storage or
111
+ * inside an indexed repository belongs to its repository's examination;
112
+ * every other name is proved as a named leaf. The index is only a routing
113
+ * hint: each lane re-reads the real routes, and a wrong hint widens. */
114
+ repositoryNames(directory, paths) {
115
+ const leaves = [], repositories = [];
116
+ const nearest = indexedRepositories(this.store.rowAtAbsolutePath, directory);
117
+ for (const path of paths) {
118
+ (nearest(path) !== null || path.split("/").includes(".git") ? repositories : leaves).push(path);
119
+ }
120
+ return { leaves, repositories };
121
+ }
122
+ /** A blocked batch does not own unrelated named leaves. Retry its independent
123
+ * names without settling the original Watch generation. */
124
+ detectAvailable(observation, register, lease) {
125
+ return new NamedDetectAttempt(this.store, register, lease).detectGroups(observation);
126
+ }
127
+ /** The physical footprint a lane's proof of these paths reads: each path's
128
+ * subtree, or its nearest indexed repository's whole root and any Git
129
+ * storage kept outside it, plus the root's enrollment policy. Ancestors
130
+ * need no lease of their own: an effect on any ancestor overlaps these
131
+ * cones. An index that changes before admission fails the ground basis
132
+ * there; storage the observed binding names must lie inside the lease. */
133
+ footprint(directory, paths) {
134
+ const cones = new Set([join(directory, AMALGM_IGNORE_FILE)]);
135
+ const nearest = indexedRepositories(this.store.rowAtAbsolutePath, directory);
136
+ const repositories = new Set();
137
+ for (const path of paths) {
138
+ const repository = nearest(path);
139
+ if (repository === null)
140
+ cones.add(join(directory, path));
141
+ else
142
+ repositories.add(repository.path);
143
+ }
144
+ // Every name in one repository shares its cones: they are read once.
145
+ for (const repository of repositories) {
146
+ for (const cone of repositoryGroundCones(join(directory, repository)))
147
+ cones.add(cone);
148
+ }
149
+ return [...cones];
150
+ }
151
+ }
152
+ /** The nearest indexed repository enclosing each named path, root-relative,
153
+ * each ancestor looked up once for all the names. It is a lane and footprint
154
+ * hint; each proof re-reads the real routes. */
155
+ function indexedRepositories(rowAtAbsolutePath, directory) {
156
+ const known = new Map();
157
+ const nearest = (at) => {
158
+ let found = known.get(at);
159
+ if (found !== undefined)
160
+ return found;
161
+ const row = rowAtAbsolutePath.get(join(directory, at));
162
+ found = row?.type === "repo.git" ? { path: at, uuid: row.uuid }
163
+ : at === "" ? null : nearest(at.includes("/") ? at.slice(0, at.lastIndexOf("/")) : "");
164
+ known.set(at, found);
165
+ return found;
166
+ };
167
+ return nearest;
168
+ }
169
+ /** One root's named proof: its reader, ownership snapshot, Git proof,
170
+ * collection registration and ground lease belong to this attempt alone. */
171
+ class NamedDetectAttempt {
172
+ register;
173
+ lease;
91
174
  database;
92
175
  cacheDir;
93
- userRoot;
94
- rootAtDirectory;
95
- rowAtPath;
96
- rowAtUuid;
97
- rowsUnderPath;
98
- rowsAtPhysicalIdentity;
99
- notebookChildren;
100
- materializedSiblings;
101
- /** One frozen sibling snapshot per Detect pass: collision-derived names
102
- * are computed once per parent, never once per captured file. */
103
- slotNamesByParent = new Map();
104
- materializedByUuid;
105
- writeEntity;
106
- writeNotebook;
107
- appendRecord;
176
+ bindingDir;
177
+ rowAtAbsolutePath;
108
178
  receivedThrough;
109
179
  acceptedInbox;
180
+ measureGround;
110
181
  onRecordCommit;
111
- userPolicy;
112
- walkedEntries = 0;
113
- constructor(databaseFile, cacheDir, userRoot, onRecordCommit) {
114
- this.database = openFileDatabase(databaseFile);
115
- this.database.pragma("busy_timeout = 5000");
116
- this.database.pragma("journal_mode = WAL");
117
- this.database.pragma("synchronous = FULL");
118
- this.cacheDir = cacheDir;
119
- this.userRoot = resolve(userRoot);
120
- this.userPolicy = this.readUserPolicy();
121
- this.rootAtDirectory = this.database.prepare(`${ROW_SELECT} WHERE absolute_path = ? AND relative_path = '' AND status = 'active' LIMIT 1`);
122
- this.rowAtPath = this.database.prepare(`${ROW_SELECT} WHERE root_uuid = ? AND relative_path = ? AND status = 'active' LIMIT 1`);
123
- this.rowAtUuid = this.database.prepare(`${ROW_SELECT} WHERE uuid = ? LIMIT 1`);
124
- this.rowsUnderPath = this.database.prepare(`${ROW_SELECT}
125
- WHERE root_uuid = ? AND status = 'active'
126
- AND (? = '' OR relative_path = ?
127
- OR substr(relative_path, 1, length(?) + 1) = ? || '/')
128
- ORDER BY relative_path`);
129
- this.rowsAtPhysicalIdentity = this.database.prepare(`${ROW_SELECT} WHERE device_number = ? AND inode = ? AND status = 'active' ORDER BY uuid`);
130
- this.notebookChildren = this.database.prepare(`
131
- SELECT uuid, name FROM detection_notebook
132
- WHERE parent_uuid = ? AND status = 'active' ORDER BY name, uuid
133
- `);
134
- this.materializedSiblings = this.database.prepare(`${ROW_SELECT} WHERE parent_uuid = ? AND status = 'active' ORDER BY name, uuid`);
135
- this.materializedByUuid = this.database.prepare("SELECT 1 AS present FROM entities WHERE uuid = ?");
136
- this.writeEntity = upsert(this.database, "entities");
137
- this.writeNotebook = upsert(this.database, "detection_notebook");
138
- this.appendRecord = this.database.prepare(`
139
- INSERT INTO record_outbox(
140
- mutation_id, authority_id, entity_id, record_json, status,
141
- global_sequence, created_at, accepted_at
142
- ) VALUES (?, ?, ?, ?, 'pending', NULL, ?, NULL)
143
- `);
144
- this.receivedThrough = this.database.prepare(`
145
- SELECT received_through AS receivedThrough
146
- FROM entity_receive_cursors
147
- WHERE authority_id = ? AND entity_id = ?
148
- `);
149
- this.acceptedInbox = new AcceptedInboxResultIndex(this.database);
150
- this.onRecordCommit = onRecordCommit;
182
+ onDetectCommit;
183
+ gitBindings;
184
+ observation = null;
185
+ ownership = null;
186
+ gitProof = null;
187
+ /** The root's content-clock stamp, taken before this observation's first
188
+ * byte: every leaf it reads carries it. */
189
+ clock = null;
190
+ /** What this observation's reads staged, named together before its
191
+ * Record needs them (cargo-group.ts). */
192
+ cargo = null;
193
+ phaseStarted = 0;
194
+ phases = new Map();
195
+ get ground() {
196
+ if (!this.observation)
197
+ throw new Error("named ground read outside its observation");
198
+ return this.observation;
151
199
  }
152
- close() {
153
- this.database.close();
200
+ constructor(store,
201
+ /** Registers each collection this attempt brings to admission. */
202
+ register, lease) {
203
+ this.register = register;
204
+ this.lease = lease;
205
+ this.database = store.database;
206
+ this.cacheDir = store.cacheDir;
207
+ this.bindingDir = store.bindingDir;
208
+ this.rowAtAbsolutePath = store.rowAtAbsolutePath;
209
+ this.receivedThrough = store.receivedThrough;
210
+ this.acceptedInbox = store.acceptedInbox;
211
+ this.measureGround = store.measureGround;
212
+ if (store.onRecordCommit)
213
+ this.onRecordCommit = store.onRecordCommit;
214
+ if (store.onDetectCommit)
215
+ this.onDetectCommit = store.onDetectCommit;
216
+ this.gitBindings = store.gitBindings;
217
+ }
218
+ /** Ends a phase; a phase reached more than once accumulates. */
219
+ mark(phase) {
220
+ const now = performance.now();
221
+ this.phases.set(phase, (this.phases.get(phase) ?? 0) + now - this.phaseStarted);
222
+ this.phaseStarted = now;
223
+ }
224
+ timings() {
225
+ const git = this.gitProof;
226
+ return {
227
+ phases: Object.fromEntries(NAMED_PHASES.flatMap(phase => this.phases.has(phase) ? [[phase, this.phases.get(phase)]] : [])),
228
+ git: git ? { discoveryWaitMs: git.discoveryWaitMs, routeMs: git.routeMs(), semanticMs: git.semanticMs() } : null,
229
+ };
230
+ }
231
+ async detect(observation) {
232
+ if (observation.suspicion.paths === null) {
233
+ throw new Error("named Detect requires named suspicion; complete roots belong to reconciliation");
234
+ }
235
+ this.phaseStarted = performance.now();
236
+ this.phases = new Map();
237
+ this.observation = new NamedGroundReader(this.database);
238
+ this.cargo = CargoGroup.open();
239
+ try {
240
+ return { ...await this.attempt(observation), timings: this.timings() };
241
+ }
242
+ finally {
243
+ await this.cargo.abandon();
244
+ this.observation = null;
245
+ this.ownership = null;
246
+ this.gitProof = null;
247
+ this.clock = null;
248
+ this.cargo = null;
249
+ }
154
250
  }
155
- refreshEnrollmentPolicy() {
156
- this.userPolicy = this.readUserPolicy();
251
+ async attempt(observation) {
252
+ try {
253
+ ({ boundary: this.clock } = await stampContentClock(this.database.name, observation.directory));
254
+ this.ownership = new ApplyOwnershipSnapshot(sqliteApplyOwnership(this.database));
255
+ this.mark("open");
256
+ return await this.observe([observation]);
257
+ }
258
+ catch (error) {
259
+ if (error instanceof GitNoticeChanged)
260
+ return retry("git-notice-changed");
261
+ if (error instanceof GroundBasisChangedError)
262
+ return retry("ground-basis-changed");
263
+ if (error instanceof GroundEffectCrossedError)
264
+ return retry("ground-effect-crossed", "ground-release");
265
+ if (error instanceof GroundFootprintExceededError)
266
+ return retry("ground-footprint-exceeded", "ground-release");
267
+ if (error instanceof CaptureSourceChangedError)
268
+ return retry(`source-changed: ${error.message}`);
269
+ if (error instanceof ApplyAttributionDeferred)
270
+ return { ...nothing, kind: "deferred", owners: error.owners };
271
+ if (denied(error))
272
+ return widen("unreadable", [observation.directory]);
273
+ throw error;
274
+ }
157
275
  }
158
- async detect(observations) {
159
- this.slotNamesByParent.clear();
160
- if (observations.some(({ suspicion }) => suspicion.paths === null)) {
161
- return { handled: false, evidence: [] };
276
+ async detectGroups(observation) {
277
+ const first = await this.detect(observation);
278
+ const paths = observation.suspicion.paths;
279
+ if (first.kind !== "deferred" || !paths || paths.length < 2)
280
+ return first;
281
+ const groups = new Map();
282
+ for (const path of paths) {
283
+ const absolute = join(observation.directory, path);
284
+ if (first.owners.some(owner => owner.cones.some(cone => absolute === cone || absolute.startsWith(`${cone}/`))))
285
+ continue;
286
+ // The real named proof below still re-reads ancestors, identities and ownership.
287
+ const row = this.rowAtAbsolutePath.get(absolute);
288
+ const ordinaryLeaf = row !== undefined && ["file.text", "file.binary", "link"].includes(row.type);
289
+ const key = ordinaryLeaf ? "ordinary-leaves" : `entry:${path}`;
290
+ const group = groups.get(key) ?? [];
291
+ group.push(path);
292
+ groups.set(key, group);
162
293
  }
163
- const repository = await this.detectRepository(observations);
164
- if (repository)
165
- return repository;
294
+ const evidence = [], observedEntityIds = [], examine = [];
295
+ // A committed group's owed examinations outlive this batch's answer.
296
+ const progress = () => ({ evidence, observedEntityIds, ...(examine.length ? { examine } : {}) });
297
+ const owners = [...first.owners];
298
+ let widened = null, moved = null;
299
+ for (const paths of groups.values()) {
300
+ const result = await this.detect({ ...observation, suspicion: { ...observation.suspicion, paths } });
301
+ for (const item of result.evidence)
302
+ evidence.push(item);
303
+ for (const uuid of result.observedEntityIds)
304
+ observedEntityIds.push(uuid);
305
+ for (const path of result.examine ?? [])
306
+ examine.push(path);
307
+ if (result.kind === "widen")
308
+ widened ??= result.proof;
309
+ // A racy source waits for its timer; an immediate rerun would read it again.
310
+ if (result.kind === "retry" && (!moved || result.after === "timer"))
311
+ moved = result;
312
+ if (result.kind === "deferred")
313
+ owners.push(...result.owners);
314
+ }
315
+ // Deferred names still hold this root, so it cannot be reconciled alone
316
+ // now: remaining unproved names need the domain continuation.
317
+ if (widened)
318
+ return { kind: "widen", proof: widened, roots: "domain", ...progress() };
319
+ if (moved)
320
+ return { kind: "retry", reason: moved.reason, after: moved.after, ...progress() };
321
+ return { kind: "deferred", owners, ...progress() };
322
+ }
323
+ requireAttribution(scope) {
324
+ const owners = this.ownership.use(scope);
325
+ if (owners.length)
326
+ throw new ApplyAttributionDeferred(owners);
327
+ }
328
+ async observe(observations) {
329
+ if (observations.every(({ suspicion }) => suspicion.paths?.length === 0)) {
330
+ return { ...nothing, kind: "unchanged" };
331
+ }
332
+ const members = this.ground.repositoryMembers();
333
+ this.mark("members");
334
+ this.gitProof = await this.gitBindings.observe(members, uuid => this.ground.physical(uuid));
335
+ const wider = new Set();
336
+ for (const observation of observations)
337
+ for (const path of observation.suspicion.paths) {
338
+ const route = this.gitProof.route(join(observation.directory, path));
339
+ if (route.kind !== "reconcile")
340
+ continue;
341
+ wider.add(observation.directory);
342
+ for (const uuid of route.rootUUIDs) {
343
+ const root = this.ground.physical(uuid);
344
+ if (!root || root.relativePath !== "")
345
+ throw new Error("Git notice consumer has no materialized root");
346
+ wider.add(root.absolutePath);
347
+ }
348
+ }
349
+ if (wider.size)
350
+ return widen("git-notice-consumer", [...wider]);
351
+ const contexts = new Map();
352
+ for (const observation of observations) {
353
+ if (observation.suspicion.paths?.length === 0)
354
+ continue;
355
+ const root = this.ground.root(observation.directory);
356
+ if (!root)
357
+ return widen("unindexed-root", [observation.directory]);
358
+ const context = new NamedDirectoryProof({
359
+ lookup: (path, parent) => {
360
+ const row = parent ? this.ground.atPath(root.rootUUID, path).find(row => activeUnder(row, parent))
361
+ : this.activeRowAtPath(root.rootUUID, path);
362
+ this.requireAttribution({ entries: [join(observation.directory, path)], ...(row ? { uuids: new Set([row.uuid]) } : {}) });
363
+ return row;
364
+ },
365
+ read: path => {
366
+ const absolutePath = join(observation.directory, ...path.split("/"));
367
+ const stats = tryLstat(absolutePath);
368
+ return stats?.isDirectory() && !stats.isSymbolicLink()
369
+ ? { physicalKey: physicalKey(stats), repository: hasGitMarker(absolutePath) }
370
+ : null;
371
+ },
372
+ });
373
+ contexts.set(observation.directory, context);
374
+ for (const path of observation.suspicion.paths) {
375
+ // Each name's directory proof is its own reads: names are boundaries.
376
+ await turnBoundary();
377
+ this.requireAttribution({ entries: [join(observation.directory, path)] });
378
+ if (!context.named(path))
379
+ return widen("named-directory", [observation.directory]);
380
+ }
381
+ }
382
+ this.mark("route");
383
+ const result = await this.detectWithContext(observations, contexts, new PhysicalPathWitnesses());
384
+ return { ...result, evidence: result.evidence.map(evidence => ({
385
+ ...evidence,
386
+ metadataReads: evidence.metadataReads + (contexts.get(evidence.directory)?.metadataReads ?? 0),
387
+ })) };
388
+ }
389
+ async detectWithContext(observations, contexts, sources) {
166
390
  const captured = [];
167
- const outgoing = new Set();
168
- const membershipParents = new Set();
391
+ const acceptedBasis = this.acceptedInbox.snapshot();
169
392
  const evidence = [];
170
- const prepared = [];
171
- let repositoryBoundary = false;
393
+ const policies = new Map();
394
+ const examine = new Set();
395
+ // Roots whose policy this attempt reads as a named leaf.
396
+ const policyChanges = [];
397
+ const widened = (proof, detail) => widen(proof, observations.filter(item => item.suspicion.paths?.length).map(item => item.directory), detail);
398
+ let excluded = 0;
172
399
  for (const observation of observations) {
173
400
  const paths = observation.suspicion.paths;
174
- if (paths.length === 0)
401
+ if (!paths.length)
175
402
  continue;
176
- if (paths.some((path) => path === ".amalgmignore"
177
- || path === ".git" || path.startsWith(".git/") || path.includes("/.git/")
178
- || path.endsWith("/.git"))) {
179
- return { handled: false, evidence: [] };
180
- }
181
- const root = this.rootAtDirectory.get(observation.directory);
403
+ const root = this.ground.root(observation.directory);
182
404
  if (!root)
183
- return { handled: false, evidence: [] };
405
+ return widened("unindexed-root");
406
+ const policy = readSharingPolicy(observation.directory);
407
+ policies.set(root.uuid, policy);
408
+ if (paths.includes(AMALGM_IGNORE_FILE)) {
409
+ policyChanges.push({ directory: observation.directory, rootUUID: root.rootUUID, text: policy.text });
410
+ }
184
411
  const started = performance.now();
185
- const scopedPaths = paths.filter((path) => !paths.some((candidate) => candidate !== path && path.startsWith(`${candidate}/`)));
186
- const policy = resolve(observation.directory) === this.userRoot
187
- ? this.userPolicy
188
- : () => true;
189
- const entries = scopedPaths.filter(policy).map((path) => {
190
- const exact = this.rowAtPath.get(root.rootUUID, path);
412
+ const first = captured.length;
413
+ // 1. Classify every name from metadata alone. Structure beyond ordinary
414
+ // leaves, and every control or repository consumer, needs wider proof.
415
+ const leaves = [];
416
+ const absent = [];
417
+ for (const path of paths) {
418
+ await turnBoundary();
419
+ if (path.split("/").includes(".git"))
420
+ return widened("structural-path", path);
421
+ if (path === "")
422
+ return widened("root-path");
423
+ const exact = this.activeRowAtPath(root.rootUUID, path);
191
424
  const absolutePath = join(observation.directory, ...path.split("/"));
425
+ this.requireAttribution({ entries: [absolutePath], ...(exact ? { uuids: new Set([exact.uuid]) } : {}) });
192
426
  const stats = tryLstat(absolutePath);
193
- if (!stats && exact) {
194
- if (this.enclosedByRepository(exact)) {
195
- repositoryBoundary = true;
196
- }
197
- }
198
- return { path, absolutePath, exact, stats };
199
- });
200
- if (entries.some(({ exact, stats }) => exact !== undefined && stats === null)) {
201
- // An absent named address is not enough to distinguish deletion from
202
- // a move whose destination doorbell arrives later (or never arrives
203
- // on this platform). Reconcile the complete materialized root while
204
- // the old row still supplies physical identity evidence.
205
- return {
206
- handled: false,
207
- evidence: [],
208
- widenedRoots: [observation.directory],
209
- };
210
- }
211
- const moveIntoRepository = entries.some(({ path, absolutePath, exact, stats }) => {
212
- if (exact !== undefined || stats === null)
213
- return false;
214
- const portableParent = posix.dirname(path);
215
- const parentPath = portableParent === "." ? "" : portableParent;
216
- const parent = this.rowAtPath.get(root.rootUUID, parentPath);
217
- if (!parent || (parent.type !== "repo.git" && !this.enclosedByRepository(parent))) {
218
- return false;
427
+ if (stats) {
428
+ const owners = this.ownership.physical(ownedObject(observation.directory, physicalKey(stats)));
429
+ if (owners.length)
430
+ throw new ApplyAttributionDeferred(owners);
219
431
  }
220
- return this.uniqueMovedBase(stats.dev, stats.ino, absolutePath) !== null;
221
- });
222
- if (moveIntoRepository) {
223
- // A destination-only event can cross from the ordinary file rail into
224
- // repository territory. The complete root is the smallest proof that
225
- // can remove the old address and add the new repo-owned address in one
226
- // identity reconciliation.
227
- return {
228
- handled: false,
229
- evidence: [],
230
- widenedRoots: [observation.directory],
231
- };
232
- }
233
- if (repositoryBoundary)
234
- return { handled: false, evidence: [] };
235
- prepared.push({ observation, root, paths, policy, started, entries });
236
- }
237
- for (const item of prepared) {
238
- const { observation, root, paths, policy, started, entries } = item;
239
- let metadataReads = entries.length;
240
- let contentReads = 0;
241
- let recordBytes = 0;
242
- const beforeCount = captured.length;
243
- for (const { path, absolutePath, exact, stats } of entries) {
432
+ sources.remember(absolutePath, stats);
433
+ if (exact ? this.enclosedByRepository(exact)
434
+ : this.repositoryAtPath(root.rootUUID, path, observation.directory))
435
+ return widened("repository-enclosed", path);
244
436
  if (!stats) {
437
+ absent.push(path);
245
438
  continue;
246
439
  }
247
- const portableParent = posix.dirname(path);
248
- const parentPath = portableParent === "." ? "" : portableParent;
249
- const parent = this.rowAtPath.get(root.rootUUID, parentPath);
250
- if (!parent || !["workspace", "folder"].includes(parent.type)
251
- || this.enclosedByRepository(parent)) {
252
- return { handled: false, evidence: [] };
253
- }
254
- let scope;
255
- if (stats.isFile()) {
256
- if (exact && !["file.text", "file.binary"].includes(exact.type)) {
257
- return { handled: false, evidence: [] };
258
- }
259
- const file = await this.captureFile({
260
- root, path, absolutePath, stats, parent, exact,
261
- });
262
- scope = {
263
- items: file.item ? [file.item] : [],
264
- metadataReads: 1,
265
- contentReads: file.contentReads,
266
- };
267
- }
268
- else if (stats.isSymbolicLink()) {
269
- if (exact?.type === "reference"
270
- || (resolve(observation.directory) === this.userRoot
271
- && (path === "workspaces" || path.startsWith("workspaces/")))) {
272
- return { handled: false, evidence: [] };
273
- }
274
- const link = this.captureLink({
275
- root, path, absolutePath, stats, parent, exact,
276
- });
277
- if (!link)
278
- return { handled: false, evidence: [] };
279
- scope = {
280
- items: link.item ? [link.item] : [],
281
- metadataReads: 1,
282
- contentReads: link.contentReads,
283
- };
284
- }
285
- else if (stats.isDirectory() && !stats.isSymbolicLink()) {
286
- const directory = await this.captureDirectory({
287
- root,
288
- path,
289
- absolutePath,
290
- stats,
291
- parent,
292
- policy,
293
- });
294
- if (!directory)
295
- return { handled: false, evidence: [] };
296
- scope = directory;
297
- }
298
- else {
299
- return { handled: false, evidence: [] };
300
- }
301
- metadataReads += scope.metadataReads - 1;
302
- contentReads += scope.contentReads;
303
- for (const item of scope.items) {
304
- if (item.proposal
305
- && !this.acceptedInbox.covers(item.result.resourceId, item.proposal)) {
306
- outgoing.add(item.proposal);
307
- recordBytes += Buffer.byteLength(this.previewRecordJson(item.result.resourceId, item.proposal));
308
- }
309
- captured.push(item);
310
- if (item.base === null
311
- || item.base.parentUUID !== item.result.parentUUID
312
- || item.base.name !== item.result.name) {
313
- if (item.base?.parentUUID)
314
- membershipParents.add(item.base.parentUUID);
315
- if (item.result.parentUUID)
316
- membershipParents.add(item.result.parentUUID);
317
- }
318
- }
319
- }
320
- evidence.push({
321
- directory: observation.directory,
322
- paths: paths.length,
323
- metadataReads,
324
- contentReads,
325
- gitInspections: 0,
326
- repositoryCaptures: 0,
327
- plannedRows: captured.length - beforeCount,
328
- records: captured.slice(beforeCount).filter(({ proposal }) => proposal !== null && outgoing.has(proposal)).length,
329
- recordBytes,
330
- durationMs: performance.now() - started,
331
- });
332
- }
333
- const committedRecords = [];
334
- const commitStarted = performance.now();
335
- const committed = this.database.transaction(() => {
336
- if (captured.some((item) => {
337
- const current = this.rowAtUuid.get(item.result.uuid);
338
- return item.base ? !sameNotebookBasisRow(item.base, current) : current !== undefined;
339
- }))
340
- return false;
341
- for (const item of captured) {
342
- this.writeEntity.run(...rowValues(item.result));
343
- this.writeNotebook.run(...rowValues(item.result));
344
- if (!item.proposal || !outgoing.has(item.proposal))
345
- continue;
346
- if (this.acceptedInbox.covers(item.result.resourceId, item.proposal))
347
- continue;
348
- committedRecords.push(this.appendOutboxRecord(item.result.resourceId, item.proposal));
349
- }
350
- for (const parentUUID of membershipParents) {
351
- const base = this.rowAtUuid.get(parentUUID);
352
- if (!base || !["workspace", "folder"].includes(base.type))
353
- continue;
354
- const children = this.notebookChildren.all(parentUUID);
355
- const payloadVersion = membershipHash(children, sha256Hex);
356
- const version = canonicalVersion({
357
- type: base.type,
358
- parentUUID: base.parentUUID,
359
- name: base.name,
360
- status: base.status,
361
- payloadVersion,
362
- }, sha256Hex);
363
- if (version === base.version)
364
- continue;
365
- const result = { ...base, payloadVersion: null, transportVersion: null, version };
366
- this.writeEntity.run(...rowValues(result));
367
- this.writeNotebook.run(...rowValues(result));
368
- const proposal = detectedChangeProposal({
369
- base: portable(base),
370
- result: portable(result),
371
- replay: { kind: "structure" },
372
- });
373
- if (!proposal)
374
- continue;
375
- if (!this.acceptedInbox.covers(base.resourceId, proposal)) {
376
- committedRecords.push(this.appendOutboxRecord(base.resourceId, proposal));
377
- }
378
- }
379
- return true;
380
- })();
381
- if (!committed)
382
- return { handled: false, evidence: [] };
383
- this.onRecordCommit?.({
384
- durationMs: performance.now() - commitStarted,
385
- records: committedRecords,
386
- });
387
- return { handled: true, evidence };
388
- }
389
- /** A known leaf edit inside one repository is still named evidence. It
390
- * updates that leaf's local identity proof, then records one nearest-repo
391
- * Card + Checkpoint transition. Ambiguous structure and rail changes widen
392
- * to reconciliation before this method mutates SQLite. */
393
- async detectRepository(observations) {
394
- const active = observations.filter(({ suspicion }) => suspicion.paths.length > 0);
395
- if (active.length === 0)
396
- return null;
397
- let repository = null;
398
- let directory = "";
399
- const leafRows = new Map();
400
- let metadataReads = 0;
401
- let contentReads = 0;
402
- let observedPaths = 0;
403
- let stateEvidence = false;
404
- const gitLockPaths = [];
405
- const started = performance.now();
406
- for (const observation of active) {
407
- const root = this.rootAtDirectory.get(observation.directory);
408
- if (!root)
409
- return null;
410
- const paths = observation.suspicion.paths
411
- .filter((path) => !observation.suspicion.paths?.some((candidate) => candidate !== path && path.startsWith(`${candidate}/`)));
412
- observedPaths += paths.length;
413
- for (const path of paths) {
414
- const owner = this.repositoryAtPath(root.rootUUID, path);
415
- if (!owner || (repository && owner.uuid !== repository.uuid))
416
- return null;
417
- repository = owner;
418
- directory = observation.directory;
419
- const segments = path.split("/");
420
- const marker = segments.indexOf(".git");
421
- if (marker >= 0) {
422
- // An established repository may report Card or index metadata.
423
- // Any named worktree path in the same generation is captured above;
424
- // the Git Checkpoint itself carries staged bytes and modes exactly.
425
- if (!hasGitMarker(owner.absolutePath))
426
- return null;
427
- metadataReads += 1;
428
- if (isGitLockPath(segments.slice(marker + 1)))
429
- gitLockPaths.push(path);
430
- else
431
- stateEvidence = true;
432
- continue;
433
- }
434
- stateEvidence = true;
435
- const exact = this.rowAtPath.get(root.rootUUID, path);
436
- const stats = tryLstat(join(observation.directory, ...path.split("/")));
437
- if (!exact || !stats || exact.uuid === owner.uuid
438
- || this.repositoryAtPath(root.rootUUID, exact.relativePath)?.uuid !== owner.uuid)
439
- return null;
440
- metadataReads += 1;
441
- if (stats.isFile() && ["file.text", "file.binary"].includes(exact.type)) {
442
- const captured = await captureContentFile(exact.absolutePath, stats, null);
443
- const type = classifyFile({ binary: captured.binary });
444
- const result = {
445
- ...exact,
446
- type,
447
- version: canonicalVersion({
448
- type,
449
- parentUUID: exact.parentUUID,
450
- name: exact.name,
451
- status: exact.status,
452
- payloadVersion: captured.contentHash,
453
- fileMode: portableFileMode(type, stats.mode),
454
- }, sha256Hex),
455
- payloadVersion: captured.contentHash,
456
- transportVersion: null,
457
- deviceNumber: stats.dev,
458
- inode: stats.ino,
459
- byteSize: stats.size,
460
- modifiedTimeMs: stats.mtimeMs,
461
- changedTimeMs: stats.ctimeMs,
462
- filesystemMode: stats.mode,
463
- contentVerifiedAtMs: Date.now(),
464
- };
465
- leafRows.set(result.uuid, result);
466
- contentReads += 1;
467
- continue;
440
+ const kind = stats.isFile() ? "file" : stats.isSymbolicLink() ? "link" : null;
441
+ if (kind === "link" && referenceWorkspaceId(absolutePath, this.bindingDir) !== null) {
442
+ return widened("managed-reference", path);
468
443
  }
469
- if (stats.isSymbolicLink() && exact.type === "link") {
470
- const target = readlinkSync(exact.absolutePath);
471
- const targetBytes = Buffer.from(target, "utf8");
472
- const payloadVersion = sha256Hex(targetBytes);
473
- // Record may become sendable in the same SQLite transaction below;
474
- // immutable cargo therefore has to be sealed during this truth read,
475
- // exactly like ordinary file capture.
476
- sealCapturedArtifact(this.cacheDir, {
477
- entityId: exact.uuid,
478
- entityType: "link",
479
- kind: "link",
480
- contentHash: payloadVersion,
481
- }, targetBytes);
482
- const result = {
483
- ...exact,
484
- version: canonicalVersion({
485
- type: "link",
486
- parentUUID: exact.parentUUID,
487
- name: exact.name,
488
- status: exact.status,
489
- payloadVersion,
490
- }, sha256Hex),
491
- payloadVersion,
492
- deviceNumber: stats.dev,
493
- inode: stats.ino,
494
- byteSize: stats.size,
495
- modifiedTimeMs: stats.mtimeMs,
496
- changedTimeMs: stats.ctimeMs,
497
- filesystemMode: stats.mode,
498
- contentVerifiedAtMs: Date.now(),
499
- };
500
- leafRows.set(result.uuid, result);
501
- contentReads += 1;
502
- continue;
444
+ if (kind === null || (exact && !LEAF_TYPES.has(exact.type))) {
445
+ const found = kind ?? (stats.isDirectory() ? "directory" : "special");
446
+ return widened(exact ? "kind-changed" : "unindexed-path", `${exact?.type ?? "none"} to ${found}: ${path}`);
503
447
  }
504
- return null;
448
+ leaves.push({ path, absolutePath, stats, kind });
505
449
  }
506
- }
507
- if (!repository || !directory)
508
- return null;
509
- if (!stateEvidence && gitLockPaths.some((path) => existsSync(join(directory, ...path.split("/"))))) {
510
- // A lock path is proof that Git may still be constructing the state it
511
- // will reveal. Keep this Watch generation pending and retry cheaply;
512
- // absence of a second OS callback is never proof that the operation did
513
- // not finish. Once every named lock is gone, capture Card + Checkpoint
514
- // exactly once from settled repository truth.
515
- return { handled: false, evidence: [], unsettled: true };
516
- }
517
- const priorRows = this.rowsUnderPath.all(repository.rootUUID, repository.relativePath, repository.relativePath, repository.relativePath, repository.relativePath);
518
- const projectedIdentity = repositoryIdentity(repository, priorRows);
519
- const priorByOwnedPath = new Map(projectedIdentity.map((entry) => [entry.path, entry.uuid]));
520
- const priorByUuid = new Map(priorRows.map((row) => [row.uuid, row]));
521
- // A Card transition can make a formerly dirty path clean without emitting
522
- // a worktree event for that path. Refresh every nearest-owned clean leaf
523
- // from Git's index/object database before sealing the identity map; this
524
- // reads no worktree bytes and prevents the receiver from correcting the
525
- // sender's stale evidence as an otherwise-valid echo.
526
- const refreshRepositoryRows = async () => {
527
- const registration = inspectGitRegistration(repository.absolutePath);
528
- for (const [path, clean] of registration.cleanLeaves) {
529
- const uuid = priorByOwnedPath.get(path);
530
- const base = uuid ? (leafRows.get(uuid) ?? priorByUuid.get(uuid)) : undefined;
531
- if (!base || (clean.type !== "link"
532
- && clean.type !== "file.text" && clean.type !== "file.binary"))
533
- continue;
534
- const version = canonicalVersion({
535
- type: clean.type,
536
- parentUUID: base.parentUUID,
537
- name: base.name,
538
- status: base.status,
539
- payloadVersion: clean.payloadVersion,
540
- fileMode: portableFileMode(clean.type, clean.fileMode),
541
- }, sha256Hex);
542
- if (base.type === clean.type
543
- && base.payloadVersion === clean.payloadVersion
544
- && base.version === version)
545
- continue;
546
- leafRows.set(base.uuid, {
547
- ...base,
548
- type: clean.type,
549
- version,
550
- payloadVersion: clean.payloadVersion,
551
- transportVersion: null,
552
- contentVerifiedAtMs: Date.now(),
553
- });
450
+ // A link to any file read may appear while it is read.
451
+ this.lease.physical(new PhysicalKeySet(leaves.filter(leaf => leaf.kind === "file").map(leaf => physicalKey(leaf.stats))));
452
+ // Collection is complete; identity preparation starts at a boundary.
453
+ await turnBoundary();
454
+ // 2. One identity decision for every leaf, from the closure of facts
455
+ // the complete resolver reads for them.
456
+ const identity = await resolveScopedLeaves(root.rootUUID, leaves.map(leaf => ({ relativePath: leaf.path,
457
+ kind: leaf.kind, physicalKey: physicalKey(leaf.stats), links: Number(leaf.stats.nlink) })), scopedIdentityPort({ root, directory: observation.directory, ground: this.ground,
458
+ proof: contexts.get(observation.directory), sources, eligible: row => this.isEffectivelyActive(row),
459
+ attribute: scope => this.requireAttribution(scope), require: address => this.lease.require([address]),
460
+ lstat: tryLstat }));
461
+ if (identity.kind === "examine")
462
+ return widened("identity-unproved", `${identity.reason}: ${identity.path}`);
463
+ for (const path of identity.occupied)
464
+ examine.add(join(observation.directory, ...path.split("/")));
465
+ const moved = new Map();
466
+ const { identities } = identity;
467
+ for (const leaf of leaves) {
468
+ const row = identities.get(leaf.path) ?? null;
469
+ if (row && row.relativePath !== leaf.path)
470
+ moved.set(row.relativePath, row.uuid);
554
471
  }
555
- for (const path of registration.dirtyPaths) {
556
- const uuid = priorByOwnedPath.get(path);
557
- const base = uuid ? (leafRows.get(uuid) ?? priorByUuid.get(uuid)) : undefined;
558
- if (!base)
559
- return null;
560
- let stats;
561
- try {
562
- stats = lstatSync(base.absolutePath);
563
- }
564
- catch {
565
- // A dirty deletion or a newly discovered path changes repository
566
- // membership. The ordinary reconciliation lane owns that graph
567
- // change and will rebuild the exact identity map once.
568
- return null;
569
- }
570
- const observed = uuid ? leafRows.get(uuid) : undefined;
571
- if (observed
572
- && observed.deviceNumber === stats.dev
573
- && observed.inode === stats.ino
574
- && observed.byteSize === stats.size
575
- && observed.modifiedTimeMs === stats.mtimeMs
576
- && observed.changedTimeMs === stats.ctimeMs
577
- && observed.filesystemMode === stats.mode) {
578
- continue;
579
- }
580
- let type;
581
- let payloadVersion;
582
- if (stats.isSymbolicLink()) {
583
- type = "link";
584
- payloadVersion = sha256Hex(Buffer.from(readlinkSync(base.absolutePath), "utf8"));
585
- }
586
- else if (stats.isFile()) {
587
- const captured = await captureContentFile(base.absolutePath, stats, null);
588
- type = classifyFile({ binary: captured.binary });
589
- payloadVersion = captured.contentHash;
590
- }
591
- else {
592
- return null;
593
- }
594
- leafRows.set(base.uuid, {
595
- ...base,
596
- type,
597
- version: canonicalVersion({
598
- type,
599
- parentUUID: base.parentUUID,
600
- name: base.name,
601
- status: base.status,
602
- payloadVersion,
603
- fileMode: portableFileMode(type, stats.mode),
604
- }, sha256Hex),
605
- payloadVersion,
606
- transportVersion: null,
607
- deviceNumber: stats.dev,
608
- inode: stats.ino,
609
- byteSize: stats.size,
610
- modifiedTimeMs: stats.mtimeMs,
611
- changedTimeMs: stats.ctimeMs,
612
- filesystemMode: stats.mode,
613
- contentVerifiedAtMs: Date.now(),
614
- });
615
- contentReads += 1;
472
+ // 3. An identity moved in this unit covers its old address. A name no
473
+ // active identity holds, observed or shared, settles as
474
+ // examined-no-change: a trashed row there is an absence already
475
+ // concluded. Any other absence needs the complete membership.
476
+ for (const path of absent) {
477
+ await turnBoundary();
478
+ const holders = ["entities", "detection_notebook"].flatMap(inventory => this.ground.atPath(root.rootUUID, path, inventory).filter(row => this.isEffectivelyActive(row, inventory)));
479
+ if (holders.some(row => moved.get(path) !== row.uuid))
480
+ return widened("absent-path", path);
616
481
  }
617
- return priorRows.map((row) => leafRows.get(row.uuid) ?? row);
618
- };
619
- const currentRows = await refreshRepositoryRows();
620
- if (!currentRows)
621
- return null;
622
- let identity = repositoryIdentity(repository, currentRows);
623
- let prior = null;
624
- let priorLayout = null;
625
- if (repository.payloadVersion && repository.transportVersion) {
626
- const priorFile = join(this.cacheDir, `${repository.transportVersion}.bin`);
627
- if (existsSync(priorFile)) {
628
- try {
629
- const layout = inspectRepositoryTransportFile(priorFile);
630
- if (layout.stateId === repository.payloadVersion) {
631
- priorLayout = layout;
632
- prior = {
633
- stateId: repository.payloadVersion,
634
- transportVersion: repository.transportVersion,
635
- card: layout.card,
636
- identityHash: layout.identityHash,
637
- };
638
- }
639
- }
640
- catch {
641
- prior = null;
642
- priorLayout = null;
643
- }
482
+ // Every address is checked before any byte is read; the reads then run
483
+ // together on the capture threads instead of one file at a time.
484
+ const reads = [];
485
+ await eachInTurns(leaves, leaf => {
486
+ const base = identities.get(leaf.path) ?? null;
487
+ const parent = this.activeRowAtPath(root.rootUUID, parentPath(leaf.path));
488
+ if (!parent)
489
+ throw new Error(`named leaf ${leaf.path} has no proved parent`);
490
+ reads.push({ root, path: leaf.path, absolutePath: leaf.absolutePath, stats: leaf.stats, kind: leaf.kind,
491
+ parent, base, uuid: base?.uuid ?? randomUUID(), sources });
492
+ });
493
+ // Each read's reuse proof and dispatch are ground reads of its own.
494
+ const results = await settleInTurns(reads, input => input.kind === "link" ? this.captureLink(input) : this.captureFile(input));
495
+ let contentReads = 0;
496
+ for (const [index, result] of results.entries()) {
497
+ captured.push(result.item);
498
+ if (!policy.includes(reads[index].path))
499
+ excluded++;
500
+ contentReads += result.contentReads;
644
501
  }
502
+ evidence.push({ directory: observation.directory, metadataReads: paths.length,
503
+ metadataReused: leaves.length - contentReads, contentReads,
504
+ gitInspections: 0, repositoryCaptures: 0, plannedRows: captured.length - first,
505
+ records: 0, settled: 0, recordBytes: 0, durationMs: performance.now() - started });
645
506
  }
646
- let captured = null;
647
- let coherent = false;
648
- const previousIdentity = prior
649
- ? readCachedRepositoryIdentity(this.cacheDir, prior.transportVersion)
650
- : null;
651
- for (let attempt = 0; attempt < 3; attempt += 1) {
652
- captured = captureRepository(repository.absolutePath, identity, prior, prior ? previousIdentity : null);
653
- const identityPaths = new Set(identity.map((entry) => entry.path));
654
- if (captured.state.checkpoint.untracked.some((entry) => !identityPaths.has(entry.path))) {
655
- // The Checkpoint found ground for which this named pass has no durable
656
- // UUID yet. Widen to the ordinary reconciliation lane so bytes and
657
- // identity enter one repository record, never an identity-only echo
658
- // on the receiving machine.
659
- return null;
507
+ // Each captured name owes its object's other names (alias-examination.ts).
508
+ for (const path of aliasExaminations(captured.map(({ base, result }) => ({ before: base, after: result })), { objectRows: key => this.ground.objectRows(key), physical: uuid => this.ground.physical(uuid) }))
509
+ examine.add(path);
510
+ this.mark("collect");
511
+ const priors = new Map();
512
+ const sharing = new Map();
513
+ const adopted = new Set();
514
+ for (const { base, result } of captured) {
515
+ await turnBoundary();
516
+ const prior = base ? this.ground.sharing(result.uuid) : undefined;
517
+ if (prior && prior.status !== "active")
518
+ return widened("unshared-leaf", result.relativePath);
519
+ if (prior)
520
+ priors.set(result.uuid, prior);
521
+ // A new sharing identity allocates its address; another identity there
522
+ // is a divergence only the complete graph resolves.
523
+ else if (this.ground.atPath(result.rootUUID, result.relativePath, "detection_notebook").length) {
524
+ return widened("address-changed", result.relativePath);
660
525
  }
661
- const refreshedRows = await refreshRepositoryRows();
662
- if (!refreshedRows)
663
- return null;
664
- const after = repositoryIdentity(repository, refreshedRows);
665
- if (sameGitIdentity(after, identity)) {
666
- identity = [...captured.identity];
667
- coherent = true;
668
- break;
526
+ if (acceptedBasis.covers(result.resourceId, { entityId: result.uuid, result: portable(result) })) {
527
+ adopted.add(result.uuid);
528
+ sharing.set(result.uuid, result);
669
529
  }
670
- identity = after;
530
+ else if (prior)
531
+ sharing.set(result.uuid, prior);
671
532
  }
672
- if (!coherent || !captured) {
673
- throw new Error("repository identity did not settle during Detect capture");
533
+ const targets = new Set(captured.map(item => item.result.uuid));
534
+ const context = this.publicationContext([...captured.flatMap(({ base, result }) => base ? [base, result] : [result]),
535
+ ...priors.values(), ...sharing.values()], targets);
536
+ if (context === null)
537
+ return widened("address-changed", "ancestor structure differs");
538
+ let selection;
539
+ try {
540
+ selection = selectPublication({ before: [...sharing.values()], observed: captured.map(item => item.result),
541
+ context, includes: (path, rootId) => {
542
+ const policy = policies.get(rootId);
543
+ if (!policy)
544
+ throw new Error("publication has no observed root policy");
545
+ return policy.includes(path);
546
+ } });
674
547
  }
675
- for (const entry of captured.identity) {
676
- if (!entry.payloadVersion
677
- || (entry.type !== "file.text" && entry.type !== "file.binary" && entry.type !== "link")) {
678
- continue;
679
- }
680
- const uuid = priorByOwnedPath.get(entry.path);
681
- const base = uuid ? (leafRows.get(uuid) ?? priorByUuid.get(uuid)) : undefined;
682
- if (!base)
683
- continue;
684
- const type = entry.type;
685
- leafRows.set(base.uuid, {
686
- ...base,
687
- type,
688
- version: canonicalVersion({
689
- type,
690
- parentUUID: base.parentUUID,
691
- name: base.name,
692
- status: base.status,
693
- payloadVersion: entry.payloadVersion,
694
- fileMode: portableFileMode(type, base.filesystemMode),
695
- }, sha256Hex),
696
- payloadVersion: entry.payloadVersion,
697
- transportVersion: null,
698
- });
699
- }
700
- if (captured.bytes) {
701
- sealCapturedArtifact(this.cacheDir, {
702
- entityId: repository.uuid,
703
- entityType: "repo.git",
704
- kind: "git",
705
- contentHash: captured.transportVersion,
706
- }, captured.bytes);
548
+ catch (error) {
549
+ if (!(error instanceof PublicationScopeExceeded))
550
+ throw error;
551
+ return widened("scope-exceeded", contextPath(context, error.entityIds[0]));
707
552
  }
708
- const layout = inspectRepositoryTransportFile(join(this.cacheDir, `${captured.transportVersion}.bin`));
709
- sealCachedRepositoryIdentity(this.cacheDir, captured.transportVersion, captured.identity, layout.identityHash);
710
- const stats = lstatSync(repository.absolutePath);
711
- const result = {
712
- ...repository,
713
- version: canonicalVersion({
714
- type: "repo.git",
715
- parentUUID: repository.parentUUID,
716
- name: repository.name,
717
- status: repository.status,
718
- payloadVersion: captured.stateId,
719
- }, sha256Hex),
720
- payloadVersion: captured.stateId,
721
- transportVersion: captured.transportVersion,
722
- deviceNumber: stats.dev,
723
- inode: stats.ino,
724
- byteSize: stats.size,
725
- modifiedTimeMs: stats.mtimeMs,
726
- changedTimeMs: stats.ctimeMs,
727
- filesystemMode: stats.mode,
728
- };
729
- const proposal = detectedChangeProposal({
730
- base: portable(repository),
731
- result: portable(result),
732
- replay: {
733
- kind: "repo.git",
734
- stateId: layout.stateId,
735
- cardId: layout.cardId,
736
- checkpointId: layout.checkpointId,
737
- cardChanged: priorLayout?.cardId !== layout.cardId,
738
- checkpointChanged: priorLayout?.checkpointId !== layout.checkpointId,
739
- parentTransportVersion: layout.parentTransportVersion,
740
- transportVersion: captured.transportVersion,
741
- },
553
+ const admitted = selection.selected.filter(row => selection.sources.get(row.uuid) === "observed" || adopted.has(row.uuid));
554
+ // Selection is complete; container preparation starts at a boundary.
555
+ await turnBoundary();
556
+ // Parents derive O from every observed result and S from the selected
557
+ // result only, each against its stored row; neither membership change is
558
+ // a Record of its own.
559
+ const observedContainers = finalizeContainers({
560
+ parents: [...membershipParents(captured.map(({ base, result }) => ({ before: base ? this.ground.physical(result.uuid) : null, after: result })))]
561
+ .map(uuid => required(this.ground.physical(uuid), uuid)),
562
+ children: uuid => this.ground.siblings("entities", uuid),
563
+ targets, results: captured.map(item => item.result), sha256Hex
564
+ });
565
+ const selected = new Map(selection.selected.map(row => [row.uuid, row]));
566
+ const sharedContainers = finalizeContainers({
567
+ parents: [...membershipParents([...targets].map(uuid => ({ before: priors.get(uuid) ?? null, after: selected.get(uuid) ?? null })))]
568
+ .map(uuid => required(this.ground.sharing(uuid), uuid)),
569
+ children: uuid => this.ground.siblings("detection_notebook", uuid),
570
+ targets, results: selection.selected, sha256Hex
742
571
  });
743
- const shouldRecord = proposal !== null
744
- && !this.acceptedInbox.covers(repository.resourceId, proposal);
745
- const recordBytes = proposal && shouldRecord
746
- ? Buffer.byteLength(this.previewRecordJson(repository.resourceId, proposal))
747
- : 0;
748
- const committedRecords = [];
572
+ // Payload replays read their results' cache names; no other stage is
573
+ // cargo. A payload observed through a reused proof is read once now.
574
+ const carried = admitted.filter(row => !adopted.has(row.uuid) && replaysPayload(priors.get(row.uuid) ?? null, row));
575
+ const unread = carried.filter(row => !this.cargo.holds(row.payloadVersion));
576
+ await settleInTurns(unread, row => this.sealUnread(row));
577
+ await this.cargo.publish(new Set(carried.map(row => row.payloadVersion)));
578
+ // Every replay starts before any is awaited: each reads the cache on a
579
+ // content thread.
580
+ const proposals = (await settleInTurns(admitted.filter(row => !adopted.has(row.uuid)), async (row) => {
581
+ const proposal = await this.proposalFor(priors.get(row.uuid) ?? null, row);
582
+ return proposal ? [{ authorityId: row.resourceId, proposal }] : [];
583
+ })).flat();
584
+ await makeRecordContentDurable(this.cacheDir, proposals.flatMap(({ proposal }) => proposal.cargo));
585
+ this.mark("durable");
586
+ // Consumed Git answers their witnesses cannot close are asked again;
587
+ // only native witnesses close inside admission.
588
+ await this.gitProof.proof.semantic();
589
+ this.mark("close");
590
+ const basis = this.ground.finish();
591
+ const ownership = this.ownership.seal();
749
592
  const commitStarted = performance.now();
750
- let committedRecord = false;
751
- const committed = this.database.transaction(() => {
752
- const bases = new Map([[repository.uuid, repository]]);
753
- for (const uuid of leafRows.keys()) {
754
- const base = priorByUuid.get(uuid);
755
- if (base)
756
- bases.set(uuid, base);
593
+ const accounted = admitted.filter(row => adopted.has(row.uuid)).length;
594
+ const written = await this.admit("named leaves: admission depends on their ancestor spine", basis, acceptedBasis, () => {
595
+ ownership.assertCurrent();
596
+ // One recheck after every leaf was observed closes each container.
597
+ if ([...contexts.values()].some(context => !context.unchanged()))
598
+ return null;
599
+ closePhysically(this.gitProof.proof);
600
+ for (const policy of policies.values())
601
+ policy.validate();
602
+ sources.validate();
603
+ const createdAt = Date.now();
604
+ return {
605
+ writes: {
606
+ repositoryObservations: [], seals: [],
607
+ materialized: captured.map(item => retainContentVerification(item.base, item.result)).concat(observedContainers),
608
+ materializedRemovals: [],
609
+ notebook: admitted.map(row => retainContentVerification(sharing.get(row.uuid), row)).concat(sharedContainers),
610
+ records: proposals.map(({ authorityId, proposal }) => createLocalEntityRecord({ mutationId: randomUUID(),
611
+ authorityId, lastSeenGlobalSequence: this.entityReceivedThrough(authorityId, proposal.entityId),
612
+ change: proposal, createdAt })),
613
+ },
614
+ counts: { proposed: proposals.length + accounted, admitted: proposals.length,
615
+ alreadyAccounted: accounted, withheld: captured.length - admitted.length },
616
+ };
617
+ });
618
+ if (!written)
619
+ return retry("admission-basis-changed");
620
+ const { records: committedRecords, ground, changes } = written;
621
+ if (changes)
622
+ this.onDetectCommit(Object.freeze({ changes, records: written.recordJSON }));
623
+ this.onRecordCommit?.({ durationMs: performance.now() - commitStarted,
624
+ records: committedRecords, ...(ground ? { ground } : {}) });
625
+ // A changed policy owes a proof wherever sharing is not yet its
626
+ // selection. Saves commit in posting order, so this commit made every
627
+ // admission that read the old policy visible to the read; any later one
628
+ // read this policy.
629
+ for (const { directory, rootUUID, text } of policyChanges) {
630
+ for (const path of await readOwedAddresses(this.database.name, rootUUID, text, this.bindingDir)) {
631
+ examine.add(join(directory, ...path.split("/")));
757
632
  }
758
- if ([...bases.values()].some((base) => !sameNotebookBasisRow(base, this.rowAtUuid.get(base.uuid)))) {
759
- return false;
633
+ }
634
+ if (policyChanges.length)
635
+ this.mark("include");
636
+ return { ...settled(evidence.map(item => {
637
+ const root = observations.find(observation => observation.directory === item.directory);
638
+ const within = (row) => row.absolutePath.startsWith(`${root.directory}/`);
639
+ const ids = new Set(captured.filter(entry => within(entry.result)).map(entry => entry.result.uuid));
640
+ const records = committedRecords.filter(record => ids.has(record.entityId));
641
+ const late = unread.filter(within).length;
642
+ return { ...item, metadataReused: item.metadataReused - late, contentReads: item.contentReads + late,
643
+ records: records.length, settled: [...ids].filter(id => adopted.has(id)).length,
644
+ recordBytes: records.reduce((sum, record) => sum + Buffer.byteLength(encodeEntityRecord(record)), 0) };
645
+ }), captured.map(item => item.result.uuid), captured.length > 0 && excluded === captured.length),
646
+ ...(examine.size ? { examine: [...examine] } : {}) };
647
+ }
648
+ /** The unchanged structure that closes the targets' collision-derived
649
+ * addresses, from each inventory separately: every container on the
650
+ * ancestry of any target row, and the complete active child group of each
651
+ * one a collision owes (Live's childGroupOwed), in both inventories. A
652
+ * container's members are the target rows and ancestry containers it
653
+ * holds; their slots are read one by one, never the container, so a leaf
654
+ * in a directory of any size reads its ancestry and a few slots. Ancestors
655
+ * agree structurally in both inventories; owed groups need not, since an
656
+ * O-only ignored sibling is observation, not sharing. The selector refuses
657
+ * any selection that would change a context member. Content outside the
658
+ * targets is neither loaded nor a validation dependency. */
659
+ publicationContext(rows, targets) {
660
+ const before = new Map(), observed = new Map();
661
+ const members = new Map();
662
+ const place = (row) => {
663
+ const placed = members.get(row.parentUUID);
664
+ if (placed)
665
+ placed.push(row);
666
+ else
667
+ members.set(row.parentUUID, [row]);
668
+ };
669
+ const shape = (row) => JSON.stringify([
670
+ row.uuid, row.parentUUID, row.name, row.type, row.relativePath, row.status,
671
+ ]);
672
+ const node = (row) => row.relativePath === "" ? { ...row, parentUUID: null } : row;
673
+ for (const row of rows) {
674
+ place(row);
675
+ const path = new Set();
676
+ for (let parentUUID = row.parentUUID; parentUUID !== null && !observed.has(parentUUID);) {
677
+ if (path.has(parentUUID))
678
+ throw new Error("publication ancestry has a cycle");
679
+ path.add(parentUUID);
680
+ const current = this.ground.structure("entities", parentUUID);
681
+ const shared = this.ground.structure("detection_notebook", parentUUID);
682
+ if (!current || !shared || current.status !== "active" || shape(current) !== shape(shared))
683
+ return null;
684
+ before.set(shared.uuid, node(shared));
685
+ observed.set(current.uuid, node(current));
686
+ if (current.relativePath === "")
687
+ break;
688
+ place(current);
689
+ parentUUID = current.parentUUID;
760
690
  }
761
- for (const row of leafRows.values()) {
762
- this.writeEntity.run(...rowValues(row));
763
- this.writeNotebook.run(...rowValues(row));
691
+ }
692
+ const active = (row) => row.status === "active";
693
+ // An ancestor keeps the structure row proved above.
694
+ const ancestry = new Set(observed.keys());
695
+ const unchanged = (row) => active(row) && !targets.has(row.uuid) && !ancestry.has(row.uuid);
696
+ for (const [parentUUID, placed] of members) {
697
+ if (!["detection_notebook", "entities"].some(inventory => childGroupOwed(placed, name => this.ground.slot(inventory, parentUUID, name).filter(active).map(row => row.uuid))))
698
+ continue;
699
+ for (const sibling of this.ground.siblings("detection_notebook", parentUUID).filter(unchanged)) {
700
+ before.set(sibling.uuid, sibling);
764
701
  }
765
- this.writeEntity.run(...rowValues(result));
766
- this.writeNotebook.run(...rowValues(result));
767
- if (proposal && shouldRecord
768
- && !this.acceptedInbox.covers(repository.resourceId, proposal)) {
769
- committedRecords.push(this.appendOutboxRecord(repository.resourceId, proposal));
770
- committedRecord = true;
702
+ for (const sibling of this.ground.siblings("entities", parentUUID).filter(unchanged)) {
703
+ observed.set(sibling.uuid, sibling);
771
704
  }
772
- return true;
773
- })();
774
- if (!committed)
775
- return { handled: false, evidence: [] };
776
- this.onRecordCommit?.({
777
- durationMs: performance.now() - commitStarted,
778
- records: committedRecords,
779
- });
780
- return {
781
- handled: true,
782
- evidence: [{
783
- directory,
784
- paths: active.reduce((count, item) => count + item.suspicion.paths.length, 0),
785
- metadataReads,
786
- contentReads,
787
- gitInspections: 1,
788
- repositoryCaptures: 1,
789
- plannedRows: 1,
790
- records: committedRecord ? 1 : 0,
791
- recordBytes: committedRecord ? recordBytes : 0,
792
- durationMs: performance.now() - started,
793
- }],
794
- };
795
- }
796
- repositoryAtPath(rootUUID, path) {
797
- const segments = path.split("/");
798
- const marker = segments.indexOf(".git");
799
- if (marker >= 0) {
800
- const candidate = segments.slice(0, marker).join("/");
801
- const row = this.rowAtPath.get(rootUUID, candidate);
802
- return row?.type === "repo.git" ? row : null;
803
705
  }
706
+ return { before: [...before.values()], observed: [...observed.values()] };
707
+ }
708
+ repositoryAtPath(rootUUID, path, directory) {
709
+ const route = this.gitProof.route(join(directory, path));
710
+ if (route.kind === "reconcile")
711
+ return null;
712
+ if (route.kind === "single")
713
+ return this.ground.physical(route.uuid) ?? null;
804
714
  let candidate = path;
805
715
  while (true) {
806
- const row = this.rowAtPath.get(rootUUID, candidate);
716
+ const row = this.activeRowAtPath(rootUUID, candidate);
807
717
  if (row?.type === "repo.git")
808
718
  return row;
809
719
  if (candidate === "")
@@ -812,77 +722,77 @@ export class NamedDetectRuntime {
812
722
  candidate = parent === "." ? "" : parent;
813
723
  }
814
724
  }
815
- previewRecordJson(authorityId, proposal) {
816
- return encodeEntityRecord(createLocalEntityRecord({
817
- mutationId: "00000000-0000-4000-8000-000000000000",
818
- authorityId,
819
- lastSeenGlobalSequence: this.entityReceivedThrough(authorityId, proposal.entityId),
820
- change: proposal,
821
- createdAt: 0,
822
- }));
823
- }
824
- appendOutboxRecord(authorityId, proposal) {
825
- const createdAt = Date.now();
826
- const record = createLocalEntityRecord({
827
- mutationId: randomUUID(),
828
- authorityId,
829
- lastSeenGlobalSequence: this.entityReceivedThrough(authorityId, proposal.entityId),
830
- change: proposal,
831
- createdAt,
832
- });
833
- this.appendRecord.run(record.mutationId, record.authorityId, record.entityId, encodeEntityRecord(record), createdAt);
834
- return record;
725
+ /** One admission of a registered collection, as one save
726
+ * (admission-save.ts): its ready fact commits beside its outcome, in the
727
+ * save on success or after it otherwise. Collection held no ground; what
728
+ * this thread owns is answered when the save is posted, and saves commit
729
+ * in post order. Null when the basis moved. */
730
+ async admit(unit, basis, accepted, prepare) {
731
+ const collection = this.register(unit, basis);
732
+ this.mark("ready");
733
+ const invalidated = { disposition: "invalidated", retry: null, phase: "admission-basis",
734
+ reason: "the admission basis changed after collection" };
735
+ try {
736
+ let prepared, posted = null;
737
+ try {
738
+ this.lease.assertUncrossed();
739
+ prepared = prepare();
740
+ if (prepared)
741
+ posted = save(this.database.name, "detect.admit", {
742
+ units: [{ ...prepared.writes, basis: basis.toData(), dispositions: collection.admission(prepared.counts) }],
743
+ accepted: accepted.reads(), observe: this.onDetectCommit !== undefined
744
+ });
745
+ }
746
+ catch (error) {
747
+ collection.rejected(admissionRejection(error), null);
748
+ throw error;
749
+ }
750
+ if (!prepared || !posted) {
751
+ collection.rejected(invalidated, null);
752
+ return null;
753
+ }
754
+ const requested = performance.now();
755
+ let saved;
756
+ try {
757
+ saved = await posted;
758
+ }
759
+ catch (error) {
760
+ collection.rejected(admissionRejection(error), { requested, began: null, checked: null, ended: performance.now() });
761
+ throw error;
762
+ }
763
+ const result = saved.value;
764
+ const marks = { requested: saved.posted, began: saved.began,
765
+ checked: result.accepted ? result.checkedAt - performance.timeOrigin : null, ended: saved.ended };
766
+ if (!result.accepted || result.stale.length) {
767
+ collection.rejected(invalidated, marks);
768
+ return null;
769
+ }
770
+ collection.committed(marks, result.outcomes[0]);
771
+ return { records: prepared.writes.records, recordJSON: Object.freeze([...result.records]),
772
+ ...(this.measureGround() || result.changes ? { ground: result.evidence } : {}),
773
+ ...(result.changes ? { changes: result.changes } : {}) };
774
+ }
775
+ finally {
776
+ this.mark("admit");
777
+ }
835
778
  }
836
779
  entityReceivedThrough(authorityId, entityId) {
837
780
  const row = this.receivedThrough.get(authorityId, entityId);
838
781
  return row?.receivedThrough ?? 0;
839
782
  }
840
- /** Derived collision names are local addresses, not portable renames. A
841
- * same-parent observation at either the portable name or the exact address
842
- * derived from the current sibling set keeps the stored portable name. A
843
- * different parent, or any third basename, is real move/rename evidence. */
844
- capturedName(base, path, parentUUID) {
845
- const seen = posix.basename(path);
846
- if (!base || base.parentUUID !== parentUUID)
847
- return seen;
848
- let derived = this.slotNamesByParent.get(parentUUID);
849
- if (!derived) {
850
- derived = materializedSlotNames(this.materializedSiblings.all(parentUUID));
851
- this.slotNamesByParent.set(parentUUID, derived);
852
- }
853
- const expected = derived.get(base.uuid) ?? base.name;
854
- return seen === base.name || seen === expected ? base.name : seen;
855
- }
856
783
  async captureFile(input) {
857
- const { root, path, absolutePath, stats, parent, exact } = input;
858
- const base = exact ?? this.uniqueMovedBase(stats.dev, stats.ino, absolutePath);
859
- const fingerprintTrusted = base !== null
860
- && base.relativePath === path
861
- && this.materializedByUuid.get(base.uuid) !== undefined
862
- && sameFingerprint(base, stats)
863
- && base.contentVerifiedAtMs !== null
864
- && stats.mtimeMs < base.contentVerifiedAtMs;
865
- if (fingerprintTrusted)
866
- return { item: null, contentReads: 0 };
867
- const canReuseMovedContent = base !== null
868
- && base.relativePath !== path
869
- && base.deviceNumber === stats.dev
870
- && base.inode === stats.ino
871
- && base.byteSize === stats.size
872
- && base.modifiedTimeMs === stats.mtimeMs
873
- && base.filesystemMode === stats.mode
874
- && base.payloadVersion !== null;
875
- const content = canReuseMovedContent ? null : await captureContentFile(absolutePath, stats, this.cacheDir);
876
- const type = content ? classifyFile({ binary: content.binary }) : base?.type;
877
- const payloadVersion = content?.contentHash ?? base?.payloadVersion ?? null;
878
- if (!type || !["file.text", "file.binary"].includes(type) || !payloadVersion) {
879
- throw new Error(`named Detect could not capture ${path}`);
880
- }
881
- const name = this.capturedName(base, path, parent.uuid);
784
+ const { root, path, absolutePath, stats, parent, base, uuid, sources } = input;
785
+ sources.remember(absolutePath, stats);
786
+ if (this.unchangedLeaf(input))
787
+ return { item: { base, result: base }, contentReads: 0 };
788
+ const content = await captureContent(absolutePath, stats, this.cacheDir, { lane: "named", cargo: this.cargo });
789
+ const type = classifyFile({ binary: content.binary });
790
+ const payloadVersion = content.contentHash;
791
+ const name = observedPortableName(base, parent.uuid, posix.basename(path));
882
792
  const result = {
883
793
  resourceId: root.resourceId,
884
794
  rootUUID: root.rootUUID,
885
- uuid: base?.uuid ?? randomUUID(),
795
+ uuid,
886
796
  type,
887
797
  parentUUID: parent.uuid,
888
798
  name,
@@ -893,222 +803,31 @@ export class NamedDetectRuntime {
893
803
  name,
894
804
  status: "active",
895
805
  payloadVersion,
896
- fileMode: portableFileMode(type, stats.mode),
806
+ fileMode: portableFileMode(type, Number(stats.mode)),
897
807
  }, sha256Hex),
898
808
  payloadVersion,
899
809
  transportVersion: null,
900
810
  relativePath: path,
901
811
  absolutePath,
902
- deviceNumber: stats.dev,
903
- inode: stats.ino,
904
- byteSize: stats.size,
905
- modifiedTimeMs: stats.mtimeMs,
906
- changedTimeMs: stats.ctimeMs,
907
- filesystemMode: stats.mode,
908
- contentVerifiedAtMs: content ? Date.now() : base?.contentVerifiedAtMs ?? null,
909
- };
910
- const replay = base !== null
911
- && base.type === result.type
912
- && base.payloadVersion === result.payloadVersion
913
- && base.transportVersion === result.transportVersion
914
- ? { kind: "structure" }
915
- : this.replayFor(base, result);
916
- return {
917
- item: {
918
- base,
919
- result,
920
- proposal: detectedChangeProposal({
921
- base: base ? portable(base) : null,
922
- result: portable(result),
923
- replay,
924
- }),
925
- },
926
- contentReads: content ? 1 : 0,
927
- };
928
- }
929
- async captureDirectory(input) {
930
- if (existsSync(join(input.absolutePath, ".git")))
931
- return null;
932
- const exact = this.rowAtPath.get(input.root.rootUUID, input.path);
933
- if (exact && !["workspace", "folder"].includes(exact.type))
934
- return null;
935
- const base = exact ?? this.uniqueMovedBase(input.stats.dev, input.stats.ino, input.absolutePath);
936
- if (base && !["workspace", "folder"].includes(base.type))
937
- return null;
938
- const uuid = base?.uuid ?? randomUUID();
939
- const childItems = [];
940
- let metadataReads = 1;
941
- let contentReads = 0;
942
- const children = [];
943
- for (const child of readdirSync(input.absolutePath, { withFileTypes: true })
944
- .sort((left, right) => left.name.localeCompare(right.name))) {
945
- const path = `${input.path}/${child.name}`;
946
- if (!input.policy(path))
947
- continue;
948
- const absolutePath = join(input.absolutePath, child.name);
949
- const stats = lstatSync(absolutePath);
950
- metadataReads += 1;
951
- // A named new folder can still be a large subtree. Yield periodically so
952
- // the wire and Apply progress while it is enumerated.
953
- if (++this.walkedEntries % NAMED_WALK_YIELD_ENTRIES === 0) {
954
- await new Promise((resolve) => setImmediate(resolve));
955
- }
956
- const parentRow = {
957
- resourceId: input.root.resourceId,
958
- rootUUID: input.root.rootUUID,
959
- uuid,
960
- type: "folder",
961
- parentUUID: input.parent.uuid,
962
- name: posix.basename(input.path),
963
- status: "active",
964
- version: "0".repeat(64),
965
- payloadVersion: null,
966
- transportVersion: null,
967
- relativePath: input.path,
968
- absolutePath: input.absolutePath,
969
- deviceNumber: input.stats.dev,
970
- inode: input.stats.ino,
971
- byteSize: input.stats.size,
972
- modifiedTimeMs: input.stats.mtimeMs,
973
- changedTimeMs: input.stats.ctimeMs,
974
- filesystemMode: input.stats.mode,
975
- contentVerifiedAtMs: null,
976
- };
977
- if (stats.isSymbolicLink()) {
978
- const link = this.captureLink({
979
- root: input.root,
980
- path,
981
- absolutePath,
982
- stats,
983
- parent: parentRow,
984
- exact: this.rowAtPath.get(input.root.rootUUID, path),
985
- });
986
- if (!link)
987
- return null;
988
- const identity = link.item?.result
989
- ?? this.rowAtPath.get(input.root.rootUUID, path);
990
- if (!identity)
991
- throw new Error(`captured link ${path} has no identity`);
992
- children.push({ uuid: identity.uuid, name: identity.name });
993
- if (link.item)
994
- childItems.push(link.item);
995
- contentReads += link.contentReads;
996
- }
997
- else if (stats.isDirectory()) {
998
- const nested = await this.captureDirectory({
999
- root: input.root,
1000
- path,
1001
- absolutePath,
1002
- stats,
1003
- parent: parentRow,
1004
- policy: input.policy,
1005
- });
1006
- if (!nested)
1007
- return null;
1008
- const nestedRoot = nested.items[0];
1009
- if (!nestedRoot)
1010
- throw new Error(`captured directory ${path} has no root`);
1011
- children.push({ uuid: nestedRoot.result.uuid, name: nestedRoot.result.name });
1012
- childItems.push(...nested.items);
1013
- metadataReads += nested.metadataReads - 1;
1014
- contentReads += nested.contentReads;
1015
- }
1016
- else if (stats.isFile()) {
1017
- const file = await this.captureFile({
1018
- root: input.root,
1019
- path,
1020
- absolutePath,
1021
- stats,
1022
- parent: parentRow,
1023
- exact: this.rowAtPath.get(input.root.rootUUID, path),
1024
- });
1025
- const identity = file.item?.result
1026
- ?? this.rowAtPath.get(input.root.rootUUID, path);
1027
- if (!identity)
1028
- throw new Error(`captured file ${path} has no identity`);
1029
- children.push({ uuid: identity.uuid, name: identity.name });
1030
- if (file.item)
1031
- childItems.push(file.item);
1032
- contentReads += file.contentReads;
1033
- }
1034
- }
1035
- const name = this.capturedName(base, input.path, input.parent.uuid);
1036
- const result = {
1037
- resourceId: input.root.resourceId,
1038
- rootUUID: input.root.rootUUID,
1039
- uuid,
1040
- type: "folder",
1041
- parentUUID: input.parent.uuid,
1042
- name,
1043
- status: "active",
1044
- version: canonicalVersion({
1045
- type: "folder",
1046
- parentUUID: input.parent.uuid,
1047
- name,
1048
- status: "active",
1049
- payloadVersion: membershipHash(children, sha256Hex),
1050
- }, sha256Hex),
1051
- payloadVersion: null,
1052
- transportVersion: null,
1053
- relativePath: input.path,
1054
- absolutePath: input.absolutePath,
1055
- deviceNumber: input.stats.dev,
1056
- inode: input.stats.ino,
1057
- byteSize: input.stats.size,
1058
- modifiedTimeMs: input.stats.mtimeMs,
1059
- changedTimeMs: input.stats.ctimeMs,
1060
- filesystemMode: input.stats.mode,
1061
- contentVerifiedAtMs: null,
1062
- };
1063
- const proposal = detectedChangeProposal({
1064
- base: base ? portable(base) : null,
1065
- result: portable(result),
1066
- replay: { kind: "structure" },
1067
- });
1068
- return {
1069
- items: [{ base, result, proposal }, ...childItems],
1070
- metadataReads,
1071
- contentReads,
812
+ ...sourceFingerprint(stats),
813
+ contentVerifiedNs: leafBoundary(this.clock, stats),
1072
814
  };
815
+ return { item: { base, result }, contentReads: 1 };
1073
816
  }
1074
- captureLink(input) {
1075
- const { root, path, absolutePath, stats, parent, exact } = input;
1076
- const base = exact ?? this.uniqueMovedBase(stats.dev, stats.ino, absolutePath);
1077
- if (base && base.type !== "link")
1078
- return null;
1079
- const fingerprintTrusted = base !== null
1080
- && base.relativePath === path
1081
- && this.materializedByUuid.get(base.uuid) !== undefined
1082
- && sameFingerprint(base, stats)
1083
- && base.contentVerifiedAtMs !== null
1084
- && stats.mtimeMs < base.contentVerifiedAtMs;
1085
- if (fingerprintTrusted)
1086
- return { item: null, contentReads: 0 };
1087
- const canReuseMovedTarget = base !== null
1088
- && base.relativePath !== path
1089
- && base.deviceNumber === stats.dev
1090
- && base.inode === stats.ino
1091
- && base.byteSize === stats.size
1092
- && base.modifiedTimeMs === stats.mtimeMs
1093
- && base.filesystemMode === stats.mode
1094
- && base.payloadVersion !== null;
1095
- const target = canReuseMovedTarget ? null : readlinkSync(absolutePath);
1096
- const targetBytes = target === null ? null : Buffer.from(target, "utf8");
1097
- const uuid = base?.uuid ?? randomUUID();
1098
- const payloadVersion = target === null
1099
- ? base?.payloadVersion ?? null
1100
- : sha256Hex(targetBytes);
1101
- if (!payloadVersion)
1102
- return null;
1103
- if (targetBytes) {
1104
- sealCapturedArtifact(this.cacheDir, {
1105
- entityId: uuid,
1106
- entityType: "link",
1107
- kind: "link",
1108
- contentHash: payloadVersion,
1109
- }, targetBytes);
1110
- }
1111
- const name = this.capturedName(base, path, parent.uuid);
817
+ async captureLink(input) {
818
+ const { root, path, absolutePath, stats, parent, base, uuid, sources } = input;
819
+ sources.remember(absolutePath, stats);
820
+ if (this.unchangedLeaf(input))
821
+ return { item: { base, result: base }, contentReads: 0 };
822
+ const targetBytes = Buffer.from(readlinkSync(absolutePath), "utf8");
823
+ const payloadVersion = sha256Hex(targetBytes);
824
+ await sealContent(this.cacheDir, {
825
+ entityId: uuid,
826
+ entityType: "link",
827
+ kind: "link",
828
+ contentHash: payloadVersion,
829
+ }, targetBytes, "detect-link", this.cargo);
830
+ const name = observedPortableName(base, parent.uuid, posix.basename(path));
1112
831
  const result = {
1113
832
  resourceId: root.resourceId,
1114
833
  rootUUID: root.rootUUID,
@@ -1128,99 +847,97 @@ export class NamedDetectRuntime {
1128
847
  transportVersion: null,
1129
848
  relativePath: path,
1130
849
  absolutePath,
1131
- deviceNumber: stats.dev,
1132
- inode: stats.ino,
1133
- byteSize: stats.size,
1134
- modifiedTimeMs: stats.mtimeMs,
1135
- changedTimeMs: stats.ctimeMs,
1136
- filesystemMode: stats.mode,
1137
- contentVerifiedAtMs: target === null ? base?.contentVerifiedAtMs ?? null : Date.now(),
1138
- };
1139
- const replay = base !== null && base.payloadVersion === result.payloadVersion
1140
- ? { kind: "structure" }
1141
- : target === null ? { kind: "structure" } : {
1142
- kind: "link",
1143
- resultPayloadVersion: payloadVersion,
1144
- target,
1145
- };
1146
- return {
1147
- item: {
1148
- base,
1149
- result,
1150
- proposal: detectedChangeProposal({
1151
- base: base ? portable(base) : null,
1152
- result: portable(result),
1153
- replay,
1154
- }),
1155
- },
1156
- contentReads: target === null ? 0 : 1,
850
+ ...sourceFingerprint(stats),
851
+ contentVerifiedNs: leafBoundary(this.clock, stats),
1157
852
  };
853
+ return { item: { base, result }, contentReads: 1 };
854
+ }
855
+ /** An existing leaf still at its address whose exact source signature
856
+ * predates its last content verification needs no byte read. */
857
+ unchangedLeaf({ path, stats, parent, base }) {
858
+ return base !== null
859
+ && base.relativePath === path
860
+ && base.parentUUID === parent.uuid
861
+ && this.ground.physical(base.uuid) !== undefined
862
+ && sourceReusable(base, stats);
863
+ }
864
+ /** A result whose content proof this pass reused staged no cargo. Its
865
+ * bytes are read once, and must be the observed payload; the source the
866
+ * observation remembered is validated again at admission. */
867
+ async sealUnread(row) {
868
+ const payloadVersion = row.payloadVersion;
869
+ if (row.type === "link") {
870
+ const target = Buffer.from(readlinkSync(row.absolutePath), "utf8");
871
+ if (sha256Hex(target) !== payloadVersion) {
872
+ throw new CaptureSourceChangedError(`link changed before sealing: ${row.absolutePath}`);
873
+ }
874
+ await sealContent(this.cacheDir, { entityId: row.uuid, entityType: "link", kind: "link", contentHash: payloadVersion }, target, "detect-link", this.cargo);
875
+ return;
876
+ }
877
+ const content = await captureContent(row.absolutePath, lstatSync(row.absolutePath, { bigint: true }), this.cacheDir, { lane: "named", cargo: this.cargo });
878
+ if (content.contentHash !== payloadVersion) {
879
+ throw new CaptureSourceChangedError(`file changed before sealing: ${row.absolutePath}`);
880
+ }
881
+ }
882
+ /** Replay construction follows the portable sharing decision. */
883
+ async proposalFor(shared, result) {
884
+ if (shared?.version === result.version)
885
+ return null;
886
+ const replay = !replaysPayload(shared, result) ? { kind: "structure" }
887
+ : await this.replayFor(shared, result);
888
+ return detectedChangeProposal({ base: shared ? portable(shared) : null, result: portable(result), replay });
1158
889
  }
1159
890
  enclosedByRepository(row) {
1160
891
  let cursor = row;
1161
892
  const seen = new Set();
1162
- while (cursor.parentUUID !== null) {
893
+ for (let parentUUID = localParentOf(cursor); parentUUID !== null; parentUUID = localParentOf(cursor)) {
1163
894
  if (seen.has(cursor.uuid))
1164
- throw new Error("Detect notebook has a parent cycle");
895
+ throw new Error("Detect physical ground has a parent cycle");
1165
896
  seen.add(cursor.uuid);
1166
- const parent = this.rowAtUuid.get(cursor.parentUUID);
897
+ const parent = this.ground.physical(parentUUID);
1167
898
  if (!parent)
1168
- throw new Error(`Detect notebook has no parent ${cursor.parentUUID}`);
899
+ throw new Error(`Detect physical ground has no parent ${parentUUID}`);
1169
900
  if (parent.type === "repo.git")
1170
901
  return true;
1171
902
  cursor = parent;
1172
903
  }
1173
904
  return false;
1174
905
  }
1175
- readUserPolicy() {
1176
- const ignore = join(this.userRoot, ".amalgmignore");
1177
- return createUserGroundEnrollmentPolicy(existsSync(ignore) ? readFileSync(ignore, "utf8") : "");
906
+ /** A trashed ancestor makes every old descendant ineligible for identity
907
+ * reuse, even when its stored status is still active. Each inventory's
908
+ * lifecycle is read along its own parent chain. */
909
+ isEffectivelyActive(row, inventory = "entities") {
910
+ return effectivelyActive(row, uuid => inventory === "entities" ? this.ground.physical(uuid) : this.ground.sharing(uuid));
1178
911
  }
1179
- uniqueMovedBase(deviceNumber, inode, absolutePath) {
1180
- const candidates = this.rowsAtPhysicalIdentity.all(deviceNumber, inode)
1181
- .filter((row) => row.absolutePath !== absolutePath
1182
- // Windows may name only the destination of a rename. The entity
1183
- // projection proves this physical identity was still materialized
1184
- // before the current observation; notebook-only rows deliberately do
1185
- // not qualify, so a later inode reuse cannot steal missing identity.
1186
- && this.materializedByUuid.get(row.uuid) !== undefined
1187
- && !existsSync(row.absolutePath));
1188
- return candidates.length === 1 ? candidates[0] : null;
912
+ activeRowAtPath(rootUUID, path) {
913
+ return this.ground.atPath(rootUUID, path).find(row => this.isEffectivelyActive(row));
1189
914
  }
1190
- replayFor(base, result) {
1191
- if (result.type === "file.text") {
1192
- const resultBytes = readFileSync(join(this.cacheDir, `${result.payloadVersion}.bin`));
1193
- const baseFile = base?.payloadVersion ? join(this.cacheDir, `${base.payloadVersion}.bin`) : null;
1194
- const baseBytes = baseFile && existsSync(baseFile) ? readFileSync(baseFile) : null;
1195
- return exactTextReplay({
1196
- entityId: result.uuid,
1197
- basePayloadVersion: base?.payloadVersion ?? null,
1198
- baseBytes,
1199
- resultPayloadVersion: result.payloadVersion,
1200
- resultBytes,
1201
- sha256Hex,
1202
- });
915
+ /** A text or link replay carries sealed cache bytes, built on a content
916
+ * thread; a binary replay carries its manifest. */
917
+ async replayFor(base, result) {
918
+ if (result.type === "file.text" || result.type === "link") {
919
+ return replayCachedContent(this.cacheDir, { entityId: result.uuid, type: result.type,
920
+ basePayloadVersion: base?.payloadVersion ?? null, resultPayloadVersion: result.payloadVersion }, "named");
1203
921
  }
1204
- const manifestFile = join(this.cacheDir, "manifests", `${result.payloadVersion}.json`);
1205
- const checked = checkContentManifest(JSON.parse(readFileSync(manifestFile, "utf8")), result.payloadVersion);
1206
- if (!checked.ok)
1207
- throw new Error(checked.error);
922
+ const artifact = artifactForRecord(portable(result));
923
+ if (!artifact)
924
+ throw new Error(`detected binary file ${result.uuid} has no content head`);
1208
925
  return {
1209
926
  kind: "file.binary",
1210
- resultPayloadVersion: result.payloadVersion,
1211
- manifest: checked.value,
927
+ resultPayloadVersion: artifact.contentHash,
928
+ manifest: await contentManifest(this.cacheDir, artifact),
1212
929
  };
1213
930
  }
1214
931
  }
1215
- /** `.git/index.lock`, `.git/HEAD.lock`, `.git/refs/heads/main.lock`, and
1216
- * their packed and worktree-local equivalents: Git's transient control files. */
1217
- function isGitLockPath(pathInsideGit) {
1218
- const name = pathInsideGit.at(-1);
1219
- return pathInsideGit.length > 0 && name !== undefined && name.endsWith(".lock");
932
+ /** A Record replays its result's payload unless it shares its base's payload,
933
+ * when it replays structure only. Only a payload replay carries cargo. */
934
+ function replaysPayload(shared, result) {
935
+ return shared === null || shared.type !== result.type
936
+ || shared.payloadVersion !== result.payloadVersion || shared.transportVersion !== result.transportVersion;
1220
937
  }
1221
938
  function tryLstat(path) {
1222
939
  try {
1223
- return lstatSync(path);
940
+ return lstatSync(path, { bigint: true });
1224
941
  }
1225
942
  catch (error) {
1226
943
  const code = error.code;
@@ -1229,16 +946,4 @@ function tryLstat(path) {
1229
946
  throw error;
1230
947
  }
1231
948
  }
1232
- function repositoryIdentity(repository, rows) {
1233
- return indexRepositoryTerritory(rows).ownedBy(repository.uuid)
1234
- .map((row) => ({
1235
- path: repository.relativePath
1236
- ? row.relativePath.slice(repository.relativePath.length + 1)
1237
- : row.relativePath,
1238
- uuid: row.uuid,
1239
- type: row.type,
1240
- payloadVersion: row.type === "repo.git" ? null : row.payloadVersion,
1241
- }))
1242
- .sort((left, right) => left.path.localeCompare(right.path));
1243
- }
1244
949
  //# sourceMappingURL=runtime.js.map