@amalgm/shell 0.1.164-preview.36061234379 → 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,343 +1,213 @@
1
1
  import { createHash } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
- import { chmodSync, closeSync, createReadStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readSync, readlinkSync, rmSync, symlinkSync, statSync, writeFileSync, } from "node:fs";
2
+ import { closeSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readdirSync, readSync, rmSync, writeFileSync, } from "node:fs";
3
+ import { mkdtemp, open, readFile, rm } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { dirname, join, resolve } from "node:path";
6
- import { Transform } from "node:stream";
7
- import { pipeline } from "node:stream/promises";
8
- import { REPOSITORY_CARD_VERSION, applyRepositoryIdentity, classifyFile, decodeRepositoryTransport, deriveCardId, deriveCheckpointId, deriveStateId, encodeRepositoryTransport, inspectRepositoryTransport, planRepositoryIdentity, repositoryIdentityHash, repositoryTransportMetadataBytes, } from "@amalgm/live";
9
- import { gitText, runGit, runGitToFile, tryGit, tryGitText, } from "./git-command.js";
6
+ import { applyRepositoryIdentity, assertRepositoryTransportTruth, deriveCardId, deriveCheckpointId, deriveStateId, encodeRepositoryTransport, inspectRepositoryTransport, ITEMS_PER_BOUNDARY, repositoryIdentityDelta, repositoryIdentityHash, repositoryIdentityMap, repositoryPayloadVersion, repositoryTransportMetadataBytes, } from "@amalgm/live";
7
+ import { gitHost, gitText, runGit } from "./git-command.js";
8
+ import { busyMarker, gitOperationWitness, mergeOperation, REVEAL_MARKER } from "./git-operation-host.js";
10
9
  import { atomicWrite } from "./filesystem.js";
10
+ import { repositoryIdentityMapSteps } from "./repository-identity-steps.js";
11
+ import { CaptureSourceChangedError, hashContentFile, writeCacheHint } from "./content-cache-host.js";
12
+ import { literalRepositoryIdentity, literalRepositoryIdentityMapSteps, collectLiteralRepositoryView, observeLiteralPaths, } from "./git-literal-host.js";
13
+ import { leafPayload } from "./git-literal-read.js";
14
+ import { PrivateGitView } from "./git-private-view.js";
15
+ import { installLiteralCheckpoint } from "./git-literal-replay.js";
16
+ import { prepareLiteralCheckpoint } from "./git-checkpoint-preparation.js";
17
+ import { checkpointReads, constructLiteralCheckpoint } from "./git-checkpoint-construction.js";
18
+ import { PhysicalPathWitnesses } from "./physical-path-witness.js";
11
19
  import { recordFileDurability } from "./durability-barrier.js";
12
- const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
13
- const BUSY_MARKERS = [
14
- "rebase-merge",
15
- "rebase-apply",
16
- "CHERRY_PICK_HEAD",
17
- "REVERT_HEAD",
18
- ];
20
+ import { GitCapture } from "./git-capture.js";
21
+ import { stashList } from "./git-card.js";
22
+ import { eachInTurns, finishStepsInTurns, turnBoundary } from "./turns.js";
23
+ import { looseRefs } from "./git-ref-store.js";
24
+ import { gitStorageDirectory } from "./git-binding.js";
25
+ import { admissionProof, closeSource, sourceProofs, witnessProof } from "./source-proof.js";
26
+ import { physicalKey, sourceSignature } from "./source-signature.js";
19
27
  const sha256Hex = (input) => createHash("sha256").update(input).digest("hex");
20
28
  const bufferView = (bytes) => Buffer.isBuffer(bytes)
21
29
  ? bytes
22
30
  : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
