@idleware/ringi 0.4.0 → 0.6.0

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 (375) hide show
  1. package/README.md +1 -1
  2. package/package.json +5 -1
  3. package/target/dts/adapters/fs/index.d.ts +2 -0
  4. package/target/dts/adapters/fs/lock.d.ts +1 -0
  5. package/target/dts/adapters/fs/write.d.ts +5 -0
  6. package/target/dts/adapters/host-posix/group-state.d.ts +7 -0
  7. package/target/dts/adapters/host-posix/groups.d.ts +1 -0
  8. package/target/dts/adapters/host-posix/index.d.ts +2 -0
  9. package/target/dts/adapters/host-posix/launch.d.ts +7 -0
  10. package/target/dts/adapters/host-posix/probes.d.ts +5 -0
  11. package/target/dts/adapters/host-posix/stats.d.ts +9 -0
  12. package/target/dts/adapters/keys-file/index.d.ts +2 -0
  13. package/target/dts/adapters/state-fs/index.d.ts +5 -0
  14. package/target/dts/adapters/tool-claude/index.d.ts +2 -0
  15. package/target/dts/adapters/tool-codex/index.d.ts +2 -0
  16. package/target/dts/adapters/tool-cursor/index.d.ts +2 -0
  17. package/target/dts/adapters/tool-traces/index.d.ts +3 -0
  18. package/target/dts/adapters/transport-git/branches.d.ts +43 -0
  19. package/target/dts/adapters/transport-git/files.d.ts +2 -0
  20. package/target/dts/adapters/transport-git/index.d.ts +32 -0
  21. package/target/dts/adapters/transport-git/marks.d.ts +20 -0
  22. package/target/dts/adapters/transport-git/remote/admit.d.ts +7 -0
  23. package/target/dts/adapters/transport-git/remote/alarms.d.ts +3 -0
  24. package/target/dts/adapters/transport-git/remote/bootstrap.d.ts +7 -0
  25. package/target/dts/adapters/transport-git/remote/branch-commit.d.ts +3 -0
  26. package/target/dts/adapters/transport-git/remote/branch-control.d.ts +15 -0
  27. package/target/dts/adapters/transport-git/remote/branch-scan.d.ts +2 -0
  28. package/target/dts/adapters/transport-git/remote/branch.d.ts +5 -0
  29. package/target/dts/adapters/transport-git/remote/cache.d.ts +3 -0
  30. package/target/dts/adapters/transport-git/remote/commits.d.ts +2 -0
  31. package/target/dts/adapters/transport-git/remote/forget.d.ts +2 -0
  32. package/target/dts/adapters/transport-git/remote/git.d.ts +26 -0
  33. package/target/dts/adapters/transport-git/remote/heartbeat-facts.d.ts +1 -0
  34. package/target/dts/adapters/transport-git/remote/heartbeat-name.d.ts +6 -0
  35. package/target/dts/adapters/transport-git/remote/heartbeat-object.d.ts +10 -0
  36. package/target/dts/adapters/transport-git/remote/heartbeat-push.d.ts +2 -0
  37. package/target/dts/adapters/transport-git/remote/heartbeat.d.ts +7 -0
  38. package/target/dts/adapters/transport-git/remote/keys.d.ts +13 -0
  39. package/target/dts/adapters/transport-git/remote/main.d.ts +11 -0
  40. package/target/dts/adapters/transport-git/remote/process.d.ts +20 -0
  41. package/target/dts/adapters/transport-git/remote/push-retry.d.ts +2 -0
  42. package/target/dts/adapters/transport-git/remote/push-validate.d.ts +2 -0
  43. package/target/dts/adapters/transport-git/remote/push.d.ts +16 -0
  44. package/target/dts/adapters/transport-git/remote/roster.d.ts +7 -0
  45. package/target/dts/adapters/transport-git/remote/signals.d.ts +15 -0
  46. package/target/dts/adapters/transport-git/remote/signature.d.ts +5 -0
  47. package/target/dts/adapters/transport-git/remote/sync-heartbeat.d.ts +2 -0
  48. package/target/dts/adapters/transport-git/remote/sync-rooms.d.ts +6 -0
  49. package/target/dts/adapters/transport-git/remote/sync-tags.d.ts +8 -0
  50. package/target/dts/adapters/transport-git/remote/sync.d.ts +2 -0
  51. package/target/dts/adapters/transport-git/remote/types.d.ts +64 -0
  52. package/target/dts/adapters/transport-git/remote.d.ts +17 -0
  53. package/target/dts/adapters/transport-git/repo.d.ts +31 -0
  54. package/target/dts/adapters/transport-git/run.d.ts +21 -0
  55. package/target/dts/adapters/transport-git/signing.d.ts +4 -0
  56. package/target/dts/adapters/transport-git/tags.d.ts +15 -0
  57. package/target/dts/adapters/transport-git/transport.d.ts +7 -0
  58. package/target/dts/adapters/transport-kafka/index.d.ts +2 -0
  59. package/target/dts/adapters/transport-temporal/index.d.ts +2 -0
  60. package/target/dts/cli.d.ts +2 -1
  61. package/target/dts/{cli/agent.d.ts → clients/cli/agent/index.d.ts} +3 -3
  62. package/target/dts/{cli → clients/cli/agent}/session.d.ts +2 -2
  63. package/target/dts/clients/cli/arg-parse.d.ts +2 -0
  64. package/target/dts/{cli → clients/cli}/args.d.ts +2 -2
  65. package/target/dts/clients/cli/commands.d.ts +2 -0
  66. package/target/dts/clients/cli/context.d.ts +4 -0
  67. package/target/dts/{cli/index.d.ts → clients/cli/help.d.ts} +1 -3
  68. package/target/dts/clients/cli/index.d.ts +4 -0
  69. package/target/dts/clients/cli/operator/heartbeat-format.d.ts +5 -0
  70. package/target/dts/clients/cli/operator/heartbeat.d.ts +3 -0
  71. package/target/dts/clients/cli/operator/hub-commands.d.ts +6 -0
  72. package/target/dts/clients/cli/operator/hub.d.ts +2 -0
  73. package/target/dts/clients/cli/operator/index.d.ts +5 -0
  74. package/target/dts/clients/cli/operator/room-lifecycle.d.ts +5 -0
  75. package/target/dts/clients/cli/operator/room-list.d.ts +5 -0
  76. package/target/dts/clients/cli/operator/room-members.d.ts +4 -0
  77. package/target/dts/clients/cli/operator/room.d.ts +2 -0
  78. package/target/dts/clients/cli/operator/sessions.d.ts +5 -0
  79. package/target/dts/clients/cli/operator/watch.d.ts +2 -0
  80. package/target/dts/clients/cli/operator/where-format.d.ts +7 -0
  81. package/target/dts/clients/cli/operator/where.d.ts +2 -0
  82. package/target/dts/clients/cli/shared/budget.d.ts +7 -0
  83. package/target/dts/clients/cli/shared/files.d.ts +4 -0
  84. package/target/dts/clients/cli/shared/index.d.ts +7 -0
  85. package/target/dts/clients/cli/shared/messages.d.ts +7 -0
  86. package/target/dts/clients/cli/shared/profile.d.ts +4 -0
  87. package/target/dts/clients/cli/shared/session.d.ts +6 -0
  88. package/target/dts/clients/cli/shared/signals.d.ts +3 -0
  89. package/target/dts/clients/cli/shared/work.d.ts +6 -0
  90. package/target/dts/clients/cli/skill/index.d.ts +3 -0
  91. package/target/dts/{cli/skill.d.ts → clients/cli/skill/targets.d.ts} +1 -2
  92. package/target/dts/composition/index.d.ts +2 -0
  93. package/target/dts/core/agents/heartbeat/index.d.ts +33 -0
  94. package/target/dts/core/agents/heartbeat/pulse.d.ts +32 -0
  95. package/target/dts/core/agents/heartbeat/singleton.d.ts +3 -0
  96. package/target/dts/core/agents/heartbeat/tools.d.ts +6 -0
  97. package/target/dts/{app → core/agents}/overview.d.ts +12 -6
  98. package/target/dts/core/agents/ports/host.d.ts +47 -0
  99. package/target/dts/core/agents/ports/index.d.ts +9 -0
  100. package/target/dts/core/agents/ports/tools.d.ts +18 -0
  101. package/target/dts/core/agents/sessions/agent.d.ts +70 -0
  102. package/target/dts/core/agents/sessions/binding.d.ts +9 -0
  103. package/target/dts/core/agents/sessions/context.d.ts +8 -0
  104. package/target/dts/{runtime/session.d.ts → core/agents/sessions/index.d.ts} +2 -9
  105. package/target/dts/core/agents/sessions/join.d.ts +20 -0
  106. package/target/dts/core/agents/sessions/resume.d.ts +2 -0
  107. package/target/dts/core/agents/sessions/session.d.ts +46 -0
  108. package/target/dts/core/agents/sessions/wait.d.ts +38 -0
  109. package/target/dts/core/agents/turns/budgets.d.ts +5 -0
  110. package/target/dts/core/agents/turns/dispatch.d.ts +9 -0
  111. package/target/dts/core/agents/turns/exhaustion.d.ts +11 -0
  112. package/target/dts/core/agents/turns/grants.d.ts +4 -0
  113. package/target/dts/core/agents/turns/index.d.ts +4 -0
  114. package/target/dts/core/agents/turns/limits.d.ts +2 -0
  115. package/target/dts/core/agents/turns/messages.d.ts +10 -0
  116. package/target/dts/core/agents/turns/pings.d.ts +5 -0
  117. package/target/dts/core/agents/turns/plan.d.ts +3 -0
  118. package/target/dts/core/agents/turns/poll.d.ts +8 -0
  119. package/target/dts/core/agents/turns/results.d.ts +3 -0
  120. package/target/dts/core/agents/turns/seat.d.ts +2 -0
  121. package/target/dts/core/agents/turns/types.d.ts +59 -0
  122. package/target/dts/core/hub/decisions/budget.d.ts +15 -0
  123. package/target/dts/core/hub/decisions/check.d.ts +6 -0
  124. package/target/dts/core/hub/decisions/index.d.ts +5 -0
  125. package/target/dts/core/hub/decisions/proposal.d.ts +38 -0
  126. package/target/dts/core/hub/decisions/usage.d.ts +12 -0
  127. package/target/dts/{runtime/reader.d.ts → core/hub/history/index.d.ts} +12 -6
  128. package/target/dts/core/hub/ports/index.d.ts +16 -0
  129. package/target/dts/core/hub/ports/state.d.ts +12 -0
  130. package/target/dts/{store/index.d.ts → core/hub/ports/store.d.ts} +58 -17
  131. package/target/dts/core/hub/protocol/index.d.ts +5 -0
  132. package/target/dts/{protocol/index.d.ts → core/hub/protocol/model.d.ts} +0 -25
  133. package/target/dts/core/hub/protocol/order.d.ts +2 -0
  134. package/target/dts/core/hub/protocol/reading.d.ts +8 -0
  135. package/target/dts/core/hub/protocol/text.d.ts +11 -0
  136. package/target/dts/core/hub/protocol/threads.d.ts +5 -0
  137. package/target/dts/core/hub/rooms/compat.d.ts +5 -0
  138. package/target/dts/core/hub/rooms/hubs.d.ts +14 -0
  139. package/target/dts/core/hub/rooms/index.d.ts +53 -0
  140. package/target/dts/core/hub/rooms/messages.d.ts +8 -0
  141. package/target/dts/core/hub/rooms/rooms.d.ts +10 -0
  142. package/target/dts/core/hub/signals/check.d.ts +3 -0
  143. package/target/dts/core/hub/signals/effective.d.ts +3 -0
  144. package/target/dts/core/hub/signals/index.d.ts +5 -0
  145. package/target/dts/core/hub/signals/marks.d.ts +4 -0
  146. package/target/dts/core/hub/signals/model.d.ts +28 -0
  147. package/target/dts/core/hub/signals/pins.d.ts +5 -0
  148. package/target/dts/{hub/signals.d.ts → core/hub/signals/write.d.ts} +1 -1
  149. package/target/dts/core/hub/usecases/actor.d.ts +6 -0
  150. package/target/dts/{app → core/hub/usecases}/budget.d.ts +5 -18
  151. package/target/dts/core/hub/usecases/config.d.ts +6 -0
  152. package/target/dts/{app → core/hub/usecases}/context.d.ts +3 -8
  153. package/target/dts/{app → core/hub/usecases}/hubs.d.ts +1 -1
  154. package/target/dts/core/hub/usecases/propose.d.ts +17 -0
  155. package/target/dts/{app → core/hub/usecases}/room.d.ts +8 -33
  156. package/target/dts/{app → core/hub/usecases}/rooms.d.ts +2 -6
  157. package/target/dts/core/hub/usecases/roster.d.ts +5 -0
  158. package/target/dts/core/hub/usecases/send.d.ts +21 -0
  159. package/target/dts/core/hub/usecases/staging.d.ts +8 -0
  160. package/target/dts/{app → core/hub/usecases}/view.d.ts +4 -4
  161. package/target/dts/{domain/profiles.d.ts → core/hub/work/index.d.ts} +3 -7
  162. package/target/dts/core/hub/work/parse.d.ts +2 -0
  163. package/target/dts/core/hub/work/task.d.ts +6 -0
  164. package/target/dts/core/signing/index.d.ts +35 -0
  165. package/target/dts/index.d.ts +8 -8
  166. package/target/dts/lib/fail.d.ts +8 -0
  167. package/target/dts/lib/index.d.ts +3 -11
  168. package/target/dts/lib/json.d.ts +1 -0
  169. package/target/esm/adapters/fs/index.js +5 -0
  170. package/target/esm/adapters/fs/lock.js +83 -0
  171. package/target/esm/adapters/fs/write.js +25 -0
  172. package/target/esm/adapters/host-posix/group-state.js +24 -0
  173. package/target/esm/adapters/host-posix/groups.js +43 -0
  174. package/target/esm/adapters/host-posix/index.js +35 -0
  175. package/target/esm/adapters/host-posix/launch.js +80 -0
  176. package/target/esm/adapters/host-posix/probes.js +66 -0
  177. package/target/esm/adapters/host-posix/stats.js +27 -0
  178. package/target/esm/adapters/keys-file/index.js +68 -0
  179. package/target/esm/adapters/state-fs/index.js +79 -0
  180. package/target/esm/adapters/tool-claude/index.js +32 -0
  181. package/target/esm/adapters/tool-codex/index.js +38 -0
  182. package/target/esm/adapters/tool-cursor/index.js +8 -0
  183. package/target/esm/adapters/tool-traces/index.js +18 -0
  184. package/target/esm/adapters/transport-git/branches.js +155 -0
  185. package/target/esm/adapters/transport-git/files.js +48 -0
  186. package/target/esm/adapters/transport-git/index.js +128 -0
  187. package/target/esm/adapters/transport-git/marks.js +88 -0
  188. package/target/esm/adapters/transport-git/remote/admit.js +71 -0
  189. package/target/esm/adapters/transport-git/remote/alarms.js +30 -0
  190. package/target/esm/adapters/transport-git/remote/bootstrap.js +47 -0
  191. package/target/esm/adapters/transport-git/remote/branch-commit.js +36 -0
  192. package/target/esm/adapters/transport-git/remote/branch-control.js +37 -0
  193. package/target/esm/adapters/transport-git/remote/branch-scan.js +28 -0
  194. package/target/esm/adapters/transport-git/remote/branch.js +37 -0
  195. package/target/esm/adapters/transport-git/remote/cache.js +23 -0
  196. package/target/esm/adapters/transport-git/remote/commits.js +25 -0
  197. package/target/esm/adapters/transport-git/remote/forget.js +35 -0
  198. package/target/esm/adapters/transport-git/remote/git.js +58 -0
  199. package/target/esm/adapters/transport-git/remote/heartbeat-facts.js +35 -0
  200. package/target/esm/adapters/transport-git/remote/heartbeat-name.js +19 -0
  201. package/target/esm/adapters/transport-git/remote/heartbeat-object.js +40 -0
  202. package/target/esm/adapters/transport-git/remote/heartbeat-push.js +44 -0
  203. package/target/esm/adapters/transport-git/remote/heartbeat.js +44 -0
  204. package/target/esm/adapters/transport-git/remote/keys.js +75 -0
  205. package/target/esm/adapters/transport-git/remote/main.js +41 -0
  206. package/target/esm/adapters/transport-git/remote/process.js +37 -0
  207. package/target/esm/adapters/transport-git/remote/push-retry.js +46 -0
  208. package/target/esm/adapters/transport-git/remote/push-validate.js +39 -0
  209. package/target/esm/adapters/transport-git/remote/push.js +62 -0
  210. package/target/esm/adapters/transport-git/remote/roster.js +48 -0
  211. package/target/esm/adapters/transport-git/remote/signals.js +63 -0
  212. package/target/esm/adapters/transport-git/remote/signature.js +56 -0
  213. package/target/esm/adapters/transport-git/remote/sync-heartbeat.js +26 -0
  214. package/target/esm/adapters/transport-git/remote/sync-rooms.js +38 -0
  215. package/target/esm/adapters/transport-git/remote/sync-tags.js +48 -0
  216. package/target/esm/adapters/transport-git/remote/sync.js +38 -0
  217. package/target/esm/adapters/transport-git/remote/types.js +10 -0
  218. package/target/esm/adapters/transport-git/remote.js +17 -0
  219. package/target/esm/adapters/transport-git/repo.js +115 -0
  220. package/target/esm/adapters/transport-git/run.js +57 -0
  221. package/target/esm/adapters/transport-git/signing.js +44 -0
  222. package/target/esm/adapters/transport-git/tags.js +21 -0
  223. package/target/esm/adapters/transport-git/transport.js +29 -0
  224. package/target/esm/adapters/transport-kafka/index.js +4 -0
  225. package/target/esm/adapters/transport-temporal/index.js +4 -0
  226. package/target/esm/cli.js +5 -4
  227. package/target/esm/{cli/agent.js → clients/cli/agent/index.js} +8 -7
  228. package/target/esm/{cli → clients/cli/agent}/session.js +13 -7
  229. package/target/esm/clients/cli/arg-parse.js +41 -0
  230. package/target/esm/{cli → clients/cli}/args.js +3 -36
  231. package/target/esm/clients/cli/commands.js +45 -0
  232. package/target/esm/clients/cli/context.js +6 -0
  233. package/target/esm/{cli/index.js → clients/cli/help.js} +6 -73
  234. package/target/esm/clients/cli/index.js +27 -0
  235. package/target/esm/clients/cli/operator/heartbeat-format.js +8 -0
  236. package/target/esm/clients/cli/operator/heartbeat.js +35 -0
  237. package/target/esm/clients/cli/operator/hub-commands.js +37 -0
  238. package/target/esm/clients/cli/operator/hub.js +10 -0
  239. package/target/esm/clients/cli/operator/index.js +8 -0
  240. package/target/esm/clients/cli/operator/room-lifecycle.js +23 -0
  241. package/target/esm/clients/cli/operator/room-list.js +27 -0
  242. package/target/esm/clients/cli/operator/room-members.js +15 -0
  243. package/target/esm/clients/cli/operator/room.js +15 -0
  244. package/target/esm/clients/cli/operator/sessions.js +32 -0
  245. package/target/esm/clients/cli/operator/watch.js +15 -0
  246. package/target/esm/clients/cli/operator/where-format.js +24 -0
  247. package/target/esm/clients/cli/operator/where.js +32 -0
  248. package/target/esm/clients/cli/shared/budget.js +50 -0
  249. package/target/esm/clients/cli/shared/files.js +26 -0
  250. package/target/esm/clients/cli/shared/index.js +10 -0
  251. package/target/esm/clients/cli/shared/messages.js +41 -0
  252. package/target/esm/clients/cli/shared/profile.js +17 -0
  253. package/target/esm/clients/cli/shared/session.js +8 -0
  254. package/target/esm/clients/cli/shared/signals.js +21 -0
  255. package/target/esm/clients/cli/shared/work.js +26 -0
  256. package/target/esm/clients/cli/skill/index.js +40 -0
  257. package/target/esm/clients/cli/skill/targets.js +20 -0
  258. package/target/esm/composition/index.js +34 -0
  259. package/target/esm/core/agents/heartbeat/index.js +43 -0
  260. package/target/esm/core/agents/heartbeat/pulse.js +86 -0
  261. package/target/esm/core/agents/heartbeat/singleton.js +38 -0
  262. package/target/esm/core/agents/heartbeat/tools.js +24 -0
  263. package/target/esm/{app → core/agents}/overview.js +15 -9
  264. package/target/esm/core/agents/ports/host.js +4 -0
  265. package/target/esm/core/agents/ports/index.js +7 -0
  266. package/target/esm/core/agents/ports/tools.js +5 -0
  267. package/target/esm/{app → core/agents/sessions}/agent.js +11 -43
  268. package/target/esm/core/agents/sessions/binding.js +43 -0
  269. package/target/esm/core/agents/sessions/context.js +11 -0
  270. package/target/esm/core/agents/sessions/index.js +59 -0
  271. package/target/esm/core/agents/sessions/join.js +55 -0
  272. package/target/esm/core/agents/sessions/resume.js +54 -0
  273. package/target/esm/core/agents/sessions/session.js +28 -0
  274. package/target/esm/core/agents/sessions/wait.js +47 -0
  275. package/target/esm/core/agents/turns/budgets.js +48 -0
  276. package/target/esm/core/agents/turns/dispatch.js +40 -0
  277. package/target/esm/core/agents/turns/exhaustion.js +16 -0
  278. package/target/esm/core/agents/turns/grants.js +36 -0
  279. package/target/esm/core/agents/turns/index.js +33 -0
  280. package/target/esm/core/agents/turns/limits.js +11 -0
  281. package/target/esm/core/agents/turns/messages.js +32 -0
  282. package/target/esm/core/agents/turns/pings.js +30 -0
  283. package/target/esm/core/agents/turns/plan.js +29 -0
  284. package/target/esm/core/agents/turns/poll.js +48 -0
  285. package/target/esm/core/agents/turns/results.js +40 -0
  286. package/target/esm/core/agents/turns/seat.js +44 -0
  287. package/target/esm/core/agents/turns/types.js +3 -0
  288. package/target/esm/{domain/index.js → core/hub/capabilities.js} +3 -3
  289. package/target/esm/core/hub/decisions/budget.js +40 -0
  290. package/target/esm/core/hub/decisions/check.js +43 -0
  291. package/target/esm/core/hub/decisions/index.js +10 -0
  292. package/target/esm/core/hub/decisions/proposal.js +73 -0
  293. package/target/esm/core/hub/decisions/usage.js +39 -0
  294. package/target/esm/core/hub/history/index.js +90 -0
  295. package/target/esm/core/hub/ports/index.js +10 -0
  296. package/target/esm/core/hub/ports/state.js +5 -0
  297. package/target/esm/core/hub/ports/store.js +10 -0
  298. package/target/esm/{protocol → core/hub/protocol}/files.js +4 -2
  299. package/target/esm/core/hub/protocol/index.js +9 -0
  300. package/target/esm/core/hub/protocol/model.js +15 -0
  301. package/target/esm/core/hub/protocol/order.js +39 -0
  302. package/target/esm/core/hub/protocol/reading.js +29 -0
  303. package/target/esm/core/hub/protocol/text.js +95 -0
  304. package/target/esm/core/hub/protocol/threads.js +52 -0
  305. package/target/esm/core/hub/rooms/compat.js +42 -0
  306. package/target/esm/core/hub/rooms/hubs.js +92 -0
  307. package/target/esm/core/hub/rooms/index.js +23 -0
  308. package/target/esm/core/hub/rooms/messages.js +47 -0
  309. package/target/esm/core/hub/rooms/rooms.js +84 -0
  310. package/target/esm/core/hub/signals/check.js +33 -0
  311. package/target/esm/core/hub/signals/effective.js +31 -0
  312. package/target/esm/core/hub/signals/index.js +23 -0
  313. package/target/esm/core/hub/signals/marks.js +30 -0
  314. package/target/esm/core/hub/signals/model.js +5 -0
  315. package/target/esm/core/hub/signals/pins.js +21 -0
  316. package/target/esm/{hub/signals.js → core/hub/signals/write.js} +4 -4
  317. package/target/esm/core/hub/usecases/actor.js +3 -0
  318. package/target/esm/core/hub/usecases/budget.js +78 -0
  319. package/target/esm/core/hub/usecases/config.js +5 -0
  320. package/target/esm/core/hub/usecases/context.js +13 -0
  321. package/target/esm/core/hub/usecases/hubs.js +95 -0
  322. package/target/esm/core/hub/usecases/propose.js +39 -0
  323. package/target/esm/core/hub/usecases/room.js +82 -0
  324. package/target/esm/{app → core/hub/usecases}/rooms.js +11 -63
  325. package/target/esm/core/hub/usecases/roster.js +47 -0
  326. package/target/esm/core/hub/usecases/send.js +78 -0
  327. package/target/esm/core/hub/usecases/staging.js +60 -0
  328. package/target/esm/{app → core/hub/usecases}/view.js +33 -33
  329. package/target/esm/core/hub/work/index.js +13 -0
  330. package/target/esm/core/hub/work/parse.js +37 -0
  331. package/target/esm/core/hub/work/task.js +68 -0
  332. package/target/esm/core/signing/index.js +4 -0
  333. package/target/esm/index.js +7 -6
  334. package/target/esm/lib/fail.js +18 -0
  335. package/target/esm/lib/index.js +20 -195
  336. package/target/esm/lib/json.js +81 -0
  337. package/target/dts/app/agent.d.ts +0 -109
  338. package/target/dts/app/session.d.ts +0 -65
  339. package/target/dts/cli/context.d.ts +0 -6
  340. package/target/dts/cli/operator.d.ts +0 -8
  341. package/target/dts/cli/shared.d.ts +0 -27
  342. package/target/dts/domain/decisions.d.ts +0 -65
  343. package/target/dts/domain/signals.d.ts +0 -37
  344. package/target/dts/home/index.d.ts +0 -21
  345. package/target/dts/hub/index.d.ts +0 -72
  346. package/target/dts/runtime/watch.d.ts +0 -17
  347. package/target/dts/store/git/index.d.ts +0 -79
  348. package/target/dts/store/git/remote.d.ts +0 -86
  349. package/target/dts/store/kafka/index.d.ts +0 -2
  350. package/target/dts/store/temporal/index.d.ts +0 -2
  351. package/target/esm/app/budget.js +0 -95
  352. package/target/esm/app/context.js +0 -16
  353. package/target/esm/app/hubs.js +0 -76
  354. package/target/esm/app/room.js +0 -163
  355. package/target/esm/app/session.js +0 -99
  356. package/target/esm/cli/context.js +0 -8
  357. package/target/esm/cli/operator.js +0 -157
  358. package/target/esm/cli/shared.js +0 -159
  359. package/target/esm/cli/skill.js +0 -54
  360. package/target/esm/domain/decisions.js +0 -178
  361. package/target/esm/domain/profiles.js +0 -96
  362. package/target/esm/domain/signals.js +0 -134
  363. package/target/esm/home/index.js +0 -88
  364. package/target/esm/hub/index.js +0 -224
  365. package/target/esm/protocol/index.js +0 -219
  366. package/target/esm/runtime/reader.js +0 -65
  367. package/target/esm/runtime/session.js +0 -116
  368. package/target/esm/runtime/watch.js +0 -321
  369. package/target/esm/store/git/index.js +0 -644
  370. package/target/esm/store/git/remote.js +0 -758
  371. package/target/esm/store/index.js +0 -15
  372. package/target/esm/store/kafka/index.js +0 -3
  373. package/target/esm/store/temporal/index.js +0 -3
  374. /package/target/dts/{domain/index.d.ts → core/hub/capabilities.d.ts} +0 -0
  375. /package/target/dts/{protocol → core/hub/protocol}/files.d.ts +0 -0