23
- /** Classification observes only the filesystem marker. Git command health
24
- * belongs to Card + Checkpoint capture and cannot demote repository ground. */
25
- export function hasGitMarker(directory) {
31
+ /** What one lstat of a `.git` entry says. */
32
+ export const gitMarker = (marker) => ({ physicalKey: physicalKey(marker),
33
+ kind: marker.isDirectory() ? "directory" : marker.isFile() ? "file" : "other" });
34
+ /** The `.git` entry as it stands now; null when absent. */
35
+ export function observeGitMarker(directory) {
26
36
  try {
27
- const marker = lstatSync(join(directory, ".git"));
28
- return marker.isDirectory() || marker.isFile();
37
+ return gitMarker(lstatSync(join(directory, ".git"), { bigint: true }));
29
38
  }
30
39
  catch (error) {
31
40
  const code = error.code;
32
41
  if (code === "ENOENT" || code === "ENOTDIR")
33
- return false;
42
+ return null;
34
43
  throw error;
35
44
  }
36
45
  }
37
- /** Every durable ref except Amalgm's private import namespace and
38
- * `refs/stash`, whose value names only the top of a stack that lives in the
39
- * reflog; the stack travels whole through `stashList`. */
40
- function refList(repository) {
41
- const output = tryGitText(repository, ["for-each-ref", "--format=%(refname) %(objectname)", "refs"]);
42
- if (!output)
43
- return [];
44
- return output.split("\n").map((line) => {
45
- const separator = line.lastIndexOf(" ");
46
- return { name: line.slice(0, separator), commit: line.slice(separator + 1) };
47
- }).filter((ref) => ref.name !== "refs/stash" && !ref.name.startsWith("refs/amalgm-import/"))
48
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
49
- }
50
- /** The stash stack newest first, exactly as Git's reflog holds it. `%gs` is
51
- * the reflog subject after Git's own whitespace normalization, so storing a
52
- * captured message reads back unchanged. */
53
- function stashList(repository) {
54
- const output = gitText(repository, ["stash", "list", "--format=%H%x00%gs"]);
55
- if (!output)
56
- return [];
57
- return output.split("\n").map((line) => {
58
- const separator = line.indexOf("\0");
59
- return { commit: line.slice(0, separator), message: line.slice(separator + 1) };
60
- });
61
- }
62
- function remoteList(repository) {
63
- const output = tryGitText(repository, ["config", "--get-regexp", "^remote\\..*\\.url$"]);
64
- if (!output)
65
- return [];
66
- return output.split("\n").map((line) => {
67
- const separator = line.indexOf(" ");
68
- return { name: line.slice(7, separator - 4), url: line.slice(separator + 1) };
69
- }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
70
- }
71
- /** A shallow boundary is required to interpret the repository's object graph.
72
- * It travels with the Git parcel, but does not change the semantic Card id. */
73
- function shallowBoundary(repository) {
74
- const file = resolve(repository, gitText(repository, ["rev-parse", "--git-path", "shallow"]));
75
- if (!existsSync(file))
76
- return [];
77
- const commits = readFileSync(file, "utf8").split(/\r?\n/).filter(Boolean).sort();
78
- if (commits.some((commit) => !/^[0-9a-f]{40,64}$/.test(commit))) {
79
- throw new Error("repository has an invalid shallow boundary");
80
- }
81
- return commits;
82
- }
46
+ /** Classification observes only the filesystem marker. Git command health
47
+ * belongs to Card + Checkpoint capture and cannot demote repository ground. */
48
+ export const marksRepository = (marker) => marker !== null && marker.kind !== "other";
49
+ export const hasGitMarker = (directory) => marksRepository(observeGitMarker(directory));
50
+ /** The nested repositories a reading of `identity`'s repository must not enter. */
83
51
  export function nestedRepositoryPaths(identity) {
84
52
  return identity
85
53
  .filter((entry) => entry.type === "repo.git")
86
54
  .map((entry) => entry.path)
87
55
  .sort();
88
56
  }
89
- function pathWithinRepositoryBoundary(path, boundaries) {
90
- const candidate = path.endsWith("/") ? path.slice(0, -1) : path;
91
- return boundaries.some((boundary) => candidate === boundary || candidate.startsWith(`${boundary}/`));
92
- }
93
- function worktreePathspec(boundaries, excludedPaths = []) {
94
- return boundaries.length === 0 && excludedPaths.length === 0
95
- ? []
96
- : [
97
- "--", ".",
98
- ...boundaries.flatMap((path) => [`:(exclude)${path}`, `:(exclude)${path}/**`]),
99
- ...excludedPaths.map((path) => `:(exclude,literal)${path}`),
100
- ];
101
- }
102
- function portableWorktreePath(path) {
103
- return path.length > 0 && !path.startsWith("/") && !path.includes("\0")
104
- && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
105
- }
106
- function worktreeLeaf(repository, path) {
107
- if (!portableWorktreePath(path))
108
- throw new Error(`repository has an unsafe conflicted path: ${path}`);
109
- const absolutePath = join(repository, path);
110
- try {
111
- const stats = lstatSync(absolutePath);
112
- if (stats.isSymbolicLink()) {
113
- const content = Buffer.from(readlinkSync(absolutePath), "utf8");
114
- return { mode: "120000", content, sha256: sha256Hex(content) };
115
- }
116
- if (stats.isDirectory())
117
- return null;
118
- const content = readFileSync(absolutePath);
119
- return {
120
- mode: stats.mode & 0o111 ? "100755" : "100644",
121
- content,
122
- sha256: sha256Hex(content),
123
- };
124
- }
125
- catch (error) {
126
- if (error.code === "ENOENT")
127
- return null;
128
- throw error;
129
- }
130
- }
131
- function unmergedPaths(repository) {
132
- const fields = runGit(repository, ["ls-files", "--unmerged", "-z"])
133
- .toString("utf8").split("\0").filter(Boolean);
134
- const grouped = new Map();
135
- for (const field of fields) {
136
- const separator = field.indexOf("\t");
137
- const [mode, objectId, rawStage] = field.slice(0, separator).split(" ");
138
- const path = field.slice(separator + 1);
139
- const stage = Number(rawStage);
140
- if (separator < 0 || !mode || !/^[0-9a-f]{40}([0-9a-f]{24})?$/.test(objectId ?? "")
141
- || ![1, 2, 3].includes(stage) || !portableWorktreePath(path)) {
142
- throw new Error("Git returned an invalid unmerged index entry");
143
- }
144
- const entries = grouped.get(path) ?? [];
145
- if (entries.some((entry) => entry.stage === stage)) {
146
- throw new Error(`Git returned a duplicate unmerged index stage: ${path}`);
147
- }
148
- entries.push({ mode, objectId: objectId, stage: stage });
149
- grouped.set(path, entries);
150
- }
151
- return [...grouped.entries()]
152
- .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
153
- .map(([path, stages]) => ({
154
- path,
155
- stages: stages.sort((left, right) => left.stage - right.stage),
156
- worktree: worktreeLeaf(repository, path),
157
- }));
158
- }
159
- function optionalGitFile(repository, name) {
160
- try {
161
- return readFileSync(join(gitDirectory(repository), name));
162
- }
163
- catch (error) {
164
- if (error.code === "ENOENT")
165
- return Buffer.alloc(0);
166
- throw error;
167
- }
168
- }
169
- function mergeOperation(repository) {
170
- const heads = optionalGitFile(repository, "MERGE_HEAD").toString("utf8")
171
- .split(/\r?\n/).filter(Boolean);
172
- if (heads.length === 0)
173
- return null;
174
- if (heads.some((head) => !/^[0-9a-f]{40}([0-9a-f]{24})?$/.test(head))) {
175
- throw new Error("repository has an invalid MERGE_HEAD");
176
- }
177
- return {
178
- kind: "merge",
179
- heads,
180
- message: optionalGitFile(repository, "MERGE_MSG"),
181
- mode: optionalGitFile(repository, "MERGE_MODE"),
182
- };
183
- }
184
- function untrackedFiles(repository, nestedRepositories) {
185
- // Git ignore is not Amalgm enrollment policy. Every on-disk worktree leaf
186
- // travels unless the registered root's .amalgmignore excludes it.
187
- const output = runGit(repository, ["ls-files", "--others", "-z"])
188
- .toString("utf8");
189
- return output.split("\0").filter(Boolean).sort().flatMap((relativePath) => {
190
- if (pathWithinRepositoryBoundary(relativePath, nestedRepositories))
191
- return [];
192
- if (relativePath.endsWith("/"))
193
- return [];
194
- const absolutePath = join(repository, relativePath);
195
- try {
196
- const stats = lstatSync(absolutePath);
197
- if (stats.isSymbolicLink()) {
198
- const content = Buffer.from(readlinkSync(absolutePath), "utf8");
199
- return [{ path: relativePath, mode: "120000", content, sha256: sha256Hex(content) }];
200
- }
201
- if (stats.isDirectory())
202
- return [];
203
- const content = readFileSync(absolutePath);
204
- return [{
205
- path: relativePath,
206
- mode: stats.mode & 0o111 ? "100755" : "100644",
207
- content,
208
- sha256: sha256Hex(content),
209
- }];
210
- }
211
- catch (error) {
212
- if (error.code === "ENOENT")
213
- return [];
214
- throw error;
215
- }
216
- });
217
- }
218
57
  /** The common `.git` directory needs no Git invocation; a `.git` file
219
58
  * (worktree, submodule) is resolved by Git itself. */
220
- function gitDirectory(repository) {
221
- const common = join(repository, ".git");
222
- try {
223
- if (lstatSync(common).isDirectory())
224
- return common;
225
- }
226
- catch {
227
- // Resolved below.
228
- }
229
- return resolve(repository, gitText(repository, ["rev-parse", "--git-dir"]));
230
- }
231
- /** An Amalgm reveal rewrites a repository in place over several Git
232
- * invocations. Like MERGE_HEAD, this file in the Git directory says the
233
- * repository is mid-operation: capture refuses it (so Detect never records a
234
- * torn moment as truth, and retries once it settles), while the reveal that
235
- * owns it resumes from whatever state an interruption left behind. */
236
- const REVEAL_MARKER = "amalgm-reveal";
59
+ const gitDirectory = (repository) => gitStorageDirectory(repository).directory;
237
60
  export function markRepositoryReveal(repository) {
238
61
  writeFileSync(join(gitDirectory(repository), REVEAL_MARKER), "");
239
62
  }
240
63
  export function repositoryRevealInProgress(repository) {
241
64
  return hasGitMarker(repository) && existsSync(join(gitDirectory(repository), REVEAL_MARKER));
242
65
  }
243
- function busyMarker(repository, ownReveal) {
244
- const directory = gitDirectory(repository);
245
- return [...BUSY_MARKERS, ...(ownReveal ? [] : [REVEAL_MARKER])]
246
- .find((marker) => existsSync(join(directory, marker))) ?? null;
247
- }
248
66
  const CAPTURE_ATTEMPTS = 3;
249
- /** Card and Checkpoint are read by several Git invocations. One record must
250
- * describe one repository moment, so the Checkpoint diffs against the commit
251
- * resolved at the start and the whole capture is retried when HEAD moves
252
- * underneath it rather than committing a torn, unreproducible state. */
253
- function captureState(repository, nestedRepositories = [], ownReveal = false) {
254
- let moved = null;
255
- for (let attempt = 0; attempt < CAPTURE_ATTEMPTS; attempt += 1) {
256
- const state = captureStateOnce(repository, nestedRepositories, ownReveal);
257
- const branch = tryGitText(repository, ["symbolic-ref", "-q", "--short", "HEAD"]);
258
- const commit = tryGitText(repository, ["rev-parse", "-q", "--verify", "HEAD"]);
259
- if (branch === state.card.head.branch && commit === state.card.head.commit)
260
- return state;
261
- moved = `${state.card.head.branch ?? "detached"}@${state.card.head.commit ?? "none"}`
262
- + ` -> ${branch ?? "detached"}@${commit ?? "none"}`;
263
- }
264
- throw new Error(`repository HEAD kept moving during capture: ${moved}`);
265
- }
266
- function captureStateOnce(repository, nestedRepositories, ownReveal) {
267
- const busy = busyMarker(repository, ownReveal);
67
+ /** The first attempt uses the caller's capture; a source that moved while
68
+ * it was read is asked again from a fresh one. */
69
+ async function retrySourceChange(capture, operation) {
70
+ for (let attempt = 0;; attempt += 1) {
71
+ try {
72
+ return await operation(attempt === 0 ? capture : new GitCapture(capture.repository));
73
+ }
74
+ catch (error) {
75
+ if (!(error instanceof CaptureSourceChangedError) || attempt + 1 >= CAPTURE_ATTEMPTS)
76
+ throw error;
77
+ }
78
+ }
79
+ }
80
+ /** One collected repository moment with its unclosed source. Detect retains
81
+ * the proof and closes it once, at the admission that trusts it; Apply's
82
+ * readback closes it itself. */
83
+ async function collectState(capture, nestedRepositories, lane, proven, ownReveal = false) {
84
+ const directory = gitDirectory(capture.repository);
85
+ // A busy repository is unsettled, never a fault: it reads again once the
86
+ // operation that owns the marker ends.
87
+ const busy = busyMarker(directory, ownReveal);
268
88
  if (busy)
269
- throw new Error(`repository is busy: ${busy}`);
270
- const branch = tryGitText(repository, ["symbolic-ref", "-q", "--short", "HEAD"]);
271
- const commit = tryGitText(repository, ["rev-parse", "-q", "--verify", "HEAD"]);
272
- const shallow = shallowBoundary(repository);
273
- const unmerged = unmergedPaths(repository);
274
- const operation = mergeOperation(repository);
275
- const checkpointPathspec = worktreePathspec(nestedRepositories, unmerged.map((entry) => entry.path));
276
- const card = {
277
- version: REPOSITORY_CARD_VERSION,
278
- head: { branch, commit, detached: branch === null && commit !== null },
279
- refs: refList(repository),
280
- stash: stashList(repository),
281
- remotes: remoteList(repository),
282
- ...(shallow.length > 0 ? { shallow } : {}),
283
- };
89
+ throw new CaptureSourceChangedError(`repository is busy: ${busy}`);
90
+ const configuration = await capture.configuration();
91
+ const { value: card, proof: cardProof } = await capture.card();
92
+ const operation = mergeOperation(directory);
93
+ const view = await collectLiteralRepositoryView(capture, card.head.commit, nestedRepositories, lane, proven);
284
94
  const checkpoint = {
285
- baseCommit: commit,
286
- branch,
287
- stagedPatch: runGit(repository, [
288
- "diff", "--cached", "--binary", "--full-index", commit ?? EMPTY_TREE,
289
- ...checkpointPathspec,
290
- ]),
291
- unstagedPatch: runGit(repository, [
292
- "diff", "--binary", "--full-index", ...checkpointPathspec,
293
- ]),
294
- unmerged,
295
- operation,
296
- untracked: untrackedFiles(repository, nestedRepositories),
95
+ baseCommit: card.head.commit, branch: card.head.branch,
96
+ stagedPatch: view.stagedPatch, unstagedPatch: view.unstagedPatch,
97
+ unmerged: view.unmerged, untracked: view.untracked, operation,
297
98
  };
298
99
  const cardId = deriveCardId(card, sha256Hex);
299
100
  const checkpointId = deriveCheckpointId(checkpoint, sha256Hex);
300
- return {
301
- stateId: deriveStateId(cardId, checkpointId, sha256Hex),
302
- cardId,
303
- checkpointId,
304
- card,
305
- checkpoint,
306
- pack: null,
307
- };
101
+ // Operation and busy markers are native files in the observed Git
102
+ // directory, so admission re-reads them with the other physical witnesses.
103
+ const markers = witnessProof([gitOperationWitness(directory, ownReveal, operation)]);
104
+ const proof = sourceProofs([cardProof, configuration, markers, view.proof]);
105
+ return { capture, state: { stateId: deriveStateId(cardId, checkpointId, sha256Hex),
106
+ cardId, checkpointId, card, checkpoint, pack: null }, view, configuration, proof };
107
+ }
108
+ /** Physical observation for publication. Selection and package sealing have
109
+ * not happened; the caller retains this source proof, unclosed, and its
110
+ * admission closes it once. */
111
+ export function observeRepositoryForPublication(capture, identity, lane, proven) {
112
+ return retrySourceChange(capture, attempt => collectState(attempt, nestedRepositoryPaths(identity), lane, proven));
113
+ }
114
+ /** Seal an observation when publication selected every observed member. No
115
+ * second Git/worktree observation is necessary; the original proof still
116
+ * closes once, at admission. */
117
+ export async function captureObservedRepository(observed, identity, observedIdentity, prior, previousIdentity) {
118
+ return sealRepository(observed, await selectedLiteralIdentity(identity, observed.view, observedIdentity), prior, previousIdentity);
119
+ }
120
+ /** The literal identity of the selected members. Publication usually selects
121
+ * exactly the observed members, whose literal map the observation already
122
+ * derived: equal paths and UUIDs, where a leaf's type and payload come from
123
+ * the same view and every other entry must match as recorded. */
124
+ async function selectedLiteralIdentity(selected, view, observed) {
125
+ const derived = () => finishStepsInTurns(literalRepositoryIdentityMapSteps(selected, view));
126
+ if (selected.length !== observed.entries.length)
127
+ return derived();
128
+ const byPath = new Map();
129
+ await eachInTurns(observed.entries, entry => byPath.set(entry.path, entry));
130
+ let visited = 0;
131
+ for (const entry of selected) {
132
+ const other = byPath.get(entry.path);
133
+ if (other?.uuid !== entry.uuid || !(view.leaves.has(entry.path)
134
+ || (other.type === entry.type && (other.payloadVersion ?? null) === (entry.payloadVersion ?? null))))
135
+ return derived();
136
+ if (++visited % ITEMS_PER_BOUNDARY === 0)
137
+ await turnBoundary();
138
+ }
139
+ return observed;
140
+ }
141
+ /** The same Card + Checkpoint without the untracked names a caller accounts
142
+ * for separately. */
143
+ function withoutUntracked(state, omitted) {
144
+ const checkpoint = { ...state.checkpoint, untracked: state.checkpoint.untracked.filter(file => !omitted(file.path)) };
145
+ const checkpointId = deriveCheckpointId(checkpoint, sha256Hex);
146
+ return { ...state, checkpoint, checkpointId, stateId: deriveStateId(state.cardId, checkpointId, sha256Hex) };
308
147
  }
309
148
  const cardTips = (card) => [...new Set([
310
149
  ...(card.head.commit ? [card.head.commit] : []),
311
- ...card.refs.map((ref) => ref.commit),
150
+ ...card.refs.flatMap((ref) => ref.kind === "direct" ? [ref.commit] : []),
312
151
  ...card.stash.map((entry) => entry.commit),
313
152
  ])];
314
- /** Produce the same native thin-pack work as send-pack: current wants minus
153
+ /** The commits a state names: its Card tips and an unfinished merge's heads. */
154
+ const stateCommits = (state) => [...new Set([...cardTips(state.card), ...(state.checkpoint.operation?.heads ?? [])])];
155
+ /** Every object a state's pack delivers: its commits and each conflict
156
+ * stage. A gitlink records a commit owned by the nested repository, which
157
+ * the outer repository preserves as an opaque index value but never packs. */
158
+ const stateWants = (state) => [...new Set([...stateCommits(state),
159
+ ...state.checkpoint.unmerged.flatMap((entry) => entry.stages
160
+ .filter((stage) => stage.mode !== "160000").map((stage) => stage.objectId))])];
161
+ /** The previous boundaries the receiver holds: those its tips reach under
162
+ * the horizon it received. */
163
+ async function receiverHolds(binding, horizon, tips, boundaries) {
164
+ const receiver = await PrivateGitView.open(binding, horizon);
165
+ try {
166
+ return [...(await receiver.reached(tips, boundaries)).keys()];
167
+ }
168
+ finally {
169
+ await receiver.close();
170
+ }
171
+ }
172
+ /** Produce the same native thin-pack work as upload-pack: current wants minus
315
173
  * receiver haves. Ref updates and Amalgm metadata remain outside the pack. */
316
- function nativePack(repository, current, previous) {
317
- const wants = [...new Set([
318
- ...cardTips(current.card),
319
- // A gitlink records a commit owned by the nested repository. The outer
320
- // repository preserves that opaque index value but can never pack it.
321
- ...current.checkpoint.unmerged.flatMap((entry) => entry.stages
322
- .filter((stage) => stage.mode !== "160000")
323
- .map((stage) => stage.objectId)),
324
- ...(current.checkpoint.operation?.heads ?? []),
325
- ])];
174
+ async function nativePack(binding, current, previous) {
175
+ const commits = stateCommits(current);
176
+ const wants = stateWants(current);
326
177
  if (wants.length === 0)
327
178
  return null;
328
- const haves = (previous ? cardTips(previous) : []).filter((commit) => tryGit(repository, ["cat-file", "-e", `${commit}^{object}`]) !== null);
329
- const revisions = Buffer.from([
330
- ...wants,
331
- ...haves.map((commit) => `^${commit}`),
332
- "",
333
- ].join("\n"), "utf8");
334
- const scratch = mkdtempSync(join(tmpdir(), "amalgm-repo-pack-"));
179
+ const scratch = await mkdtemp(join(tmpdir(), "amalgm-repo-pack-"));
335
180
  const file = join(scratch, "repository.pack");
336
181
  try {
337
- runGitToFile(repository, [
338
- "pack-objects", "--stdout", "--revs", "--thin", "--delta-base-offset",
339
- ], revisions, file);
340
- const bytes = readFileSync(file);
182
+ const view = await PrivateGitView.open(binding, current.card.shallow);
183
+ try {
184
+ // The receiver holds each previous tip's closure under the previous
185
+ // horizon. A previous boundary this horizon moved past (Git's
186
+ // unshallow) is a commit it holds, if at all, without its parents. As
187
+ // upload-pack does, every traversal stops at those boundaries, so the
188
+ // haves subtract only what the receiver holds; each one this state
189
+ // reaches wants its parents, so the history newly exposed beneath it
190
+ // travels; and each one the receiver holds is an edge, so its tree
191
+ // never travels again.
192
+ const boundary = new Set(current.card.shallow ?? []);
193
+ const received = previous?.shallow ?? [];
194
+ const unshallowed = received.filter(commit => !boundary.has(commit));
195
+ const haves = previous ? await view.present(cardTips(previous)) : [];
196
+ const [exposed, held] = unshallowed.length ? await Promise.all([
197
+ view.reached(commits, unshallowed).then(found => [...found.values()].flat()),
198
+ receiverHolds(binding, received, haves, unshallowed),
199
+ ]) : [[], []];
200
+ const revisions = Buffer.from([
201
+ ...unshallowed.map(commit => `--shallow ${commit}`),
202
+ ...new Set([...wants, ...exposed]),
203
+ ...[...haves, ...held].map(commit => `^${commit}`), "",
204
+ ].join("\n"), "utf8");
205
+ await view.exportPack(revisions, file, received.length > 0);
206
+ }
207
+ finally {
208
+ await view.close();
209
+ }
210
+ const bytes = await readFile(file);
341
211
  if (bytes.byteLength < 12 || bytes.subarray(0, 4).toString("ascii") !== "PACK") {
342
212
  throw new Error("Git produced an invalid repository pack");
343
213
  }
@@ -346,73 +216,248 @@ function nativePack(repository, current, previous) {
346
216
  return { bytes, sha256: sha256Hex(bytes) };
347
217
  }
348
218
  finally {
349
- rmSync(scratch, { recursive: true, force: true });
350
- }
351
- }
352
- function identityFromCheckpoint(identity, state) {
353
- const untracked = new Map(state.checkpoint.untracked.map((entry) => [entry.path, entry]));
354
- return identity.map((entry) => {
355
- const current = untracked.get(entry.path);
356
- if (!current)
357
- return entry;
358
- const type = current.mode === "120000"
359
- ? "link"
360
- : classifyFile({
361
- binary: current.content === null
362
- ? entry.type === "file.binary"
363
- : Buffer.from(current.content).includes(0),
364
- });
365
- return { ...entry, type, payloadVersion: current.sha256 };
366
- });
219
+ await rm(scratch, { recursive: true, force: true });
220
+ }
367
221
  }
368
- export function observeRepository(repository, identity) {
369
- const { stateId, card } = captureState(repository, nestedRepositoryPaths(identity));
370
- return { stateId, card };
222
+ /** Content-addressed cache bytes are evidence, not ground. A cached head that
223
+ * cannot prove the row's declared state is a cache miss, never a Detect fault. */
224
+ export async function readPriorRepositoryTransport(cacheDir, row) {
225
+ if (row?.type !== "repo.git" || !row.payloadVersion || !row.transportVersion)
226
+ return null;
227
+ const file = join(cacheDir, `${row.transportVersion}.bin`);
228
+ try {
229
+ const layout = await inspectRepositoryTransportFile(file);
230
+ if (repositoryPayloadVersion(layout, sha256Hex) === row.payloadVersion) {
231
+ return { stateId: layout.stateId, transportVersion: row.transportVersion, card: layout.card, identityHash: layout.identityHash };
232
+ }
233
+ }
234
+ catch (error) {
235
+ if (error.code === "ENOENT")
236
+ return null;
237
+ // An unreadable envelope is the same miss.
238
+ }
239
+ await rm(file, { force: true });
240
+ return null;
241
+ }
242
+ export function pairRepositoryObservation(uuid, directory, observed, identityHash) {
243
+ const payloadVersion = repositoryPayloadVersion({ stateId: observed.state.stateId, identityHash }, sha256Hex);
244
+ return observationPair(uuid, directory, payloadVersion, observed.configuration, observed.view.sourceBinding, observed.proof);
245
+ }
246
+ /** The one pairing formula: a configuration stamp names the complete observed
247
+ * payload and the repository directory the Git binding resolved. */
248
+ function observationPair(uuid, directory, payloadVersion, configuration, binding, proof) {
249
+ // The binding's first identity is the checkout's exact `device:inode:mode`.
250
+ const [device, inode, mode] = binding.identities[0].split(":");
251
+ return Object.freeze({ uuid, absolutePath: directory, payloadVersion,
252
+ gitConfigurationObservation: configuration.stamp(uuid, payloadVersion, binding),
253
+ physicalKey: `${device}:${inode}`, filesystemMode: Number(mode), proof });
254
+ }
255
+ export function readRepository(repository, boundaries, lane, ownReveal = false) {
256
+ return retrySourceChange(new GitCapture(repository), attempt => readRepositoryAttempt(attempt, boundaries, lane, ownReveal));
257
+ }
258
+ const repositoryDirectoryStats = (path) => {
259
+ const stat = lstatSync(path, { bigint: true });
260
+ if (!stat.isDirectory() || stat.isSymbolicLink())
261
+ throw new CaptureSourceChangedError("repository directory changed kind");
262
+ return stat;
263
+ };
264
+ const repositoryDirectoryWitness = (path) => {
265
+ return sourceSignature(repositoryDirectoryStats(path));
266
+ };
267
+ /** Keep only witnesses; a cached Apply comparison must not retain patches,
268
+ * untracked contents, conflict contents or the construction view's closure. */
269
+ function repositoryNamespaceProof(source, witnesses) {
270
+ const ordered = [...witnesses].sort(([a], [b]) => a.length - b.length);
271
+ const check = (directory, witness) => {
272
+ if (repositoryDirectoryWitness(directory) !== witness) {
273
+ throw new CaptureSourceChangedError("repository namespace changed before reuse");
274
+ }
275
+ };
276
+ return sourceProofs([source, admissionProof(() => {
277
+ for (const [directory, witness] of ordered)
278
+ check(directory, witness);
279
+ for (let index = ordered.length - 1; index >= 0; index -= 1)
280
+ check(...ordered[index]);
281
+ })]);
282
+ }
283
+ async function readRepositoryAttempt(capture, boundaries, lane, ownReveal) {
284
+ const repository = capture.repository;
285
+ const { state, view, configuration, proof: source } = await collectState(capture, boundaries, lane, null, ownReveal);
286
+ const members = new Map();
287
+ const directoryWitnesses = new Map();
288
+ const walk = (directory, stats, prefix) => {
289
+ directoryWitnesses.set(directory, sourceSignature(stats));
290
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
291
+ if ([".git", ".amalgm"].includes(entry.name.toLowerCase()))
292
+ continue;
293
+ const path = prefix ? `${prefix}/${entry.name}` : entry.name;
294
+ const absolute = join(directory, entry.name);
295
+ if (boundaries.includes(path))
296
+ members.set(path, { kind: "boundary", directory: entry.isDirectory() });
297
+ else if (!entry.isDirectory())
298
+ members.set(path, { kind: "leaf" });
299
+ else if (hasGitMarker(absolute))
300
+ members.set(path, { kind: "repository" });
301
+ else {
302
+ const child = repositoryDirectoryStats(absolute);
303
+ members.set(path, { kind: "directory", stats: child });
304
+ walk(absolute, child, path);
305
+ }
306
+ }
307
+ };
308
+ walk(repository, repositoryDirectoryStats(repository), "");
309
+ const proof = repositoryNamespaceProof(source, directoryWitnesses);
310
+ await closeSource(proof);
311
+ return { repository, boundaries: [...boundaries], state, view, configuration, members, proof };
312
+ }
313
+ /** Judge one reading against one identity and the remainder it may keep.
314
+ * Remainder is accounted one member at a time, never by an excluded
315
+ * ancestor: it must keep its type and stay outside Git's index. */
316
+ export function judgeRepository(reading, identity, remainder) {
317
+ if (reading.boundaries.join("\0") !== nestedRepositoryPaths(identity).join("\0")) {
318
+ throw new Error("repository reading is judged only under the boundaries it read");
319
+ }
320
+ const { state, view, configuration, members, proof } = reading;
321
+ const expected = new Map(identity.map(entry => [entry.path, entry]));
322
+ const kept = new Map(remainder.map(entry => [entry.path, entry]));
323
+ const indexed = new Set(view.index.map(entry => entry.path));
324
+ const keptStats = new Map();
325
+ let identityMatches = true;
326
+ for (const [path, member] of members) {
327
+ if (member.kind === "boundary") {
328
+ if (!member.directory)
329
+ identityMatches = false;
330
+ continue;
331
+ }
332
+ const wanted = expected.get(path) ?? kept.get(path);
333
+ if (!wanted || (kept.has(path) && indexed.has(path)))
334
+ identityMatches = false;
335
+ if (member.kind === "repository")
336
+ identityMatches = false;
337
+ else if (member.kind === "directory") {
338
+ if (wanted?.type !== "folder")
339
+ identityMatches = false;
340
+ else if (kept.has(path))
341
+ keptStats.set(path, member.stats);
342
+ }
343
+ else if (!view.leaves.has(path))
344
+ identityMatches = false;
345
+ else if (kept.has(path)) {
346
+ const leaf = view.leaves.get(path);
347
+ if (leaf.type !== wanted.type)
348
+ identityMatches = false;
349
+ else
350
+ keptStats.set(path, leaf.stats);
351
+ }
352
+ }
353
+ if (identity.some(entry => entry.type !== "repo.git" && !members.has(entry.path)))
354
+ identityMatches = false;
355
+ const observedIdentity = literalRepositoryIdentity(identity, view);
356
+ const expectedState = remainder.length === 0 ? state : withoutUntracked(state, path => !expected.has(path));
357
+ const observedRemainder = literalRepositoryIdentity(remainder.filter(entry => keptStats.has(entry.path)), view);
358
+ const observedPayload = repositoryPayloadVersion({ stateId: state.stateId,
359
+ identityHash: repositoryIdentityHash([...observedIdentity, ...observedRemainder], sha256Hex) }, sha256Hex);
360
+ const binding = view.sourceBinding;
361
+ return { stateId: expectedState.stateId, identityHash: repositoryIdentityHash(observedIdentity, sha256Hex),
362
+ identityMatches, observedPayload,
363
+ remainder: observedRemainder.map(entry => ({ entry, stats: keptStats.get(entry.path) })),
364
+ proof,
365
+ pair: (uuid, directory) => observationPair(uuid, directory, observedPayload, configuration, binding, proof) };
371
366
  }
372
367
  /** One observed repository moment reproduces one immutable transport exactly
373
- * when its state, Card and identity map all agree with it. */
368
+ * when its complete Git state and identity map agree with it. */
374
369
  export function repositoryObservationMatches(observed, identity, prior) {
375
- return repositoryObservationMatchesState(observed, prior)
376
- && prior.identityHash === planRepositoryIdentity(identity, null, sha256Hex).identityHash;
377
- }
378
- /** Card + Checkpoint are the complete physical Git state. Callers that must
379
- * also prove the transport's entity frontier use repositoryObservationMatches. */
380
- export function repositoryObservationMatchesState(observed, prior) {
381
370
  return observed.stateId === prior.stateId
382
- && JSON.stringify(prior.card) === JSON.stringify(observed.card);
371
+ && observed.identityMatches
372
+ && observed.identityHash === prior.identityHash
373
+ && prior.identityHash === repositoryIdentityMap(identity, sha256Hex).identityHash;
374
+ }
375
+ const ordinaryMember = (entry) => ["file.text", "file.binary", "link"].includes(entry.type);
376
+ /** Compile the selected working files over the observed native Git history,
377
+ * index and continuation. Retained members keep their advertised content
378
+ * witnesses; immutable byte sources never become physical stat evidence.
379
+ * Every other selected member is read once, on a content thread, and must
380
+ * reproduce the type and payload its entry declares. */
381
+ export async function captureSelectedRepository(observed, identity, retained, lane, prior, previousIdentity) {
382
+ const repository = observed.capture.repository;
383
+ const selected = new Map(identity.map(entry => [entry.path, entry]));
384
+ if (selected.size !== identity.length)
385
+ throw new Error("selected repository contains duplicate paths");
386
+ const examined = new Set();
387
+ const indexed = new Set(observed.view.index.map(entry => entry.path));
388
+ const boundaries = nestedRepositoryPaths(identity);
389
+ const additionalPaths = identity.filter(entry => ordinaryMember(entry) && !indexed.has(entry.path)).map(entry => entry.path);
390
+ const reads = (await checkpointReads(observed.view.index, boundaries, additionalPaths)).filter(({ path }) => {
391
+ const entry = selected.get(path);
392
+ return entry !== undefined && ordinaryMember(entry) && !retained.has(path);
393
+ });
394
+ const current = await observeLiteralPaths(repository, observed.view.format, reads, new PhysicalPathWitnesses(), lane);
395
+ const view = await PrivateGitView.open(observed.view.sourceBinding, observed.state.card.shallow);
396
+ let constructed;
397
+ try {
398
+ constructed = await constructLiteralCheckpoint({
399
+ view, index: observed.view.index, commit: observed.state.card.head.commit, boundaries, additionalPaths,
400
+ read: path => {
401
+ const entry = selected.get(path);
402
+ if (!entry || !ordinaryMember(entry))
403
+ return null;
404
+ const observation = current.get(path);
405
+ const leaf = retained.get(path) ?? (observation?.kind === "leaf" ? observation.leaf : null);
406
+ if (!leaf)
407
+ throw new Error(`selected observed repository member is unavailable: ${path}`);
408
+ const payload = leafPayload(entry.payloadVersion, view.format, leaf);
409
+ if (entry.type !== leaf.type || entry.payloadVersion !== payload) {
410
+ throw new Error(`selected repository source does not reproduce its member: ${path}`);
411
+ }
412
+ examined.add(path);
413
+ return leaf;
414
+ },
415
+ });
416
+ if (identity.some(entry => ordinaryMember(entry) && !examined.has(entry.path))) {
417
+ throw new Error("selected ordinary member overlaps an opaque Git owner");
418
+ }
419
+ }
420
+ finally {
421
+ await view.close();
422
+ }
423
+ const checkpoint = { ...observed.state.checkpoint, ...constructed };
424
+ const checkpointId = deriveCheckpointId(checkpoint, sha256Hex);
425
+ const state = { ...observed.state, checkpoint, checkpointId,
426
+ stateId: deriveStateId(observed.state.cardId, checkpointId, sha256Hex) };
427
+ return sealRepository({ ...observed, state }, await finishStepsInTurns(repositoryIdentityMapSteps(identity)), prior, previousIdentity);
383
428
  }
384
- /** Capture one semantic repository state. A chainable capture packs only Git
385
- * objects absent from its parent and only changed identity-map paths. */
386
- export function captureRepository(repository, identity, prior, previousIdentity = null) {
387
- const state = captureState(repository, nestedRepositoryPaths(identity));
388
- const capturedIdentity = identityFromCheckpoint(identity, state);
389
- if (prior && repositoryObservationMatches(state, capturedIdentity, prior)) {
390
- return { ...prior, state, identity: capturedIdentity, bytes: null };
429
+ async function sealRepository(observed, identity, prior, previousIdentity) {
430
+ const { capture, state, view, configuration, proof } = observed;
431
+ if (prior && state.stateId === prior.stateId && identity.identityHash === prior.identityHash) {
432
+ return { ...prior, capture, state, view, configuration, identity, bytes: null, proof };
391
433
  }
392
434
  // A repository delta extends the immutable parent transport, never the
393
435
  // machine's mutable SQLite projection. Detect may already have recorded a
394
436
  // child move before the enclosing repository capture runs. If the proposed
395
437
  // base does not reproduce the parent's declared identity map, seal one
396
438
  // self-contained transport instead of emitting a lying delta.
397
- const chainable = prior !== null && previousIdentity !== null
398
- && repositoryIdentityHash(previousIdentity, sha256Hex) === prior.identityHash;
399
- const identityPlan = planRepositoryIdentity(capturedIdentity, chainable ? previousIdentity : null, sha256Hex);
439
+ const previous = prior !== null && previousIdentity?.identityHash === prior.identityHash ? previousIdentity : null;
400
440
  const captured = {
401
441
  ...state,
402
- pack: nativePack(repository, state, chainable ? prior.card : null),
442
+ pack: await nativePack(view.sourceBinding, state, previous ? prior.card : null),
403
443
  };
404
444
  const bytes = bufferView(encodeRepositoryTransport(captured, {
405
- parentTransportVersion: chainable ? prior.transportVersion : null,
406
- ...identityPlan,
445
+ parentTransportVersion: previous ? prior.transportVersion : null,
446
+ identityHash: identity.identityHash,
447
+ identityDelta: repositoryIdentityDelta(identity, previous?.entries ?? null),
407
448
  }));
408
449
  return {
450
+ capture,
409
451
  state: captured,
410
- identity: capturedIdentity,
452
+ view,
453
+ configuration,
454
+ identity,
411
455
  stateId: captured.stateId,
412
456
  transportVersion: sha256Hex(bytes),
413
457
  card: captured.card,
414
- identityHash: identityPlan.identityHash,
458
+ identityHash: identity.identityHash,
415
459
  bytes,
460
+ proof,
416
461
  };
417
462
  }
418
463
  // Git closes immutable objects after making them read-only. Its own writer
@@ -422,15 +467,16 @@ export function captureRepository(repository, identity, prior, previousIdentity
422
467
  // user configuration. The final staging barrier still owns every other file.
423
468
  const DURABLE_GIT_ARGS = ["-c", "core.fsync=all", "-c", "core.fsyncMethod=fsync"];
424
469
  function runDurableGit(repository, args, input) {
425
- return runGit(repository, [...DURABLE_GIT_ARGS, ...args], input);
470
+ return runGit(repository, [...DURABLE_GIT_ARGS, ...args], { input, purpose: "apply" });
426
471
  }
427
472
  /** Only these immutable object components are flushed by Git's writer
428
473
  * contract. Other files, including pack retention markers, remain the final
429
474
  * barrier's responsibility. The object directory comes from its owner, Git.
430
475
  * Scan only fresh materialization; incremental Apply never walks old packs. */
431
- function gitObjectFiles(repository) {
432
- const objects = resolve(repository, gitText(repository, ["rev-parse", "--git-path", "objects"]));
433
- const hashLength = gitText(repository, ["rev-parse", "--show-object-format"]) === "sha256" ? 64 : 40;
476
+ async function gitObjectFiles(repository) {
477
+ const binding = { purpose: "binding" };
478
+ const objects = resolve(repository, await gitText(repository, ["rev-parse", "--git-path", "objects"], binding));
479
+ const hashLength = await gitText(repository, ["rev-parse", "--show-object-format"], binding) === "sha256" ? 64 : 40;
434
480
  const loose = new RegExp(`^[0-9a-f]{${hashLength - 2}}$`);
435
481
  const packed = new RegExp(`^pack-[0-9a-f]{${hashLength}}\\.(pack|idx|rev)$`);
436
482
  const files = [];
@@ -445,105 +491,77 @@ function gitObjectFiles(repository) {
445
491
  }
446
492
  return files;
447
493
  }
448
- function newlyDurableGitObjects(repository, priorFiles) {
449
- return priorFiles === null ? [] : gitObjectFiles(repository)
494
+ async function newlyDurableGitObjects(repository, priorFiles) {
495
+ return priorFiles === null ? [] : (await gitObjectFiles(repository))
450
496
  .filter((file) => !priorFiles.has(file)).map(recordFileDurability);
451
497
  }
452
- function installShallowBoundary(repository, commits) {
453
- if (commits.length === 0)
498
+ async function installShallowBoundary(repository, commits) {
499
+ const file = resolve(repository, await gitText(repository, ["rev-parse", "--git-path", "shallow"], { purpose: "binding" }));
500
+ if (commits.length === 0) {
501
+ rmSync(file, { force: true });
454
502
  return;
455
- const file = resolve(repository, gitText(repository, ["rev-parse", "--git-path", "shallow"]));
503
+ }
456
504
  mkdirSync(dirname(file), { recursive: true });
457
- writeFileSync(file, `${[...commits].sort().join("\n")}\n`);
458
- }
459
- /** One ref transaction: every Card ref at its commit, every other ref gone.
460
- * The Card's ref set was validated once when the transport was decoded, and
461
- * `update-ref` itself refuses malformed names and missing objects. */
462
- function forceRefs(repository, state) {
463
- const keep = new Set(state.card.refs.map((ref) => ref.name));
464
- const existing = tryGitText(repository, [
465
- "for-each-ref", "--format=%(refname)", "refs",
466
- ]);
505
+ // Renamed over as a new inode, never written in place.
506
+ atomicWrite(file, `${[...commits].sort().join("\n")}\n`, 0o644);
507
+ }
508
+ /** Card refs replace the stored ref set in two steps, because Git 2.39's
509
+ * `update-ref --stdin` has no symbolic verb. One transaction writes every
510
+ * direct value and deletes every stored ref the Card lacks. Card names are
511
+ * literal, so no update or delete may follow a symbolic ref (a clone's
512
+ * `refs/remotes/origin/HEAD` would otherwise write `origin/main` twice), and
513
+ * only the command-line flag covers every command; `option no-deref` covers
514
+ * one. Each differing symbolic relationship is then set by name. The stored
515
+ * set includes the loose store, since for-each-ref omits a dangling or cyclic
516
+ * symbolic ref that must still go. Rerunning both steps is idempotent. */
517
+ async function forceRefs(repository, state) {
518
+ const wanted = new Set(state.card.refs.map((ref) => ref.name));
519
+ const listed = await gitText(repository, ["for-each-ref", "--format=%(refname)", "refs"], { purpose: "apply" });
520
+ const stored = looseRefs(repository);
467
521
  const commands = [
468
- ...state.card.refs.map((ref) => `update ${ref.name} ${ref.commit}`),
469
- ...(existing ?? "").split("\n")
470
- .filter((ref) => ref && ref !== "refs/stash" && !keep.has(ref))
471
- .map((ref) => `delete ${ref}`),
522
+ ...state.card.refs.flatMap((ref) => ref.kind === "direct" ? [`update ${ref.name} ${ref.commit}`] : []),
523
+ ...[...new Set([...(listed ? listed.split("\n") : []), ...stored.keys()])]
524
+ .filter((name) => name !== "refs/stash" && !wanted.has(name))
525
+ .map((name) => `delete ${name}`),
472
526
  "",
473
527
  ];
474
- runDurableGit(repository, ["update-ref", "--stdin"], Buffer.from(commands.join("\n"), "utf8"));
528
+ await runDurableGit(repository, ["update-ref", "--no-deref", "--stdin"], Buffer.from(commands.join("\n"), "utf8"));
529
+ for (const ref of state.card.refs) {
530
+ if (ref.kind !== "symbolic")
531
+ continue;
532
+ const current = stored.get(ref.name)?.();
533
+ if (current?.kind === "symbolic" && current.target === ref.target)
534
+ continue;
535
+ await runDurableGit(repository, ["symbolic-ref", ref.name, ref.target]);
536
+ }
475
537
  }
476
538
  /** Git keeps the stash stack only in the `refs/stash` reflog, so a differing
477
539
  * stack is rebuilt by value: drop the ref and its log, then store every entry
478
540
  * oldest first. An equal stack is left untouched. */
479
- function forceStash(repository, state) {
541
+ async function forceStash(repository, state) {
480
542
  const wanted = state.card.stash;
481
- const current = stashList(repository);
543
+ const current = await stashList(repository);
482
544
  if (current.length === wanted.length && current.every((entry, index) => entry.commit === wanted[index].commit && entry.message === wanted[index].message)) {
483
545
  return;
484
546
  }
485
- runDurableGit(repository, ["update-ref", "-d", "refs/stash"]);
547
+ await runDurableGit(repository, ["update-ref", "-d", "refs/stash"]);
486
548
  for (let index = wanted.length - 1; index >= 0; index -= 1) {
487
549
  const entry = wanted[index];
488
- runDurableGit(repository, ["stash", "store", `--message=${entry.message}`, entry.commit]);
550
+ await runDurableGit(repository, ["stash", "store", `--message=${entry.message}`, entry.commit]);
489
551
  }
490
552
  }
491
- function forceHead(repository, state) {
553
+ async function forceHead(repository, state) {
492
554
  const { head } = state.card;
493
- if (head.commit === null) {
494
- if (!head.branch)
495
- throw new Error("unborn repository state has no branch");
496
- runDurableGit(repository, ["symbolic-ref", "HEAD", `refs/heads/${head.branch}`]);
497
- runDurableGit(repository, ["read-tree", "--empty"]);
498
- runDurableGit(repository, ["clean", "-fdq"]);
499
- return;
500
- }
501
- if (head.branch === null) {
502
- runDurableGit(repository, ["-c", "advice.detachedHead=false", "checkout", "--force", "--quiet", "--detach", head.commit]);
555
+ if (head.branch !== null) {
556
+ await runDurableGit(repository, ["symbolic-ref", "HEAD", `refs/heads/${head.branch}`]);
503
557
  }
504
- else {
505
- runDurableGit(repository, ["symbolic-ref", "HEAD", `refs/heads/${head.branch}`]);
506
- runDurableGit(repository, ["reset", "--hard", "--quiet"]);
507
- }
508
- runDurableGit(repository, ["clean", "-fdq"]);
509
- }
510
- function writeCheckpointLeaf(repository, path, leaf) {
511
- const absolutePath = join(repository, path);
512
- if (leaf === null) {
513
- rmSync(absolutePath, { recursive: true, force: true });
514
- return;
515
- }
516
- if (leaf.content === null)
517
- throw new Error(`repository transport omitted conflict content for ${path}`);
518
- mkdirSync(dirname(absolutePath), { recursive: true });
519
- if (leaf.mode === "120000") {
520
- rmSync(absolutePath, { force: true });
521
- symlinkSync(Buffer.from(leaf.content).toString("utf8"), absolutePath);
522
- return;
558
+ else if (head.commit !== null) {
559
+ await runDurableGit(repository, ["update-ref", "--no-deref", "HEAD", head.commit]);
523
560
  }
524
- writeFileSync(absolutePath, leaf.content);
525
- chmodSync(absolutePath, leaf.mode === "100755" ? 0o755 : 0o644);
561
+ else
562
+ throw new Error("unborn repository state has no branch");
526
563
  }
527
- function installUnmergedIndex(repository, entries) {
528
- if (entries.length === 0)
529
- return;
530
- const objectFormat = gitText(repository, ["rev-parse", "--show-object-format"]);
531
- const zero = "0".repeat(objectFormat === "sha256" ? 64 : 40);
532
- const records = entries.flatMap((entry) => [
533
- `0 ${zero}\t${entry.path}`,
534
- ...entry.stages.map((stage) => `${stage.mode} ${stage.objectId} ${stage.stage}\t${entry.path}`),
535
- ]);
536
- runDurableGit(repository, ["update-index", "-z", "--index-info"], Buffer.from(`${records.join("\0")}\0`, "utf8"));
537
- for (const entry of entries) {
538
- // A conflict made only of gitlink stages belongs to the nested repository
539
- // rail. The parent owns the opaque index entries, but never creates,
540
- // replaces, or removes that nested worktree.
541
- if (entry.stages.every((stage) => stage.mode === "160000"))
542
- continue;
543
- writeCheckpointLeaf(repository, entry.path, entry.worktree);
544
- }
545
- }
546
- function installMergeOperation(repository, operation) {
564
+ async function installMergeOperation(repository, operation) {
547
565
  const directory = gitDirectory(repository);
548
566
  for (const file of ["MERGE_HEAD", "MERGE_MSG", "MERGE_MODE"]) {
549
567
  rmSync(join(directory, file), { force: true });
@@ -554,120 +572,34 @@ function installMergeOperation(repository, operation) {
554
572
  writeFileSync(join(directory, "MERGE_MSG"), operation.message);
555
573
  writeFileSync(join(directory, "MERGE_MODE"), operation.mode);
556
574
  }
557
- function applyCheckpoint(repository, state) {
558
- if (state.checkpoint.stagedPatch.length > 0) {
559
- runDurableGit(repository, ["apply", "--index", "--binary", "--whitespace=nowarn"], state.checkpoint.stagedPatch);
560
- }
561
- if (state.checkpoint.unstagedPatch.length > 0) {
562
- runDurableGit(repository, ["apply", "--binary", "--whitespace=nowarn"], state.checkpoint.unstagedPatch);
563
- }
564
- installUnmergedIndex(repository, state.checkpoint.unmerged);
565
- installMergeOperation(repository, state.checkpoint.operation);
566
- for (const file of state.checkpoint.untracked) {
567
- if (file.content === null)
568
- throw new Error(`repository transport omitted untracked content for ${file.path}`);
569
- const absolutePath = join(repository, file.path);
570
- mkdirSync(dirname(absolutePath), { recursive: true });
571
- if (file.mode === "120000") {
572
- rmSync(absolutePath, { force: true });
573
- symlinkSync(Buffer.from(file.content).toString("utf8"), absolutePath);
574
- }
575
- else {
576
- writeFileSync(absolutePath, file.content);
577
- chmodSync(absolutePath, file.mode === "100755" ? 0o755 : 0o644);
578
- }
579
- }
580
- }
581
- function importPack(repository, state) {
582
- if (!state.pack)
583
- return;
584
- if (sha256Hex(state.pack.bytes) !== state.pack.sha256) {
585
- throw new Error("repository Git pack checksum mismatch");
586
- }
587
- runDurableGit(repository, [
588
- "index-pack", "--stdin", "--fix-thin", "--keep=amalgm repository transport",
589
- ], state.pack.bytes);
590
- }
591
- function initializeMaterializedRepository(repository) {
575
+ async function initializeMaterializedRepository(repository, format) {
592
576
  // Older Git accepts unknown -c keys without implementing their contract.
593
577
  // The writer acknowledgement requires core.fsync/core.fsyncMethod, which
594
578
  // became available in Git 2.36; admit that capability before any Git writes.
595
- const version = gitText(repository, ["--version"]);
579
+ const { version } = gitHost();
596
580
  const parsed = /^git version (\d+)\.(\d+)\.(\d+)(?:[.\s-]|$)/.exec(version);
597
581
  if (!parsed || Number(parsed[1]) < 2 || (Number(parsed[1]) === 2 && Number(parsed[2]) < 36)) {
598
582
  throw new Error(`Git 2.36 or newer is required for durable repository materialization; found ${version}`);
599
583
  }
600
- runDurableGit(repository, ["init", "--quiet"]);
601
- // A Card is Git object truth. The receiver's global Windows preference
602
- // cannot rewrite those bytes during materialization.
603
- runDurableGit(repository, ["config", "core.autocrlf", "false"]);
584
+ if (hasGitMarker(repository) && await gitText(repository, ["rev-parse", "--show-object-format"], { purpose: "binding" }) !== format) {
585
+ throw new Error("repository object format differs from accepted state");
586
+ }
587
+ await runDurableGit(repository, ["init", "--quiet", `--object-format=${format}`, "--template="]);
604
588
  }
605
- function forceRemotes(repository, remotes) {
606
- const existing = (tryGitText(repository, ["remote"]) ?? "").split("\n").filter(Boolean);
589
+ async function forceRemotes(repository, remotes) {
590
+ const existing = (await gitText(repository, ["remote"], { purpose: "apply" })).split("\n").filter(Boolean);
607
591
  const wanted = new Map(remotes.map((remote) => [remote.name, remote.url]));
608
592
  for (const name of existing) {
609
593
  if (!wanted.has(name))
610
- runDurableGit(repository, ["remote", "remove", name]);
594
+ await runDurableGit(repository, ["remote", "remove", name]);
611
595
  }
612
596
  for (const [name, url] of wanted) {
613
597
  if (existing.includes(name))
614
- runDurableGit(repository, ["remote", "set-url", name, url]);
598
+ await runDurableGit(repository, ["remote", "set-url", name, url]);
615
599
  else
616
- runDurableGit(repository, ["remote", "add", name, url]);
600
+ await runDurableGit(repository, ["remote", "add", name, url]);
617
601
  }
618
602
  }
619
- /** Materialize a complete transport chain and then re-derive its semantic
620
- * state. The oldest node must be self-contained; every later node must name
621
- * the exact predecessor whose Git objects and identity map it extends. */
622
- export function applyRepository(repository, chain) {
623
- if (chain.length === 0)
624
- throw new Error("repository transport chain is empty");
625
- const transports = [];
626
- let priorVersion = null;
627
- let identity = null;
628
- for (const bytes of chain) {
629
- const state = decodeRepositoryTransport(bytes);
630
- if (state.parentTransportVersion !== priorVersion) {
631
- throw new Error("repository transport chain is discontinuous");
632
- }
633
- identity = applyRepositoryIdentity(identity, state, sha256Hex);
634
- transports.push(state);
635
- priorVersion = sha256Hex(bytes);
636
- }
637
- const state = transports.at(-1);
638
- mkdirSync(repository, { recursive: true });
639
- const fresh = !hasGitMarker(repository);
640
- initializeMaterializedRepository(repository);
641
- // An init template may carry pre-existing objects. Git has not necessarily
642
- // written them, so their presence cannot become a durability acknowledgement.
643
- const priorFiles = fresh ? new Set(gitObjectFiles(repository)) : null;
644
- markRepositoryReveal(repository);
645
- forceRemotes(repository, state.card.remotes);
646
- for (const transport of transports)
647
- importPack(repository, transport);
648
- installShallowBoundary(repository, state.card.shallow ?? []);
649
- forceRefs(repository, state);
650
- forceStash(repository, state);
651
- forceHead(repository, state);
652
- applyCheckpoint(repository, state);
653
- const proof = captureState(repository, nestedRepositoryPaths(identity), true);
654
- if (proof.cardId !== state.cardId || proof.checkpointId !== state.checkpointId) {
655
- throw new Error("materialized repository does not reproduce its declared state");
656
- }
657
- if (JSON.stringify(proof.card.shallow ?? []) !== JSON.stringify(state.card.shallow ?? [])) {
658
- throw new Error("materialized repository does not reproduce its shallow boundary");
659
- }
660
- rmSync(join(gitDirectory(repository), REVEAL_MARKER), { force: true });
661
- return {
662
- stateId: state.stateId,
663
- cardId: state.cardId,
664
- checkpointId: state.checkpointId,
665
- card: state.card,
666
- transportVersion: priorVersion,
667
- identity: identity,
668
- durableFiles: newlyDurableGitObjects(repository, priorFiles),
669
- };
670
- }
671
603
  function readExact(file, offset, bytes) {
672
604
  const result = Buffer.allocUnsafe(bytes);
673
605
  const descriptor = openSync(file, "r");
@@ -685,40 +617,75 @@ function readExact(file, offset, bytes) {
685
617
  closeSync(descriptor);
686
618
  }
687
619
  }
688
- export function inspectRepositoryTransportFile(file) {
689
- const totalBytes = statSync(file).size;
690
- const prefix = readExact(file, 0, 12);
691
- const metadataBytes = repositoryTransportMetadataBytes(prefix);
692
- return inspectRepositoryTransport(readExact(file, 0, metadataBytes), totalBytes);
620
+ /** A transport's layout from its header alone; the pack and Checkpoint
621
+ * bytes it locates are never read. */
622
+ export async function inspectRepositoryTransportFile(file) {
623
+ const handle = await open(file, "r");
624
+ try {
625
+ const { size } = await handle.stat();
626
+ const head = async (bytes) => {
627
+ const result = Buffer.allocUnsafe(bytes);
628
+ for (let cursor = 0; cursor < bytes;) {
629
+ const { bytesRead } = await handle.read(result, cursor, bytes - cursor, cursor);
630
+ if (bytesRead === 0)
631
+ throw new Error("repository transport file is truncated");
632
+ cursor += bytesRead;
633
+ }
634
+ return result;
635
+ };
636
+ return inspectRepositoryTransport(await head(repositoryTransportMetadataBytes(await head(12))), size);
637
+ }
638
+ finally {
639
+ await handle.close();
640
+ }
693
641
  }
694
642
  const REPOSITORY_IDENTITY_CACHE_CONTRACT = "amalgm.repository-identity-cache.v1";
695
643
  const repositoryIdentityCacheFile = (cacheDir, transportVersion) => join(cacheDir, "repository-identities", `${transportVersion}.json`);
644
+ /** Identity frontiers this process has proven, by immutable transport. A
645
+ * transport names exactly one map, so a proven frontier never goes stale; the
646
+ * bound, in entries, only limits memory. The newest frontier always stays. */
647
+ const provenFrontiers = new Map();
648
+ const PROVEN_FRONTIER_ENTRIES = 100_000;
649
+ let provenFrontierEntries = 0;
650
+ function rememberFrontier(transportVersion, identity) {
651
+ const known = provenFrontiers.get(transportVersion);
652
+ if (known)
653
+ provenFrontierEntries -= known.entries.length;
654
+ provenFrontiers.delete(transportVersion);
655
+ provenFrontiers.set(transportVersion, identity);
656
+ provenFrontierEntries += identity.entries.length;
657
+ for (const [version, frontier] of provenFrontiers) {
658
+ if (provenFrontierEntries <= PROVEN_FRONTIER_ENTRIES || version === transportVersion)
659
+ break;
660
+ provenFrontiers.delete(version);
661
+ provenFrontierEntries -= frontier.entries.length;
662
+ }
663
+ }
696
664
  /** Seal the compact full identity frontier beside the current transport. The
697
665
  * evidence is disposable, but its content is accepted only when its hash
698
666
  * equals the immutable transport header that names it. */
699
- export function sealCachedRepositoryIdentity(cacheDir, transportVersion, identity, identityHash) {
700
- const planned = planRepositoryIdentity(identity, null, sha256Hex);
701
- if (planned.identityHash !== identityHash) {
667
+ export async function sealCachedRepositoryIdentity(cacheDir, transportVersion, identity, identityHash) {
668
+ if (identity.identityHash !== identityHash) {
702
669
  throw new Error("repository identity evidence does not match its transport");
703
670
  }
704
- const file = repositoryIdentityCacheFile(cacheDir, transportVersion);
705
- atomicWrite(file, JSON.stringify({
671
+ rememberFrontier(transportVersion, identity);
672
+ await writeCacheHint(cacheDir, repositoryIdentityCacheFile(cacheDir, transportVersion), Buffer.from(JSON.stringify({
706
673
  contract: REPOSITORY_IDENTITY_CACHE_CONTRACT,
707
674
  transportVersion,
708
675
  identityHash,
709
- identity: planned.identityDelta.upsert,
710
- }));
676
+ identity: identity.entries,
677
+ }), "utf8"));
711
678
  }
712
- function readSealedRepositoryIdentity(cacheDir, transportVersion, identityHash) {
679
+ async function readSealedRepositoryIdentity(cacheDir, transportVersion, identityHash) {
713
680
  try {
714
- const value = JSON.parse(readFileSync(repositoryIdentityCacheFile(cacheDir, transportVersion), "utf8"));
681
+ const value = JSON.parse(await readFile(repositoryIdentityCacheFile(cacheDir, transportVersion), "utf8"));
715
682
  if (value.contract !== REPOSITORY_IDENTITY_CACHE_CONTRACT
716
683
  || value.transportVersion !== transportVersion
717
684
  || value.identityHash !== identityHash
718
685
  || !Array.isArray(value.identity))
719
686
  return null;
720
- const planned = planRepositoryIdentity(value.identity, null, sha256Hex);
721
- return planned.identityHash === identityHash ? planned.identityDelta.upsert : null;
687
+ const identity = await finishStepsInTurns(repositoryIdentityMapSteps(value.identity));
688
+ return identity.identityHash === identityHash ? identity : null;
722
689
  }
723
690
  catch {
724
691
  return null;
@@ -728,12 +695,17 @@ function readSealedRepositoryIdentity(cacheDir, transportVersion, identityHash)
728
695
  * repository transport. Compact sealed evidence survives ancestor-pack
729
696
  * pruning; without it, a complete cached chain is replayed once and seals the
730
697
  * frontier. No mutable entity row can substitute for either proof. */
731
- export function readCachedRepositoryIdentity(cacheDir, transportVersion) {
698
+ export async function readCachedRepositoryIdentity(cacheDir, transportVersion) {
699
+ const proven = provenFrontiers.get(transportVersion);
700
+ if (proven)
701
+ return proven;
732
702
  try {
733
- const current = inspectRepositoryTransportFile(join(cacheDir, `${transportVersion}.bin`));
734
- const sealed = readSealedRepositoryIdentity(cacheDir, transportVersion, current.identityHash);
735
- if (sealed)
703
+ const current = await inspectRepositoryTransportFile(join(cacheDir, `${transportVersion}.bin`));
704
+ const sealed = await readSealedRepositoryIdentity(cacheDir, transportVersion, current.identityHash);
705
+ if (sealed) {
706
+ rememberFrontier(transportVersion, sealed);
736
707
  return sealed;
708
+ }
737
709
  const reversed = [];
738
710
  const versions = new Set();
739
711
  let version = transportVersion;
@@ -741,10 +713,7 @@ export function readCachedRepositoryIdentity(cacheDir, transportVersion) {
741
713
  if (versions.has(version))
742
714
  return null;
743
715
  versions.add(version);
744
- const file = join(cacheDir, `${version}.bin`);
745
- if (!existsSync(file))
746
- return null;
747
- const layout = inspectRepositoryTransportFile(file);
716
+ const layout = await inspectRepositoryTransportFile(join(cacheDir, `${version}.bin`));
748
717
  reversed.push({ version, layout });
749
718
  version = layout.parentTransportVersion;
750
719
  }
@@ -756,61 +725,42 @@ export function readCachedRepositoryIdentity(cacheDir, transportVersion) {
756
725
  identity = applyRepositoryIdentity(identity, node.layout, sha256Hex);
757
726
  parent = node.version;
758
727
  }
759
- const result = identity ?? [];
760
- sealCachedRepositoryIdentity(cacheDir, transportVersion, result, current.identityHash);
728
+ // Each applied node proved its declared map, the last one this header's.
729
+ const result = { entries: identity ?? [], identityHash: current.identityHash };
730
+ await sealCachedRepositoryIdentity(cacheDir, transportVersion, result, current.identityHash);
761
731
  return result;
762
732
  }
763
733
  catch {
764
734
  return null;
765
735
  }
766
736
  }
737
+ async function* hashed(source, hash) {
738
+ for await (const chunk of source) {
739
+ hash.update(chunk);
740
+ yield chunk;
741
+ }
742
+ }
767
743
  async function importPackFile(repository, file, pack) {
768
744
  const hash = createHash("sha256");
769
- const witness = new Transform({
770
- transform(chunk, _encoding, callback) {
771
- hash.update(chunk);
772
- callback(null, chunk);
773
- },
774
- });
775
- const child = spawn("git", [
776
- ...DURABLE_GIT_ARGS,
777
- "index-pack", "--stdin", "--fix-thin", "--keep=amalgm repository transport",
778
- ], {
779
- cwd: repository,
780
- stdio: ["pipe", "ignore", "pipe"],
781
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_TEST_FSYNC: "1" },
782
- });
783
- const stderr = [];
784
- child.stderr.on("data", (chunk) => stderr.push(chunk));
785
- const completed = new Promise((resolveChild, rejectChild) => {
786
- child.once("error", rejectChild);
787
- child.once("close", (code) => {
788
- if (code === 0)
789
- resolveChild();
790
- else
791
- rejectChild(new Error(`git index-pack failed${stderr.length
792
- ? `: ${Buffer.concat(stderr).toString("utf8").trim()}` : ""}`));
793
- });
794
- });
795
745
  const source = createReadStream(file, {
796
746
  start: pack.range.offset,
797
747
  end: pack.range.offset + pack.range.bytes - 1,
798
748
  });
799
- await Promise.all([pipeline(source, witness, child.stdin), completed]);
749
+ await runDurableGit(repository, ["index-pack", "--stdin", "--fix-thin", "--keep=amalgm repository transport"], hashed(source, hash));
800
750
  if (hash.digest("hex") !== pack.sha256)
801
751
  throw new Error("repository Git pack checksum mismatch");
802
752
  }
803
- /** Every transport's tips are present once its pack has landed, and each pack
804
- * closes over its parent's tips. Walking the chain from the newest node, the
805
- * first one whose tips the repository already holds ends what must be
753
+ /** A transport's pack delivers every object its state wants, and each pack
754
+ * closes over its parent's wants. Walking the chain from the newest node,
755
+ * the first whose wants the repository already holds ends what must be
806
756
  * imported: only the ranges after it are absent. */
807
- function firstAbsentTransport(repository, transports) {
757
+ async function firstAbsentTransport(repository, transports) {
808
758
  for (let index = transports.length - 1; index >= 0; index -= 1) {
809
- const tips = cardTips(transports[index].layout.card);
810
- if (tips.length === 0)
759
+ const wants = stateWants(transports[index].layout);
760
+ if (wants.length === 0)
811
761
  return index + 1;
812
- const report = runGit(repository, ["cat-file", "--batch-check=%(objectname) %(objecttype)"], Buffer.from(`${tips.join("\n")}\n`, "utf8")).toString("utf8");
813
- if (!report.split("\n").some((line) => line.endsWith(" missing")))
762
+ const report = await runGit(repository, ["cat-file", "--batch-check=%(objectname) %(objecttype)"], { input: Buffer.from(`${wants.join("\n")}\n`, "utf8"), purpose: "apply" });
763
+ if (!report.toString("utf8").split("\n").some((line) => line.endsWith(" missing")))
814
764
  return index + 1;
815
765
  }
816
766
  return 0;
@@ -818,72 +768,69 @@ function firstAbsentTransport(repository, transports) {
818
768
  function readRange(file, range) {
819
769
  return range.bytes === 0 ? Buffer.alloc(0) : readExact(file, range.offset, range.bytes);
820
770
  }
821
- function applyCheckpointFile(repository, file, checkpoint) {
822
- const staged = readRange(file, checkpoint.stagedPatch);
823
- if (staged.length > 0) {
824
- runDurableGit(repository, ["apply", "--index", "--binary", "--whitespace=nowarn"], staged);
825
- }
826
- const unstaged = readRange(file, checkpoint.unstagedPatch);
827
- if (unstaged.length > 0) {
828
- runDurableGit(repository, ["apply", "--binary", "--whitespace=nowarn"], unstaged);
829
- }
830
- const unmerged = checkpoint.unmerged.map((entry) => {
831
- if (entry.worktree?.content === null) {
832
- throw new Error(`repository transport omitted conflict content for ${entry.path}`);
833
- }
834
- const content = entry.worktree === null
835
- ? null
836
- : readRange(file, entry.worktree.content);
837
- if (content !== null && sha256Hex(content) !== entry.worktree?.sha256) {
838
- throw new Error(`repository conflict content checksum mismatch for ${entry.path}`);
839
- }
840
- return {
841
- path: entry.path,
842
- stages: entry.stages,
843
- worktree: entry.worktree === null ? null : {
844
- mode: entry.worktree.mode,
845
- sha256: entry.worktree.sha256,
846
- content,
771
+ /** A lazy byte view lets the portable validator govern file-backed replay
772
+ * without buffering the Git pack or all Checkpoint contents at once. Packs
773
+ * are hash-verified separately by the streaming importer before state writes. */
774
+ function repositoryTransportFromFile(file, layout) {
775
+ return {
776
+ ...layout, pack: null,
777
+ checkpoint: {
778
+ ...layout.checkpoint,
779
+ stagedPatch: readRange(file, layout.checkpoint.stagedPatch),
780
+ unstagedPatch: readRange(file, layout.checkpoint.unstagedPatch),
781
+ untracked: layout.checkpoint.untracked.map(entry => ({ ...entry,
782
+ get content() { return entry.content === null ? null : readRange(file, entry.content); },
783
+ })),
784
+ unmerged: layout.checkpoint.unmerged.map(entry => ({ ...entry,
785
+ worktree: entry.worktree === null ? null : { ...entry.worktree,
786
+ get content() { return entry.worktree.content === null ? null : readRange(file, entry.worktree.content); },
787
+ },
788
+ })),
789
+ operation: layout.checkpoint.operation === null ? null : {
790
+ ...layout.checkpoint.operation,
791
+ message: readRange(file, layout.checkpoint.operation.message),
792
+ mode: readRange(file, layout.checkpoint.operation.mode),
847
793
  },
848
- };
849
- });
850
- installUnmergedIndex(repository, unmerged);
851
- installMergeOperation(repository, checkpoint.operation === null ? null : {
852
- kind: checkpoint.operation.kind,
853
- heads: checkpoint.operation.heads,
854
- message: readRange(file, checkpoint.operation.message),
855
- mode: readRange(file, checkpoint.operation.mode),
794
+ },
795
+ };
796
+ }
797
+ function checkpointFileSource(file, checkpoint) {
798
+ const content = (range) => {
799
+ if (range === null)
800
+ throw new Error("repository transport omitted checkpoint content");
801
+ return readRange(file, range);
802
+ };
803
+ return {
804
+ baseCommit: checkpoint.baseCommit,
805
+ stagedPatch: readRange(file, checkpoint.stagedPatch),
806
+ unstagedPatch: readRange(file, checkpoint.unstagedPatch),
807
+ untracked: checkpoint.untracked.map(entry => ({ ...entry, read: () => content(entry.content) })),
808
+ unmerged: checkpoint.unmerged.map(entry => ({ ...entry,
809
+ worktree: entry.worktree === null ? null : { ...entry.worktree, path: entry.path,
810
+ read: () => content(entry.worktree.content) },
811
+ })),
812
+ };
813
+ }
814
+ async function applyCheckpointFile(repository, file, checkpoint, identity, ground, installMetadata) {
815
+ await installLiteralCheckpoint(repository, checkpointFileSource(file, checkpoint), identity, ground, installMetadata);
816
+ await installMergeOperation(repository, checkpoint.operation === null ? null : {
817
+ kind: checkpoint.operation.kind, heads: checkpoint.operation.heads,
818
+ message: readRange(file, checkpoint.operation.message), mode: readRange(file, checkpoint.operation.mode),
856
819
  });
857
- for (const entry of checkpoint.untracked) {
858
- if (entry.content === null) {
859
- throw new Error(`repository transport omitted untracked content for ${entry.path}`);
860
- }
861
- const content = readRange(file, entry.content);
862
- if (sha256Hex(content) !== entry.sha256) {
863
- throw new Error(`repository untracked content checksum mismatch for ${entry.path}`);
864
- }
865
- const absolutePath = join(repository, entry.path);
866
- mkdirSync(dirname(absolutePath), { recursive: true });
867
- if (entry.mode === "120000") {
868
- rmSync(absolutePath, { force: true });
869
- symlinkSync(content.toString("utf8"), absolutePath);
870
- }
871
- else {
872
- writeFileSync(absolutePath, content);
873
- chmodSync(absolutePath, entry.mode === "100755" ? 0o755 : 0o644);
874
- }
875
- }
876
820
  }
877
- /** Apply sealed repository artifacts by streaming each Git range directly
878
- * into index-pack. Download already verified each complete transport hash. */
879
- export async function applyRepositoryFiles(repository, chain) {
821
+ /** Shared immutable chain validation for retained contents and installation. */
822
+ async function readRepositoryChain(chain) {
880
823
  if (chain.length === 0)
881
824
  throw new Error("repository transport chain is empty");
882
825
  const transports = [];
883
826
  let priorVersion = null;
884
827
  let identity = null;
885
828
  for (const sealed of chain) {
886
- const layout = inspectRepositoryTransportFile(sealed.file);
829
+ if (await hashContentFile(sealed.file) !== sealed.transportVersion) {
830
+ throw new Error("repository transport failed its immutable hash");
831
+ }
832
+ const layout = await inspectRepositoryTransportFile(sealed.file);
833
+ assertRepositoryTransportTruth(repositoryTransportFromFile(sealed.file, layout), sha256Hex);
887
834
  if (layout.parentTransportVersion !== priorVersion) {
888
835
  throw new Error("repository transport chain is discontinuous");
889
836
  }
@@ -892,40 +839,76 @@ export async function applyRepositoryFiles(repository, chain) {
892
839
  priorVersion = sealed.transportVersion;
893
840
  }
894
841
  const latest = transports.at(-1);
842
+ return { transports, latest, identity: identity, transportVersion: priorVersion };
843
+ }
844
+ /** Recover retained working bytes from their immutable transport chain. This
845
+ * owns private Git objects only; it never checks out files or touches the live
846
+ * repository, its index, refs, or metadata. The caller must close the result. */
847
+ export async function prepareRepositoryFiles(chain) {
848
+ const { transports, latest, identity } = await readRepositoryChain(chain);
849
+ const scratch = await mkdtemp(join(tmpdir(), "amalgm-retained-repository-"));
850
+ let checkpoint;
851
+ try {
852
+ await runGit(scratch, ["init", "--quiet", `--object-format=${latest.layout.card.objectFormat}`, "--template="], { purpose: "apply" });
853
+ for (const transport of transports) {
854
+ if (transport.layout.pack)
855
+ await importPackFile(scratch, transport.file, transport.layout.pack);
856
+ }
857
+ checkpoint = await prepareLiteralCheckpoint(scratch, checkpointFileSource(latest.file, latest.layout.checkpoint), identity);
858
+ return {
859
+ layout: latest.layout, identity, checkpoint,
860
+ close: async () => { await checkpoint.close(); await rm(scratch, { recursive: true, force: true }); },
861
+ };
862
+ }
863
+ catch (error) {
864
+ await checkpoint?.close();
865
+ await rm(scratch, { recursive: true, force: true });
866
+ throw error;
867
+ }
868
+ }
869
+ /** Apply sealed repository artifacts by streaming each Git range directly
870
+ * into index-pack. Download already verified each complete transport hash. */
871
+ export async function applyRepositoryFiles(repository, chain, ground) {
872
+ const { transports, latest, identity, transportVersion } = await readRepositoryChain(chain);
895
873
  const state = latest.layout;
896
874
  mkdirSync(repository, { recursive: true });
897
875
  const fresh = !hasGitMarker(repository);
898
- const firstMissing = fresh ? 0 : firstAbsentTransport(repository, transports);
899
- initializeMaterializedRepository(repository);
900
- const priorFiles = fresh ? new Set(gitObjectFiles(repository)) : null;
901
- markRepositoryReveal(repository);
902
- forceRemotes(repository, state.card.remotes);
903
- for (const transport of transports.slice(firstMissing)) {
876
+ await initializeMaterializedRepository(repository, state.card.objectFormat);
877
+ // An init template may carry pre-existing objects. Git has not necessarily
878
+ // written them, so their presence cannot become a durability acknowledgement.
879
+ const priorFiles = fresh ? new Set(await gitObjectFiles(repository)) : null;
880
+ const absent = fresh ? 0 : await firstAbsentTransport(repository, transports);
881
+ for (const transport of transports.slice(absent)) {
904
882
  if (transport.layout.pack) {
905
883
  await importPackFile(repository, transport.file, transport.layout.pack);
906
884
  }
907
885
  }
908
- installShallowBoundary(repository, state.card.shallow ?? []);
909
- forceRefs(repository, state);
910
- forceStash(repository, state);
911
- forceHead(repository, state);
912
- applyCheckpointFile(repository, latest.file, state.checkpoint);
913
- const proof = captureState(repository, nestedRepositoryPaths(identity), true);
886
+ await applyCheckpointFile(repository, latest.file, state.checkpoint, identity, ground, async () => {
887
+ markRepositoryReveal(repository);
888
+ await installShallowBoundary(repository, state.card.shallow ?? []);
889
+ await forceRemotes(repository, state.card.remotes);
890
+ await forceRefs(repository, state);
891
+ await forceStash(repository, state);
892
+ await forceHead(repository, state);
893
+ });
894
+ // A target proof only: kept remainder is accounted by Verify, not here.
895
+ const kept = ground.kind === "working" ? ground.remainder : new Set();
896
+ // The readback closes before its reveal marker retires.
897
+ const reading = await readRepository(repository, nestedRepositoryPaths(identity), "apply", true);
898
+ const proof = withoutUntracked(reading.state, path => kept.has(path));
914
899
  if (proof.cardId !== state.cardId || proof.checkpointId !== state.checkpointId) {
915
900
  throw new Error("materialized repository does not reproduce its declared state");
916
901
  }
917
- if (JSON.stringify(proof.card.shallow ?? []) !== JSON.stringify(state.card.shallow ?? [])) {
918
- throw new Error("materialized repository does not reproduce its shallow boundary");
919
- }
920
902
  rmSync(join(gitDirectory(repository), REVEAL_MARKER), { force: true });
921
903
  return {
922
904
  stateId: state.stateId,
923
905
  cardId: state.cardId,
924
906
  checkpointId: state.checkpointId,
925
907
  card: state.card,
926
- transportVersion: priorVersion,
927
- identity: identity,
928
- durableFiles: newlyDurableGitObjects(repository, priorFiles),
908
+ transportVersion,
909
+ identity,
910
+ durableFiles: await newlyDurableGitObjects(repository, priorFiles),
911
+ reading,
929
912
  };
930
913
  }
931
914
  //# sourceMappingURL=git-repository-host.js.map