@@ -1,644 +0,0 @@
1
- // git-store: the Store port on git. The store directory holds one bare repository (`store/.git`):
2
- // refs/heads/main the shared contract, one commit per amendment
3
- // refs/heads/<room> the room: one linear branch, like a chat. Its first commit is the room's genesis
4
- // (the root's, on top of main); then every message is a commit by its author, and
5
- // every control record (Kind: room) a commit by the root
6
- // refs/tags/<agent>.<signal>.<message12> signals: annotated tags without text on the message's commit
7
- // refs/archive/<room>@<genesis8> an archived room, read-only
8
- // refs/ringi/accepted/<genesis>/<reader>/<author> readers' pins of each author's accepted last message
9
- // A member's log is its own commits in the room's branch, in branch order. The control files at the root of a room's
10
- // tree (members, profile, signers) change only in the root's control records.
11
- // A signed hub signs every commit and tag with its author's key and verifies what it fetches (remote.ts).
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
- import crypto from 'node:crypto';
15
- import { spawnSync } from 'node:child_process';
16
- import { cannotWrite, mkdirIn, SANDBOX } from '../../lib/index.js';
17
- import * as Remote from './remote.js';
18
- import { fail, locked, note, strictJson, writeJsonAtomic } from '../../lib/index.js';
19
- import { AGENT_RE, ID_RE, TEXT_MAX, checkOutgoing, compose, parse } from '../../protocol/index.js';
20
- import { checkCollisions, checkPath } from '../../protocol/files.js';
21
- const ZERO = '0'.repeat(40);
22
- const ident = (id) => ({ GIT_AUTHOR_NAME: id, GIT_AUTHOR_EMAIL: `${id}@ringi`, GIT_COMMITTER_NAME: id, GIT_COMMITTER_EMAIL: `${id}@ringi` });
23
- export const runGit = (args, o = {}) => {
24
- const r = spawnSync('git', args, { input: o.input, encoding: 'utf8', env: Remote.gitEnv(o.env), maxBuffer: 1 << 30, timeout: Remote.gitTimeout() });
25
- return check(args, r, o.where).stdout;
26
- };
27
- const runGitOk = (args, o = {}) => {
28
- const r = spawnSync('git', args, { input: o.input, encoding: 'utf8', env: Remote.gitEnv(o.env), maxBuffer: 1 << 30, timeout: Remote.gitTimeout() });
29
- Remote.checkGitTimeout(r.error);
30
- return r;
31
- };
32
- const runGitRaw = (args, where, input) => {
33
- const r = spawnSync('git', args, { input, encoding: 'buffer', env: Remote.gitEnv(), maxBuffer: 1 << 30, timeout: Remote.gitTimeout() });
34
- return check(args, r, where).stdout;
35
- };
36
- function check(args, r, where = 'the hub store') {
37
- Remote.checkGitTimeout(r.error);
38
- if (r.error)
39
- throw r.error;
40
- if (r.status !== 0) {
41
- const err = String(r.stderr ?? '').trim();
42
- if (SANDBOX.test(err))
43
- cannotWrite(where, err.split('\n').at(-1));
44
- fail(`git ${args.find(a => !a.startsWith('-') && !a.includes('/')) ?? ''}: ${err.split('\n').at(-1)}`);
45
- }
46
- return r;
47
- }
48
- const splitBuf = (b, sep) => {
49
- const parts = [];
50
- let at = 0;
51
- for (let i = b.indexOf(sep); i >= 0; i = b.indexOf(sep, at)) {
52
- parts.push(b.subarray(at, i));
53
- at = i + 1;
54
- }
55
- parts.push(b.subarray(at));
56
- return parts;
57
- };
58
- const REGULAR = new Set(['100644', '100755']);
59
- // The root's principal, and the files only its control records may change.
60
- const ROOT = 'operator';
61
- const CONTROL_FILES = new Set(['ROOM.md', 'members.json', 'profile.json', 'allowed_signers']);
62
- export class GitStore {
63
- type = 'git';
64
- trust;
65
- dir;
66
- d;
67
- home;
68
- // The handle remote.ts works on: the repository and what it learned on the last sync.
69
- h;
70
- constructor(hubDir, d, home) {
71
- this.d = d;
72
- this.home = home;
73
- this.trust = d.trust;
74
- this.dir = path.join(hubDir, d.store.path, '.git');
75
- this.h = { name: d.name, dir: this.dir, d };
76
- if (!fs.existsSync(this.dir))
77
- fail(`hub ${d.name}: no git store at ${this.dir}`);
78
- }
79
- args(a) { return ['--git-dir', this.dir, ...a]; }
80
- git(a, o) { return runGit(this.args(a), { ...o, where: this.home }); }
81
- gitOk(a, o) { return runGitOk(this.args(a), o); }
82
- rev(ref) {
83
- const r = this.gitOk(['rev-parse', '-q', '--verify', `${ref}^{commit}`]);
84
- return r.status === 0 ? r.stdout.trim() : null;
85
- }
86
- isAncestor(a, b) { return this.gitOk(['merge-base', '--is-ancestor', a, b]).status === 0; }
87
- isRef(s) { return /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(s ?? ''); }
88
- extends(older, newer) { return older === newer || this.isAncestor(older, newer); }
89
- // --- writing ---
90
- // A tree from a base tree without the `drop` paths, with entries put on top; nested paths are fine.
91
- tree(base, entries, drop = []) {
92
- const idx = path.join(this.home, 'tmp', `index.${process.pid}.${crypto.randomBytes(4).toString('hex')}`);
93
- mkdirIn(this.home, path.dirname(idx));
94
- const env = { GIT_INDEX_FILE: idx };
95
- try {
96
- this.git(['read-tree', ...(base ? [base] : ['--empty'])], { env });
97
- if (drop.length)
98
- this.git(['update-index', '--index-info'], { env, input: drop.map(p => `0 ${ZERO}\t${p}\n`).join('') });
99
- for (const e of entries) {
100
- const sha = e.blob ?? this.git(['hash-object', '-w', ...(e.file ? ['--', e.file] : ['--stdin'])], { input: e.content }).trim();
101
- this.git(['update-index', '--add', '--cacheinfo', `${e.mode},${sha},${e.path}`], { env });
102
- }
103
- return this.git(['write-tree'], { env }).trim();
104
- }
105
- finally {
106
- fs.rmSync(idx, { force: true });
107
- }
108
- }
109
- // Every commit is made here: on a signed hub, signed by its author's key on this machine.
110
- commit(who, text, tree, parents) {
111
- return this.git(['commit-tree', ...Remote.sign(this.h, who, this.home), tree, ...parents.flatMap(p => ['-p', p])], { input: text, env: ident(who) }).trim();
112
- }
113
- advance(ref, sha, old) {
114
- if (this.gitOk(['update-ref', ref, sha, old ?? ZERO]).status !== 0)
115
- return false;
116
- // A signed hub publishes at once; a rejected push keeps the commit here and alarms (remote.ts).
117
- return Remote.push(this.h, ref, { behind: !!old && ref !== 'refs/heads/main' });
118
- }
119
- static create(hubDir, d, home, contract, text) {
120
- const dir = path.join(hubDir, d.store.path, '.git');
121
- // A signed hub's repository already exists: rootFor made it with the root key.
122
- if (!fs.existsSync(dir)) {
123
- mkdirIn(home, path.dirname(dir));
124
- runGit(['init', '--bare', '-q', '-b', 'main', dir], { where: home });
125
- }
126
- const s = new GitStore(hubDir, d, home);
127
- const sha = s.commit('operator', text, s.tree(null, Object.entries(contract).map(([p, c]) => ({ path: p, content: c, mode: '100644' }))), []);
128
- if (d.trust === 'signed')
129
- d.genesis = sha;
130
- if (!s.advance('refs/heads/main', sha, null))
131
- fail(`hub ${d.name}: main already exists`);
132
- return s;
133
- }
134
- // A new signed hub: the root key is generated on this machine and pinned in the descriptor.
135
- static rootFor(hubDir, d, home) {
136
- const dir = path.join(hubDir, d.store.path, '.git');
137
- mkdirIn(home, path.dirname(dir));
138
- runGit(['init', '--bare', '-q', '-b', 'main', dir], { where: home });
139
- return Remote.setupSigned({ name: d.name, dir, d }, home, d.store.remote).root;
140
- }
141
- sync() {
142
- Remote.sync(this.h);
143
- }
144
- // --- the contract ---
145
- contractHead() { return this.rev('refs/heads/main') ?? fail(`hub ${this.d.name} has no contract (main)`); }
146
- contractFile(rev, name) {
147
- const r = this.gitOk(['show', `${rev}:${name}`]);
148
- return r.status === 0 ? r.stdout : null;
149
- }
150
- contractFiles(rev) { return this.git(['ls-tree', '-r', '--name-only', rev]).split('\n').filter(Boolean); }
151
- amendContract(who, text, files) {
152
- const head = this.contractHead();
153
- const sha = this.commit(who, text, this.tree(`${head}^{tree}`, Object.entries(files).map(([p, c]) => ({ path: p, content: c, mode: '100644' }))), [head]);
154
- if (!this.advance('refs/heads/main', sha, head))
155
- fail('the contract moved while amending; retry');
156
- return sha;
157
- }
158
- // --- rooms ---
159
- ref(name) { return name.includes('@') ? `refs/archive/${name}` : `refs/heads/${name}`; }
160
- transition(name) { return `refs/ringi/migrating/${name}`; }
161
- tip(name) { return this.rev(this.ref(name)) ?? this.rev(this.transition(name)); }
162
- // Rooms in ringi 0.1's layout (`<room>/control` and a branch per agent) are listed, and refused when opened.
163
- roomNames(archived) {
164
- const prefix = archived ? 'refs/archive/' : 'refs/heads/';
165
- const names = this.git(['for-each-ref', '--format=%(refname)', prefix]).split('\n').filter(Boolean).map(r => r.slice(prefix.length))
166
- .map(n => n.replace(/\/control$/, '')).filter(n => !n.includes('/') && n !== 'main' && n.includes('@') === archived);
167
- return [...new Set(names)].sort();
168
- }
169
- // A control record: the room's genesis, or a root commit of Kind: room.
170
- isControl(room, sha, author, text) {
171
- return sha === room.genesis || (author === ROOT && parse(text, x => this.isRef(x)).kind === 'room');
172
- }
173
- // The room's branch, oldest first, from its genesis. The last scan of each room is kept and grown by what the tip
174
- // added on top of it.
175
- scanned = new Map();
176
- entries(room, tip) {
177
- const last = this.scanned.get(room.genesis);
178
- if (last?.tip === tip)
179
- return last.entries;
180
- const added = last ? this.scan(room, tip, last.tip) : [];
181
- const entries = last && added.length && added[0].parent === last.tip ? [...last.entries, ...added] : this.scan(room, tip);
182
- const judged = this.judged(room, tip, entries);
183
- this.scanned.set(room.genesis, { tip, entries: judged });
184
- return judged;
185
- }
186
- // On a signed hub the branch's verification says which commits are control records, and why the others that are no
187
- // valid message are skipped; a commit off the branch's first parents is never a message (remote.ts).
188
- judged(room, tip, entries) {
189
- if (this.trust !== 'signed' || room.archived)
190
- return entries;
191
- const b = Remote.branch(this.h, { name: room.name, ref: this.ref(room.name), genesis: room.genesis }, tip);
192
- const control = new Set(b.control);
193
- return entries.map(e => ({ ...e, control: control.has(e.sha),
194
- skipped: control.has(e.sha) || b.authors.has(e.sha) ? undefined : b.skipped.get(e.sha) ?? 'not on the room\'s branch' }));
195
- }
196
- scan(room, tip, after) {
197
- const shas = this.git(['rev-list', '--topo-order', '--reverse', tip, '--not', after ?? `${room.genesis}^@`]).trim().split('\n').filter(Boolean);
198
- return Remote.readCommits(this.h, shas).map(c => {
199
- const text = c.body.toString('utf8');
200
- return { sha: c.sha, parent: c.parents[0], author: c.author, text, control: this.isControl(room, c.sha, c.author, text) };
201
- });
202
- }
203
- room(name) {
204
- const ref = this.ref(name);
205
- const tip = this.tip(name);
206
- if (!tip && this.rev(`${ref}/control`))
207
- fail(`room ${name} is in the layout of ringi 0.1; run ringi room migrate ${name} --hub ${this.d.name} to keep its history in one branch`);
208
- if (!tip)
209
- return null;
210
- const genesis = this.git(['rev-list', '--first-parent', '--reverse', tip, '^refs/heads/main']).split('\n')[0]
211
- || this.git(['rev-list', '--first-parent', '--reverse', tip]).split('\n').filter(Boolean).at(-1);
212
- const archived = name.includes('@');
213
- if (this.trust === 'signed' && !archived && this.h.remoteAlarms?.[ref])
214
- fail(`ALARM ${this.h.remoteAlarms[ref]}; room ${name} is quarantined`);
215
- const control = this.entries({ name, genesis, archived }, tip).filter(e => e.control).map(e => ({ rev: e.sha, text: e.text }));
216
- return { name, archived, head: control.at(-1).rev, genesis, tip, base: this.rev(`${genesis}^`) ?? ZERO, control };
217
- }
218
- // Control files are read at a control record, never at the branch's tip.
219
- controlFile(room, name, rev) {
220
- const r = this.gitOk(['show', `${rev ?? room.head}:${name}`]);
221
- return r.status === 0 ? r.stdout : null;
222
- }
223
- createRoom(name, who, text, files) {
224
- const ref = `refs/heads/${name}`;
225
- if (this.rev(ref))
226
- fail(`room ${name} exists in hub ${this.d.name}`);
227
- const sha = this.commit(who, text, this.tree(null, Object.entries(files).map(([p, c]) => ({ path: p, content: c, mode: '100644' }))), [this.contractHead()]);
228
- if (!this.advance(ref, sha, null))
229
- fail(`room ${name} was created meanwhile`);
230
- return sha;
231
- }
232
- // Old messages keep their ids and parents. One control record joins the old tips, then all new writes share the
233
- // room's branch. The transition ref makes the room readable even if the process exits between the two ref
234
- // transactions (Git cannot create <room> and delete <room>/control in the same transaction).
235
- migrateRoom(name) {
236
- if (this.trust !== 'asserted')
237
- fail('legacy room migration is only supported for local asserted hubs');
238
- if (!ID_RE.test(name) || name === 'main')
239
- fail('expected a room id');
240
- const ref = this.ref(name), pending = this.transition(name);
241
- const source = this.rev(pending) ?? this.rev(ref) ?? this.rev(`${ref}/control`) ?? fail(`no legacy room ${name}`);
242
- const genesis = this.git(['rev-list', '--first-parent', '--reverse', source, '^refs/heads/main']).trim().split('\n')[0];
243
- const readers = path.join(this.home, 'readers', this.d.name, genesis);
244
- const files = fs.existsSync(readers) ? fs.readdirSync(readers).filter(f => f.endsWith('.json')).sort() : [];
245
- // Readers pin tag objects while holding these same locks. Keep their state stable across both ref transactions
246
- // and the explicit conversion of their old accepted objects; no new baseline is supplied.
247
- const protect = (at) => at === files.length ? migrate() : locked(path.join(this.home, 'locks', `reader-${this.d.name}-${genesis.slice(0, 12)}-${files[at].slice(0, -5)}.lock`), () => protect(at + 1));
248
- const migrate = () => {
249
- if (!genesis || this.git(['show', '-s', '--format=%s', genesis]).trim() !== `room ${name}`)
250
- fail('legacy room genesis does not name this room');
251
- const finish = (sha) => {
252
- this.git(['update-ref', '--stdin'], { input: `start\ncreate ${ref} ${sha}\ndelete ${pending} ${sha}\nprepare\ncommit\n` });
253
- return sha;
254
- };
255
- const staged = this.rev(pending);
256
- const current = this.rev(ref);
257
- if (staged || current) {
258
- const tip = staged ?? current;
259
- const tags = this.migrateSignals(name, genesis, [tip], files.map(f => path.join(readers, f)));
260
- if (staged)
261
- finish(staged);
262
- tags();
263
- return tip;
264
- }
265
- const ctl = `${ref}/control`, head = this.rev(ctl) ?? fail(`no legacy room ${name}`);
266
- const logs = this.refsUnder(`${ref}/log/`);
267
- for (const [r, sha] of [[ctl, head], ...logs]) {
268
- const who = r === ctl ? ROOT : r.slice(`${ref}/log/`.length);
269
- if (!ID_RE.test(who) || !this.extends(genesis, sha))
270
- fail(`ALARM ${r} does not descend from its room genesis`);
271
- const commits = this.git(['rev-list', '--parents', '--reverse', sha, `^${genesis}`]).trim().split('\n').filter(Boolean);
272
- let parent = genesis;
273
- for (const row of commits) {
274
- const [id, ...parents] = row.split(' ');
275
- if (parents.length !== 1 || parents[0] !== parent)
276
- fail(`ALARM ${r} is not linear`);
277
- if (this.git(['show', '-s', '--format=%an', id]).trim() !== who)
278
- fail(`ALARM ${r} contains another author's message`);
279
- parent = id;
280
- }
281
- }
282
- // Accepted heads must still be present before any ref moves. Migration never supplies a fresh baseline.
283
- const byAuthor = new Map(logs.map(([r, sha]) => [r.slice(`${ref}/log/`.length), sha]));
284
- const checkAccepted = (who, sha) => {
285
- const tip = byAuthor.get(who);
286
- if (!tip || !this.extends(sha, tip))
287
- fail(`ALARM log ${who} lost accepted history; migration stopped`);
288
- };
289
- for (const [r, sha] of this.refsUnder(`refs/ringi/accepted/${genesis}/`))
290
- checkAccepted(r.split('/').at(-1), sha);
291
- for (const f of files) {
292
- const st = strictJson(fs.readFileSync(path.join(readers, f), 'utf8'), f);
293
- if (st.control && !this.extends(st.control, head))
294
- fail('ALARM room control lost accepted history; migration stopped');
295
- for (const [who, sha] of Object.entries(st.heads ?? {}))
296
- checkAccepted(who, sha);
297
- }
298
- const tags = this.migrateSignals(name, genesis, [head, ...logs.map(([, id]) => id)], files.map(f => path.join(readers, f)));
299
- const sha = this.commit(ROOT, compose(`migrate room ${name}`, [['To', '*'], ['Kind', 'room'], ['Ringi-Layout', 'branch/1']]), this.git(['rev-parse', `${head}^{tree}`]).trim(), [...new Set([head, ...logs.map(([, id]) => id)])]);
300
- const old = [[ctl, head], ...logs];
301
- const backup = `refs/ringi/legacy/${genesis}/`;
302
- this.git(['update-ref', '--stdin'], { input: ['start', `create ${pending} ${sha}`,
303
- ...old.flatMap(([r, id]) => [`create ${backup}${r.slice(`${ref}/`.length)} ${id}`, `delete ${r} ${id}`]),
304
- 'prepare', 'commit', ''].join('\n') });
305
- finish(sha);
306
- tags();
307
- return sha;
308
- };
309
- mkdirIn(this.home, path.join(this.home, 'locks'));
310
- return protect(0);
311
- }
312
- // An asserted hub can explicitly convert its old tag annotations, keeping their target, tagger and date. The
313
- // original objects and reader snapshots remain evidence. Ref changes are atomic; a repeat resumes reader writes
314
- // after a crash without replacing an accepted object that changed or disappeared in either format.
315
- migrateSignals(room, genesis, tips, readers) {
316
- const prefix = `refs/tags/ringi/${genesis}/`, backup = `refs/ringi/legacy/${genesis}/signals/`;
317
- const old = new Map(this.refsUnder(prefix)), saved = new Map(this.refsUnder(backup));
318
- const liveTags = new Map(this.refsUnder('refs/tags/'));
319
- const refs = [];
320
- const changes = new Map();
321
- const identities = new Map();
322
- const targets = new Set(this.git(['rev-list', ...tips, `^${genesis}`]).trim().split('\n').filter(Boolean));
323
- const kept = (sha) => tips.some(tip => this.extends(sha, tip));
324
- for (const [ref, sha] of this.refsUnder(`refs/ringi/accepted/${genesis}/`)) {
325
- if (!kept(sha))
326
- fail(`ALARM ${ref} lost accepted history; migration stopped`);
327
- }
328
- for (const suffix of new Set([...old.keys()].map(r => r.slice(prefix.length)).concat([...saved.keys()].map(r => r.slice(backup.length))))) {
329
- const before = saved.get(backup + suffix) ?? old.get(prefix + suffix);
330
- const liveOld = old.get(prefix + suffix), recorded = saved.get(backup + suffix);
331
- if (recorded && liveOld && recorded !== liveOld)
332
- fail(`ALARM signal ${prefix + suffix} changed after migration; stopped`);
333
- const m = /^([^/]+)\/((?:[0-9a-f]{40}|[0-9a-f]{64}))\.([a-z][a-z0-9.-]*)$/.exec(suffix);
334
- if (!m || !AGENT_RE.test(m[1]))
335
- return fail(`ALARM signal ${prefix + suffix} cannot be migrated to an unambiguous agent id`);
336
- const [, actor, target, signal] = m;
337
- if (this.git(['cat-file', '-t', before]).trim() !== 'tag')
338
- fail(`ALARM signal ${prefix + suffix} is not annotated`);
339
- const text = this.git(['cat-file', 'tag', before]);
340
- const split = text.indexOf('\n\n'), header = text.slice(0, split), body = text.slice(split + 2);
341
- const h = /^object ([0-9a-f]+)\ntype commit\ntag ([^\n]+)\n(tagger ([^<\n]+) <[^\n]*> \d+ [+-]\d{4})$/.exec(header);
342
- if (!h || h[1] !== target || h[2] !== (prefix + suffix).slice('refs/tags/'.length) || h[4].trim() !== actor || !targets.has(target))
343
- return fail(`ALARM signal ${prefix + suffix} disagrees with its name, actor or room target`);
344
- if (body.includes('-----BEGIN'))
345
- fail('signed legacy signals need their authors to sign the new names; migration stopped');
346
- const p = parse(body, s => this.isRef(s));
347
- for (const [k, value] of [['room', room], ['genesis', genesis], ['actor', actor], ['message', target], ['signal', signal]]) {
348
- if (p.trailers[k]?.length !== 1 || p.trailers[k][0] !== value)
349
- fail(`ALARM signal ${prefix + suffix} has inconsistent legacy metadata`);
350
- }
351
- const key = `${actor}.${signal}.${target.slice(0, 12)}`, ref = `refs/tags/${key}`;
352
- const after = this.git(['mktag'], { input: `object ${target}\ntype commit\ntag ${key}\n${h[3]}\n\n` }).trim();
353
- const existing = changes.get(key), live = liveTags.get(ref);
354
- if ((existing && existing.after !== after) || (live && live !== after))
355
- fail(`ALARM signal ${key} collides with another tag; migration stopped`);
356
- if (recorded && !live)
357
- fail(`ALARM migrated signal ${key} was removed; migration stopped`);
358
- changes.set(key, { before, after });
359
- identities.set(before, { key, target, signal });
360
- identities.set(after, { key, target, signal });
361
- if (!recorded)
362
- refs.push(`create ${backup + suffix} ${before}`);
363
- if (!live)
364
- refs.push(`create ${ref} ${after}`);
365
- if (liveOld)
366
- refs.push(`delete ${prefix + suffix} ${liveOld}`);
367
- }
368
- const planned = readers.map(file => {
369
- const text = fs.readFileSync(file, 'utf8');
370
- const st = strictJson(text, file);
371
- if (typeof st.control === 'string' && !kept(st.control))
372
- fail('ALARM room control lost accepted history; migration stopped');
373
- for (const sha of Object.values(st.heads ?? {})) {
374
- if (!kept(sha))
375
- fail('ALARM room log lost accepted history; migration stopped');
376
- }
377
- const next = {};
378
- for (const [original, object] of Object.entries(st.signals ?? {})) {
379
- const m = /(?:^|\/)([^/]+)\/((?:[0-9a-f]{40}|[0-9a-f]{64}))\.([a-z][a-z0-9.-]*)$/.exec(original);
380
- // An early reader shortened a full ref to its last component and lost the actor. Its pinned object still
381
- // proves the identity: recover it only from an exact, verified old object retained by this migration.
382
- const bare = /^((?:[0-9a-f]{40}|[0-9a-f]{64}))\.([a-z][a-z0-9.-]*)$/.exec(original);
383
- const identity = bare ? identities.get(object) : undefined;
384
- if (bare && (!identity || identity.target !== bare[1] || identity.signal !== bare[2]))
385
- fail(`ALARM accepted signal ${original} lost its verified identity; migration stopped`);
386
- const key = m ? `${m[1]}.${m[3]}.${m[2].slice(0, 12)}` : identity?.key ?? original;
387
- const change = changes.get(key);
388
- if (m && !change)
389
- fail(`ALARM accepted signal ${original} is missing; migration stopped`);
390
- if (!change && /^[a-z0-9]+(?:[_-][a-z0-9]+)*\.[a-z][a-z0-9.-]*\.[0-9a-f]{12}$/.test(key)
391
- && liveTags.get(`refs/tags/${key}`) !== object)
392
- fail(`ALARM accepted signal ${original} is missing or replaced; migration stopped`);
393
- if (change && object !== change.before && object !== change.after)
394
- fail(`ALARM accepted signal ${original} was replaced; migration stopped`);
395
- const value = change?.after ?? object;
396
- if (next[key] && next[key] !== value)
397
- fail(`ALARM accepted signal ${key} has conflicting objects; migration stopped`);
398
- next[key] = value;
399
- }
400
- return { file, text, st, next, changed: JSON.stringify(st.signals ?? {}) !== JSON.stringify(next) };
401
- });
402
- return () => {
403
- if (refs.length)
404
- this.git(['update-ref', '--stdin'], { input: ['start', ...refs, 'prepare', 'commit', ''].join('\n') });
405
- for (const p of planned) {
406
- if (!p.changed)
407
- continue;
408
- // Separate from the live readers directory so the snapshot is never mistaken for another reader.
409
- const dir = path.join(this.home, 'migration', this.d.name, genesis, 'readers');
410
- mkdirIn(this.home, dir);
411
- const snapshot = path.join(dir, `${path.basename(p.file)}.${crypto.createHash('sha256').update(p.text).digest('hex')}`);
412
- if (!fs.existsSync(snapshot))
413
- fs.writeFileSync(snapshot, p.text, { flag: 'wx', mode: 0o600 });
414
- else if (fs.readFileSync(snapshot, 'utf8') !== p.text)
415
- fail('ALARM migration reader snapshot changed; migration stopped');
416
- writeJsonAtomic(p.file, { ...p.st, signals: p.next });
417
- }
418
- };
419
- }
420
- // A control record on top of the branch: the control files come from the last control record, never from the tip,
421
- // and the messages' files are kept.
422
- updateControl(room, who, text, files) {
423
- const kept = [...CONTROL_FILES].flatMap(n => { const c = this.controlFile(room, n); return c === null ? [] : [[n, c]]; });
424
- const all = { ...Object.fromEntries(kept), ...files };
425
- return this.onTop(room, who, text, Object.entries(all).map(([p, c]) => ({ path: p, content: c, mode: '100644' })), [...CONTROL_FILES]);
426
- }
427
- refsUnder(prefix) {
428
- return this.git(['for-each-ref', '--format=%(refname) %(objectname)', prefix]).split('\n').filter(Boolean).map(l => l.split(' '));
429
- }
430
- // The branch moves under the archive; its signals stay where they are, on its commits.
431
- archiveRoom(room, as) {
432
- const tip = this.rev(this.ref(room.name)) ?? fail(`room ${room.name} is gone`);
433
- this.git(['update-ref', `refs/archive/${as}`, tip, ZERO]);
434
- this.git(['update-ref', '-d', this.ref(room.name), tip]);
435
- }
436
- // Every ref of the room in one transaction: its branch (or archive) and a pending migration, every tag on its
437
- // commits (signals, ringi 0.1's, anyone's), what the store keeps under its genesis (pins, ringi 0.1's refs, evidence)
438
- // and its unpublished commits; then the migration's backup of its readers. Objects are left to the repository.
439
- deleteRoom(room) {
440
- const commits = this.commitsOf(room);
441
- const tags = this.git(['for-each-ref', '--format=%(refname) %(objectname) %(*objectname)', 'refs/tags/']).split('\n').filter(Boolean)
442
- .map(l => l.split(' ')).filter(([ref, object, target]) => commits.has(target || object) || ref.startsWith(`refs/tags/ringi/${room.genesis}/`)).map(([ref]) => ref);
443
- const kept = this.refsUnder('refs/ringi/').map(([ref]) => ref).filter(ref => {
444
- const [, , kind, key] = ref.split('/');
445
- return key === room.genesis || (kind === 'unpublished' && commits.has(key));
446
- });
447
- const refs = [this.ref(room.name), this.transition(room.name)].filter(r => this.rev(r)).concat(tags, kept);
448
- this.git(['update-ref', '--stdin'], { input: ['start', ...refs.map(r => `delete ${r}`), 'prepare', 'commit', ''].join('\n') });
449
- fs.rmSync(path.join(this.home, 'migration', this.d.name, room.genesis), { recursive: true, force: true });
450
- }
451
- remote(room) {
452
- return { name: room.name, ref: this.ref(room.name), head: room.head, genesis: room.genesis, archived: room.archived };
453
- }
454
- roster(room) {
455
- if (this.trust !== 'signed' || room.archived)
456
- return null;
457
- const v = Remote.verifyRoom(this.h, this.remote(room));
458
- return { members: v.members, signers: v.entries };
459
- }
460
- // --- logs ---
461
- // Logs are read at the branch's live tip, as a reader sees the room now.
462
- live(room) {
463
- const tip = this.tip(room.name);
464
- return tip ? this.entries(room, tip).filter(e => !e.control && !e.skipped) : [];
465
- }
466
- // What a signed hub skips in the room's branch, as alarms; the room reads on without it.
467
- alarms(room) {
468
- const tip = this.tip(room.name);
469
- return tip ? this.entries(room, tip).filter(e => e.skipped).map(e => `commit ${e.sha.slice(0, 12)} by ${e.author || '(none)'}: ${e.skipped}`) : [];
470
- }
471
- logIds(room) { return [...new Set(this.live(room).map(e => e.author))]; }
472
- logHead(room, id) { return this.live(room).filter(e => e.author === id).at(-1)?.sha ?? null; }
473
- // One author's messages after the genesis up to `head`, oldest first, read as bytes: the text is the commit message,
474
- // the files are the explicit Ringi-Files list, or the paths the commit adds or changes for records without it.
475
- messages(room, id, head) {
476
- const tip = this.trust === 'signed' ? this.tip(room.name) : null;
477
- const skipped = new Set(tip ? this.entries(room, tip).filter(e => e.skipped).map(e => e.sha) : []);
478
- const raw = runGitRaw(this.args(['log', '--topo-order', '--reverse', '--no-renames', '--raw', '--no-abbrev', '--format=%x1e%H%x1f%P%x1f%ct%x1f', head, `^${room.genesis}`]), this.home);
479
- const records = splitBuf(raw, 0x1e).filter(b => b.length).map(rec => splitBuf(rec, 0x1f));
480
- const commits = new Map(Remote.readCommits(this.h, records.map(([sha]) => sha.toString())).map(c => [c.sha, c]));
481
- return records.flatMap(([sha, parent, ct, rest = Buffer.alloc(0)]) => {
482
- const { author, body } = commits.get(sha.toString());
483
- if (author !== id || this.isControl(room, sha.toString(), id, body.toString('utf8')) || skipped.has(sha.toString()))
484
- return [];
485
- let files = rest.toString('utf8').split('\n').map(l => /^:\d{6} (\d{6}) \S+ \S+ ([A-Z])\d*\t(.*)$/.exec(l)).filter((x) => !!x)
486
- .filter(([, , status]) => status !== 'D').map(([, mode, , p]) => ({ path: p, mode }));
487
- const declared = parse(body.toString('utf8'), s => this.isRef(s)).trailers['ringi-files'];
488
- const refused = [];
489
- if (declared) {
490
- let names = [];
491
- try {
492
- const paths = JSON.parse(declared[0]);
493
- if (declared.length !== 1 || !Array.isArray(paths) || !paths.every(p => typeof p === 'string'))
494
- fail('invalid attachment list');
495
- names = paths.map(checkPath);
496
- if (new Set(names).size !== names.length)
497
- fail('duplicate attachment path');
498
- checkCollisions(names);
499
- }
500
- catch {
501
- refused.push({ path: 'Ringi-Files', mode: 'invalid', why: 'expected one JSON array of safe, distinct file paths' });
502
- }
503
- const wanted = new Set(names);
504
- files = names.length && !refused.length ? splitBuf(runGitRaw(this.args(['ls-tree', '-r', '-z', sha.toString()]), this.home), 0)
505
- .map(b => /^(\d+) \w+ [0-9a-f]+\t(.*)$/s.exec(b.toString('utf8'))).filter((x) => !!x)
506
- .map(([, mode, p]) => ({ path: p, mode })).filter(f => wanted.has(f.path)) : [];
507
- for (const f of files)
508
- wanted.delete(f.path);
509
- if (!refused.length)
510
- for (const p of wanted)
511
- files.push({ path: p, mode: 'missing' });
512
- }
513
- return [{ id: sha.toString(), parent: parent.toString() || null, time: Number(ct.toString()), text: body,
514
- files: files.filter(f => REGULAR.has(f.mode)), refused: [...refused, ...files.filter(f => !REGULAR.has(f.mode))] }];
515
- });
516
- }
517
- blob(file) { return this.git(['hash-object', '-w', '--', file]).trim(); }
518
- // One message on top of the room's branch.
519
- append(room, who, text, entries) {
520
- if (room.archived)
521
- fail(`room ${room.name} is archived: read-only`);
522
- if (checkOutgoing(text, s => this.isRef(s)).trailers['ringi-files'])
523
- fail('Ringi-Files is store metadata; supply attachments instead');
524
- const own = entries.find(e => CONTROL_FILES.has(e.path));
525
- if (own)
526
- fail(`${own.path} is the room's own file; attach it under another path`);
527
- if (entries.length)
528
- text = `${text.trimEnd()}\nRingi-Files: ${JSON.stringify([...new Set(entries.map(e => e.path))])}\n`;
529
- if (Buffer.byteLength(text) > TEXT_MAX)
530
- fail('attachment list makes the message exceed 64 KiB; use fewer or shorter paths');
531
- checkOutgoing(text, s => this.isRef(s));
532
- return this.onTop(room, who, text, entries);
533
- }
534
- // A commit on the branch's tip, compare-and-swap: when another writer got there first, it is made again on the new tip.
535
- onTop(room, who, text, entries, drop = []) {
536
- const ref = this.ref(room.name);
537
- for (let i = 0; i < 8; i++) {
538
- const tip = this.rev(ref) ?? fail(`room ${room.name} is gone`);
539
- if (drop.length && this.room(room.name)?.head !== room.head)
540
- fail('room control moved while writing; retry the operation');
541
- const tree = entries.length || drop.length ? this.tree(`${tip}^{tree}`, entries, drop) : this.git(['rev-parse', `${tip}^{tree}`]).trim();
542
- const sha = this.commit(who, text, tree, [tip]);
543
- if (this.advance(ref, sha, tip))
544
- return sha;
545
- }
546
- return fail(`room ${room.name} kept moving while writing; retry`);
547
- }
548
- verifyLog(room, id, head) {
549
- return this.trust === 'signed' ? Remote.verify(this.h, this.remote(room), id, head) : null;
550
- }
551
- pin(room, reader, id, rev) {
552
- if (!room.archived)
553
- this.git(['update-ref', `refs/ringi/accepted/${room.genesis}/${reader}/${id}`, rev]);
554
- }
555
- unpin(room, reader, id) {
556
- this.gitOk(['update-ref', '-d', `refs/ringi/accepted/${room.genesis}/${reader}/${id}`]);
557
- }
558
- file(rev, p) { return runGitRaw(this.args(['show', `${rev}:${p}`]), this.home); }
559
- sizes(rev, paths) {
560
- if (!paths.length)
561
- return new Map();
562
- const wanted = new Set(paths);
563
- return new Map(splitBuf(runGitRaw(this.args(['ls-tree', '-r', '-l', '-z', rev]), this.home), 0)
564
- .map(b => /^\d+ blob [0-9a-f]+ +(\d+)\t(.*)$/s.exec(b.toString('utf8'))).filter((x) => !!x)
565
- .filter(([, , p]) => wanted.has(p)).map(([, n, p]) => [p, Number(n)]));
566
- }
567
- // --- signals ---
568
- // An annotated tag without text in its author's namespace, on the message's commit, signed on a signed hub. The
569
- // message's id in the name keeps it unique, so writing the same signal twice is a no-op.
570
- signal(room, who, target, name) {
571
- const tag = `${who}.${name}`;
572
- const ref = `refs/tags/${tag}`;
573
- if (this.gitOk(['rev-parse', '-q', '--verify', ref]).status === 0) {
574
- Remote.push(this.h, ref);
575
- return;
576
- }
577
- const sign = Remote.sign(this.h, who, this.home);
578
- const key = sign[0]?.slice(2);
579
- this.git([...(key ? ['-c', `user.signingkey=${key}`] : []), 'tag', key ? '-s' : '-a', '-F', '-', tag, target], { input: '', env: ident(who) });
580
- Remote.push(this.h, ref);
581
- }
582
- // The tags named `<actor>.<signal>.<message12>`, with the objects they name.
583
- tags() {
584
- return this.git(['for-each-ref', '--format=%(refname) %(objectname) %(objecttype)', 'refs/tags/']).split('\n').flatMap(line => {
585
- const [ref, object, type] = line.split(' ');
586
- const m = /^refs\/tags\/([a-z0-9]+(?:[_-][a-z0-9]+)*)\.([a-z][a-z0-9.-]*\.[0-9a-f]{12})$/.exec(ref ?? '');
587
- return m ? [{ ref, actor: m[1], name: m[2], object, type }] : [];
588
- });
589
- }
590
- // Annotated tag objects read whole, size-framed: their target, tagger and text are never split on bytes they hold.
591
- tagObjects(shas) {
592
- const out = new Map();
593
- if (!shas.length)
594
- return out;
595
- const raw = runGitRaw(this.args(['cat-file', '--batch']), this.home, Buffer.from(shas.join('\n') + '\n'));
596
- let at = 0;
597
- for (const sha of shas) {
598
- const nl = raw.indexOf(0x0a, at);
599
- const [got, type, size] = raw.subarray(at, nl).toString('utf8').split(' ');
600
- const body = raw.subarray(nl + 1, nl + 1 + Number(size));
601
- at = nl + 1 + Number(size) + 1;
602
- if (got !== sha || type !== 'tag')
603
- continue;
604
- const split = body.indexOf('\n\n');
605
- const hdr = body.subarray(0, split < 0 ? body.length : split).toString('utf8');
606
- const text = split < 0 ? '' : body.subarray(split + 2).toString('utf8').split('-----BEGIN SSH SIGNATURE-----')[0];
607
- const target = /^object ([0-9a-f]+)\ntype commit$/m.exec(hdr)?.[1] ?? '';
608
- out.set(sha, { target, tagger: /^tagger (.*?) <[^>\n]*> \d+ [+-]\d{4}$/m.exec(hdr)?.[1] ?? '', text });
609
- }
610
- return out;
611
- }
612
- // Every commit of the room's branch: a signal belongs to the room whose branch holds its message.
613
- commitsOf(room) {
614
- const tip = this.tip(room.name);
615
- return new Set(tip ? this.entries(room, tip).map(e => e.sha) : []);
616
- }
617
- signals(room) {
618
- const tags = this.tags(), objects = this.tagObjects(tags.filter(t => t.type === 'tag').map(t => t.object));
619
- const mine = this.commitsOf(room);
620
- const records = tags.flatMap(t => {
621
- const o = objects.get(t.object);
622
- // Only an annotated tag on a commit is a signal; anything else points nowhere.
623
- if (!mine.has(o?.target || t.object))
624
- return [];
625
- return [{ ref: t.ref, object: t.object, name: t.name, actor: t.actor, target: o?.target ?? '', tagger: o?.tagger ?? '', text: o?.text ?? '' }];
626
- });
627
- if (this.trust !== 'signed' || room.archived)
628
- return records;
629
- return Remote.withVerification(this.h, () => records.filter(s => {
630
- const why = Remote.verifySignal(this.h, this.remote(room), s.ref, s.object);
631
- if (why)
632
- note(`ALARM signal ${s.ref}: ${why}; quarantined and withheld`);
633
- return !why;
634
- }));
635
- }
636
- // --- signed stores ---
637
- admit(room, who, key) { return Remote.roomAdmit(this.h, this.remote(room), who, key, this.home); }
638
- joinKey(room, who) { return Remote.join(this.h, this.remote(room), who, this.home); }
639
- // A room's transport alarm is the room's, whichever author the operator forgets.
640
- forgetLog(room, _id) { Remote.forget(this.h, this.remote(room)); }
641
- rootKey() { return this.d.root ?? fail(`hub ${this.d.name} has no pinned root`); }
642
- rootRoster() { return Remote.rootRoster(this.h); }
643
- }
644
- //# sourceMappingURL=index.js.map