@smthrs/sync 0.0.0-stage → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +21 -0
  3. package/README.md +208 -2
  4. package/dist/cjs/BranchCommands.d.ts +228 -0
  5. package/dist/cjs/BranchCommands.d.ts.map +1 -0
  6. package/dist/cjs/BranchCommands.js +316 -0
  7. package/dist/cjs/BranchCommands.js.map +7 -0
  8. package/dist/cjs/BranchIds.d.ts +65 -0
  9. package/dist/cjs/BranchIds.d.ts.map +1 -0
  10. package/dist/cjs/BranchIds.js +53 -0
  11. package/dist/cjs/BranchIds.js.map +7 -0
  12. package/dist/cjs/BranchPresence.d.ts +195 -0
  13. package/dist/cjs/BranchPresence.d.ts.map +1 -0
  14. package/dist/cjs/BranchPresence.js +276 -0
  15. package/dist/cjs/BranchPresence.js.map +7 -0
  16. package/dist/cjs/BranchProjection.d.ts +179 -0
  17. package/dist/cjs/BranchProjection.d.ts.map +1 -0
  18. package/dist/cjs/BranchProjection.js +197 -0
  19. package/dist/cjs/BranchProjection.js.map +7 -0
  20. package/dist/cjs/BranchProtocol.d.ts +415 -0
  21. package/dist/cjs/BranchProtocol.d.ts.map +1 -0
  22. package/dist/cjs/BranchProtocol.js +172 -0
  23. package/dist/cjs/BranchProtocol.js.map +7 -0
  24. package/dist/cjs/BranchRpcs.d.ts +177 -0
  25. package/dist/cjs/BranchRpcs.d.ts.map +1 -0
  26. package/dist/cjs/BranchRpcs.js +85 -0
  27. package/dist/cjs/BranchRpcs.js.map +7 -0
  28. package/dist/cjs/BranchServer.d.ts +55 -0
  29. package/dist/cjs/BranchServer.d.ts.map +1 -0
  30. package/dist/cjs/BranchServer.js +147 -0
  31. package/dist/cjs/BranchServer.js.map +7 -0
  32. package/dist/cjs/BranchShare.d.ts +159 -0
  33. package/dist/cjs/BranchShare.d.ts.map +1 -0
  34. package/dist/cjs/BranchShare.js +147 -0
  35. package/dist/cjs/BranchShare.js.map +7 -0
  36. package/dist/cjs/RunCatalog.d.ts +207 -0
  37. package/dist/cjs/RunCatalog.d.ts.map +1 -0
  38. package/dist/cjs/RunCatalog.js +122 -0
  39. package/dist/cjs/RunCatalog.js.map +7 -0
  40. package/dist/cjs/SyncAuth.d.ts +70 -0
  41. package/dist/cjs/SyncAuth.d.ts.map +1 -0
  42. package/dist/cjs/SyncAuth.js +109 -0
  43. package/dist/cjs/SyncAuth.js.map +7 -0
  44. package/dist/cjs/SyncClient.d.ts +274 -0
  45. package/dist/cjs/SyncClient.d.ts.map +1 -0
  46. package/dist/cjs/SyncClient.js +474 -0
  47. package/dist/cjs/SyncClient.js.map +7 -0
  48. package/dist/cjs/SyncError.d.ts +107 -0
  49. package/dist/cjs/SyncError.d.ts.map +1 -0
  50. package/dist/cjs/SyncError.js +110 -0
  51. package/dist/cjs/SyncError.js.map +7 -0
  52. package/dist/cjs/SyncPrincipal.d.ts +100 -0
  53. package/dist/cjs/SyncPrincipal.d.ts.map +1 -0
  54. package/dist/cjs/SyncPrincipal.js +48 -0
  55. package/dist/cjs/SyncPrincipal.js.map +7 -0
  56. package/dist/cjs/SyncProtocol.d.ts +592 -0
  57. package/dist/cjs/SyncProtocol.d.ts.map +1 -0
  58. package/dist/cjs/SyncProtocol.js +165 -0
  59. package/dist/cjs/SyncProtocol.js.map +7 -0
  60. package/dist/cjs/SyncRpcs.d.ts +92 -0
  61. package/dist/cjs/SyncRpcs.d.ts.map +1 -0
  62. package/dist/cjs/SyncRpcs.js +35 -0
  63. package/dist/cjs/SyncRpcs.js.map +7 -0
  64. package/dist/cjs/SyncServer.d.ts +184 -0
  65. package/dist/cjs/SyncServer.d.ts.map +1 -0
  66. package/dist/cjs/SyncServer.js +513 -0
  67. package/dist/cjs/SyncServer.js.map +7 -0
  68. package/dist/cjs/WorkspaceShare.d.ts +174 -0
  69. package/dist/cjs/WorkspaceShare.d.ts.map +1 -0
  70. package/dist/cjs/WorkspaceShare.js +149 -0
  71. package/dist/cjs/WorkspaceShare.js.map +7 -0
  72. package/dist/cjs/index.d.ts +91 -0
  73. package/dist/cjs/index.d.ts.map +1 -0
  74. package/dist/cjs/index.js +67 -0
  75. package/dist/cjs/index.js.map +7 -0
  76. package/dist/cjs/internal/Admission.d.ts +43 -0
  77. package/dist/cjs/internal/Admission.d.ts.map +1 -0
  78. package/dist/cjs/internal/Admission.js +90 -0
  79. package/dist/cjs/internal/Admission.js.map +7 -0
  80. package/dist/cjs/internal/CauseText.d.ts +55 -0
  81. package/dist/cjs/internal/CauseText.d.ts.map +1 -0
  82. package/dist/cjs/internal/CauseText.js +38 -0
  83. package/dist/cjs/internal/CauseText.js.map +7 -0
  84. package/dist/cjs/internal/JournalErrorCode.d.ts +23 -0
  85. package/dist/cjs/internal/JournalErrorCode.d.ts.map +1 -0
  86. package/dist/cjs/internal/JournalErrorCode.js +34 -0
  87. package/dist/cjs/internal/JournalErrorCode.js.map +7 -0
  88. package/dist/cjs/internal/PolicyOptions.d.ts +57 -0
  89. package/dist/cjs/internal/PolicyOptions.d.ts.map +1 -0
  90. package/dist/cjs/internal/PolicyOptions.js +62 -0
  91. package/dist/cjs/internal/PolicyOptions.js.map +7 -0
  92. package/dist/cjs/internal/ProtocolVersion.d.ts +17 -0
  93. package/dist/cjs/internal/ProtocolVersion.d.ts.map +1 -0
  94. package/dist/cjs/internal/ProtocolVersion.js +44 -0
  95. package/dist/cjs/internal/ProtocolVersion.js.map +7 -0
  96. package/dist/cjs/internal/ShareSigner.d.ts +173 -0
  97. package/dist/cjs/internal/ShareSigner.d.ts.map +1 -0
  98. package/dist/cjs/internal/ShareSigner.js +150 -0
  99. package/dist/cjs/internal/ShareSigner.js.map +7 -0
  100. package/dist/cjs/internal/SnapshotBoundary.d.ts +17 -0
  101. package/dist/cjs/internal/SnapshotBoundary.d.ts.map +1 -0
  102. package/dist/cjs/internal/SnapshotBoundary.js +97 -0
  103. package/dist/cjs/internal/SnapshotBoundary.js.map +7 -0
  104. package/dist/cjs/package.json +1 -0
  105. package/dist/cjs/test/TestSocket.d.ts +55 -0
  106. package/dist/cjs/test/TestSocket.d.ts.map +1 -0
  107. package/dist/cjs/test/TestSocket.js +145 -0
  108. package/dist/cjs/test/TestSocket.js.map +7 -0
  109. package/dist/cjs/test/TestSync.d.ts +54 -0
  110. package/dist/cjs/test/TestSync.d.ts.map +1 -0
  111. package/dist/cjs/test/TestSync.js +96 -0
  112. package/dist/cjs/test/TestSync.js.map +7 -0
  113. package/dist/esm/BranchCommands.d.ts +228 -0
  114. package/dist/esm/BranchCommands.d.ts.map +1 -0
  115. package/dist/esm/BranchCommands.js +496 -0
  116. package/dist/esm/BranchCommands.js.map +1 -0
  117. package/dist/esm/BranchIds.d.ts +65 -0
  118. package/dist/esm/BranchIds.d.ts.map +1 -0
  119. package/dist/esm/BranchIds.js +61 -0
  120. package/dist/esm/BranchIds.js.map +1 -0
  121. package/dist/esm/BranchPresence.d.ts +195 -0
  122. package/dist/esm/BranchPresence.d.ts.map +1 -0
  123. package/dist/esm/BranchPresence.js +361 -0
  124. package/dist/esm/BranchPresence.js.map +1 -0
  125. package/dist/esm/BranchProjection.d.ts +179 -0
  126. package/dist/esm/BranchProjection.d.ts.map +1 -0
  127. package/dist/esm/BranchProjection.js +269 -0
  128. package/dist/esm/BranchProjection.js.map +1 -0
  129. package/dist/esm/BranchProtocol.d.ts +415 -0
  130. package/dist/esm/BranchProtocol.d.ts.map +1 -0
  131. package/dist/esm/BranchProtocol.js +323 -0
  132. package/dist/esm/BranchProtocol.js.map +1 -0
  133. package/dist/esm/BranchRpcs.d.ts +177 -0
  134. package/dist/esm/BranchRpcs.d.ts.map +1 -0
  135. package/dist/esm/BranchRpcs.js +103 -0
  136. package/dist/esm/BranchRpcs.js.map +1 -0
  137. package/dist/esm/BranchServer.d.ts +55 -0
  138. package/dist/esm/BranchServer.d.ts.map +1 -0
  139. package/dist/esm/BranchServer.js +168 -0
  140. package/dist/esm/BranchServer.js.map +1 -0
  141. package/dist/esm/BranchShare.d.ts +159 -0
  142. package/dist/esm/BranchShare.d.ts.map +1 -0
  143. package/dist/esm/BranchShare.js +213 -0
  144. package/dist/esm/BranchShare.js.map +1 -0
  145. package/dist/esm/RunCatalog.d.ts +207 -0
  146. package/dist/esm/RunCatalog.d.ts.map +1 -0
  147. package/dist/esm/RunCatalog.js +196 -0
  148. package/dist/esm/RunCatalog.js.map +1 -0
  149. package/dist/esm/SyncAuth.d.ts +70 -0
  150. package/dist/esm/SyncAuth.d.ts.map +1 -0
  151. package/dist/esm/SyncAuth.js +119 -0
  152. package/dist/esm/SyncAuth.js.map +1 -0
  153. package/dist/esm/SyncClient.d.ts +274 -0
  154. package/dist/esm/SyncClient.d.ts.map +1 -0
  155. package/dist/esm/SyncClient.js +586 -0
  156. package/dist/esm/SyncClient.js.map +1 -0
  157. package/dist/esm/SyncError.d.ts +107 -0
  158. package/dist/esm/SyncError.d.ts.map +1 -0
  159. package/dist/esm/SyncError.js +133 -0
  160. package/dist/esm/SyncError.js.map +1 -0
  161. package/dist/esm/SyncPrincipal.d.ts +100 -0
  162. package/dist/esm/SyncPrincipal.d.ts.map +1 -0
  163. package/dist/esm/SyncPrincipal.js +58 -0
  164. package/dist/esm/SyncPrincipal.js.map +1 -0
  165. package/dist/esm/SyncProtocol.d.ts +592 -0
  166. package/dist/esm/SyncProtocol.d.ts.map +1 -0
  167. package/dist/esm/SyncProtocol.js +434 -0
  168. package/dist/esm/SyncProtocol.js.map +1 -0
  169. package/dist/esm/SyncRpcs.d.ts +92 -0
  170. package/dist/esm/SyncRpcs.d.ts.map +1 -0
  171. package/dist/esm/SyncRpcs.js +35 -0
  172. package/dist/esm/SyncRpcs.js.map +1 -0
  173. package/dist/esm/SyncServer.d.ts +184 -0
  174. package/dist/esm/SyncServer.d.ts.map +1 -0
  175. package/dist/esm/SyncServer.js +897 -0
  176. package/dist/esm/SyncServer.js.map +1 -0
  177. package/dist/esm/WorkspaceShare.d.ts +174 -0
  178. package/dist/esm/WorkspaceShare.d.ts.map +1 -0
  179. package/dist/esm/WorkspaceShare.js +208 -0
  180. package/dist/esm/WorkspaceShare.js.map +1 -0
  181. package/dist/esm/index.d.ts +91 -0
  182. package/dist/esm/index.d.ts.map +1 -0
  183. package/dist/esm/index.js +91 -0
  184. package/dist/esm/index.js.map +1 -0
  185. package/dist/esm/internal/Admission.d.ts +43 -0
  186. package/dist/esm/internal/Admission.d.ts.map +1 -0
  187. package/dist/esm/internal/Admission.js +80 -0
  188. package/dist/esm/internal/Admission.js.map +1 -0
  189. package/dist/esm/internal/CauseText.d.ts +55 -0
  190. package/dist/esm/internal/CauseText.d.ts.map +1 -0
  191. package/dist/esm/internal/CauseText.js +67 -0
  192. package/dist/esm/internal/CauseText.js.map +1 -0
  193. package/dist/esm/internal/JournalErrorCode.d.ts +23 -0
  194. package/dist/esm/internal/JournalErrorCode.d.ts.map +1 -0
  195. package/dist/esm/internal/JournalErrorCode.js +44 -0
  196. package/dist/esm/internal/JournalErrorCode.js.map +1 -0
  197. package/dist/esm/internal/PolicyOptions.d.ts +57 -0
  198. package/dist/esm/internal/PolicyOptions.d.ts.map +1 -0
  199. package/dist/esm/internal/PolicyOptions.js +70 -0
  200. package/dist/esm/internal/PolicyOptions.js.map +1 -0
  201. package/dist/esm/internal/ProtocolVersion.d.ts +17 -0
  202. package/dist/esm/internal/ProtocolVersion.d.ts.map +1 -0
  203. package/dist/esm/internal/ProtocolVersion.js +24 -0
  204. package/dist/esm/internal/ProtocolVersion.js.map +1 -0
  205. package/dist/esm/internal/ShareSigner.d.ts +173 -0
  206. package/dist/esm/internal/ShareSigner.d.ts.map +1 -0
  207. package/dist/esm/internal/ShareSigner.js +219 -0
  208. package/dist/esm/internal/ShareSigner.js.map +1 -0
  209. package/dist/esm/internal/SnapshotBoundary.d.ts +17 -0
  210. package/dist/esm/internal/SnapshotBoundary.d.ts.map +1 -0
  211. package/dist/esm/internal/SnapshotBoundary.js +68 -0
  212. package/dist/esm/internal/SnapshotBoundary.js.map +1 -0
  213. package/dist/esm/test/TestSocket.d.ts +55 -0
  214. package/dist/esm/test/TestSocket.d.ts.map +1 -0
  215. package/dist/esm/test/TestSocket.js +128 -0
  216. package/dist/esm/test/TestSocket.js.map +1 -0
  217. package/dist/esm/test/TestSync.d.ts +54 -0
  218. package/dist/esm/test/TestSync.d.ts.map +1 -0
  219. package/dist/esm/test/TestSync.js +76 -0
  220. package/dist/esm/test/TestSync.js.map +1 -0
  221. package/docs/README.md +156 -0
  222. package/docs/api.md +328 -0
  223. package/docs/concepts/authorization.md +117 -0
  224. package/docs/concepts/branches.md +195 -0
  225. package/docs/concepts/compaction.md +144 -0
  226. package/docs/concepts/replay-then-follow.md +129 -0
  227. package/docs/concepts/scopes-and-cursors.md +135 -0
  228. package/docs/guides/authorize-a-connection.md +174 -0
  229. package/docs/guides/follow-a-run.md +174 -0
  230. package/docs/guides/handle-a-compacted-run.md +158 -0
  231. package/docs/guides/list-workspace-runs.md +120 -0
  232. package/docs/guides/run-a-long-soak.md +80 -0
  233. package/docs/guides/serve-the-read-path.md +146 -0
  234. package/docs/guides/test-a-follower.md +273 -0
  235. package/docs/installation.md +92 -0
  236. package/docs/protocol.md +183 -0
  237. package/docs/quickstart.md +159 -0
  238. package/docs/troubleshooting.md +234 -0
  239. package/package.json +304 -3
  240. package/src/BranchCommands.ts +684 -0
  241. package/src/BranchIds.ts +76 -0
  242. package/src/BranchPresence.ts +512 -0
  243. package/src/BranchProjection.ts +308 -0
  244. package/src/BranchProtocol.ts +411 -0
  245. package/src/BranchRpcs.ts +163 -0
  246. package/src/BranchServer.ts +202 -0
  247. package/src/BranchShare.ts +297 -0
  248. package/src/RunCatalog.ts +308 -0
  249. package/src/SyncAuth.ts +146 -0
  250. package/src/SyncClient.ts +902 -0
  251. package/src/SyncError.ts +147 -0
  252. package/src/SyncPrincipal.ts +115 -0
  253. package/src/SyncProtocol.ts +594 -0
  254. package/src/SyncRpcs.ts +40 -0
  255. package/src/SyncServer.ts +1184 -0
  256. package/src/WorkspaceShare.ts +292 -0
  257. package/src/index.ts +107 -0
  258. package/src/internal/Admission.ts +119 -0
  259. package/src/internal/CauseText.ts +74 -0
  260. package/src/internal/JournalErrorCode.ts +46 -0
  261. package/src/internal/PolicyOptions.ts +97 -0
  262. package/src/internal/ProtocolVersion.ts +28 -0
  263. package/src/internal/ShareSigner.ts +300 -0
  264. package/src/internal/SnapshotBoundary.ts +91 -0
  265. package/src/test/TestSocket.ts +192 -0
  266. package/src/test/TestSync.ts +113 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,208 @@
1
+ # @smthrs/sync
2
+
3
+ ## Unreleased
4
+
5
+ ### Security
6
+
7
+ - **Breaking.** `BranchShare` and `WorkspaceShare` keyrings refuse a signing
8
+ secret shorter than 32 bytes of UTF-8 with `invalid_request`. One observed
9
+ capability let an attacker search a short secret offline and forge
10
+ capabilities.
11
+ - `BranchPresence` binds a live participant to the `capabilityId` that
12
+ announced it. Another capability's announce or leave for that participant is
13
+ refused with `unauthorized` until the lease lapses.
14
+ - `BranchCommands.submit` stamps the verified `capabilityId` on the durable
15
+ command payload, and `BranchProjection` carries it on every message, command,
16
+ and field, so a command is attributed to the capability that sent it rather
17
+ than to the `participantId` the client claimed.
18
+ - **Breaking.** `TestSync.layerWorkspaceAuth` is renamed
19
+ `TestSync.layerTrustAllAsOwner` so a composition that trusts every
20
+ connection as the owner says so where it is wired.
21
+
22
+ ## [1.0.0-rc.0] - 2026-09-01
23
+
24
+ ### Added
25
+
26
+ - Added workspace authentication: the `SyncAuth` RPC middleware and its
27
+ header-verifying implementation, the `WorkspaceShare` capability authority
28
+ over a `Redacted` keyring with `kid` rotation, and the per-request
29
+ `SyncPrincipal` whose default is anonymous.
30
+ - Added compaction recovery: `SyncProtocol.Resync`, the `compacted` error code
31
+ and `SyncError.resync`, and the client-side resync that moves a run's cursor
32
+ to the checkpoint and restarts instead of dying.
33
+ - Added `SubscribeOptions.onResync`, the seam a consumer restores checkpoint
34
+ state through. It runs before the cursor moves and must succeed, so a
35
+ follower that cannot fill the hole fails rather than skipping it silently.
36
+ - Added `SubscribeOptions.apply`, which advances a cursor only after the
37
+ consumer has applied the entry, so `RunCursor` can mean what its schema says.
38
+ - Added the bounded workspace fan-out: `SyncServer.Options.concurrency` and
39
+ `tailIntervalMs`, plus `RunCatalog.makePolling` for the durable run set a
40
+ workspace follower learns from.
41
+ - Added request limits `SyncProtocol.maxReadLimit` and `maxSubscribeCredit`,
42
+ the branch ledger bound `BranchCommands.defaultLedgerCapacity`, and the
43
+ roster bound `BranchPresence.defaultMaxParticipants`.
44
+ - Added golden wire vectors: the encoded form of every frame variant, a read
45
+ response, a command receipt, the capability header, and both authorities'
46
+ signatures are frozen as literals, so a renamed field or a changed signing
47
+ encoding fails a test rather than a deployed follower.
48
+
49
+ ### Changed
50
+
51
+ - **Breaking.** `SyncError.cause` is a bounded string rather than
52
+ `Schema.Unknown`. It is the declared error schema of every RPC, so it now
53
+ carries a rendering instead of the host object that failed; a journal
54
+ failure crosses as its stable journal code and never as the driver's own
55
+ message.
56
+ - **Breaking.** `BranchShare.makeHmac` and `layerHmac` take a `Redacted`
57
+ keyring rather than a single secret, `ShareClaims` carries the signing `kid`,
58
+ and the branch scheme signs a scheme label, now `v2`, so a branch signature
59
+ can no longer be replayed as a workspace signature under a shared secret and
60
+ the branch secret rotates the way the workspace secret already did: the
61
+ retired key stays in the keyring until its links expire. `BranchShare`
62
+ gained `layerConfig`, which reads `SMITHERS_SYNC_BRANCH_SECRET` and
63
+ `SMITHERS_SYNC_BRANCH_KEY_ID`. Outstanding branch capabilities do not verify
64
+ under this release.
65
+ - **Breaking.** The client's service tag is `SyncClient.SyncClient`, keyed
66
+ `@smthrs/sync/SyncClient`, matching every other service module.
67
+ `SyncClient.Sync` stays as a deprecated alias of the same tag for one
68
+ release.
69
+ - **Breaking.** `SubscribeOptions.apply` and `onResync` fail with the
70
+ consumer's own error types, and `subscribe` returns
71
+ `Stream<Entry, SyncError | SyncGapError | EApply | EResync>`. A consumer's
72
+ transaction failure no longer has to borrow a wire code such as `unknown` or
73
+ `compacted`, so it can no longer be mistaken for a transport or protocol
74
+ failure or pass `SyncError.is`.
75
+ - **Breaking.** `SyncProtocol.Progress` is
76
+ `{ delivered: WorkspaceCursor, applied: WorkspaceCursor }`. The
77
+ `DeliveredProgress` and `AppliedProgress` tagged wrappers are removed: they
78
+ never sat in a union, so their tags discriminated nothing. `Service.cursors`
79
+ is removed; read `progress.delivered` instead.
80
+ - **Breaking.** `BranchCommands.submission` returns
81
+ `Effect<CommandSubmission, SyncError>`. It decodes the fields it fills in, so
82
+ a command name the schema forbids is the same typed refusal `submit` returns
83
+ one call later rather than a throw at the caller.
84
+ - `BranchShare.mint` and `WorkspaceShare.mint` decode their request before
85
+ signing it. An empty `branchId` or `capabilityId` and a `ttlMs` of `0` or
86
+ `NaN` are refused with `invalid_request`; `ttlMs: 0` used to mint an
87
+ already-expired capability, and the others died where the declared type
88
+ promises a `SyncError`. `Branch.CreateBranch` decodes the id its `BranchIds`
89
+ port yields instead of branding it, so a host id source returning `""`
90
+ refuses rather than crashing the handler.
91
+ - Both authorities verify through one pipeline, `shareSigner.verifyClaims`, so
92
+ the signature check, the expiry boundary, and the read-only refusal cannot
93
+ drift apart between them. `ShareHardening` asserts each against both.
94
+ - **Breaking.** A subscription's `credit` must be between 1 and
95
+ `maxSubscribeCredit`, and a read's `limit` at most `maxReadLimit`. A cursor
96
+ set that names one run twice is refused with `invalid_request`.
97
+ - **Breaking.** `BranchRpcs`'s submit, announce, leave, and roster payloads are
98
+ the service schemas rather than copies of them, so `displayName: ""` is a
99
+ typed wire refusal instead of a defect raised inside the presence service.
100
+ - **Breaking.** The branch request schemas `SubmitRequest`, `Announcement`,
101
+ `RosterRequest`, and `LeaveRequest` live in `BranchProtocol` beside every
102
+ other branch wire shape, so `BranchRpcs` no longer imports the command ledger
103
+ or the presence registry. `BranchCommands.SubmitRequest`, the three
104
+ `BranchPresence` request schemas, and the `BranchRpcs` aliases
105
+ `SubmitPayload`, `AnnouncePayload`, `LeavePayload`, and `RosterPayload` are
106
+ removed. `BranchProtocol.CommandIdentity` is removed: the ledger reads a
107
+ command's id from the payload admission already decoded.
108
+ - **Breaking.** `makeLiveWith`, `layerWith`, and `BranchPresence.layer` fail
109
+ with `invalid_request` when an option is not a positive safe integer.
110
+ `BranchPresence.Service` gained `leaseMs`.
111
+ - A workspace subscription woken by an entry committed in this process reads
112
+ only the runs that entry named, rather than re-listing the catalog and
113
+ reading one page per covered run. The catalog-wide round still runs at open,
114
+ on an announcement, and once per `tailIntervalMs`, so removals, generation
115
+ changes, and other processes' writes are reconciled as before. The bounds
116
+ documentation now separates what a follower holds open, which the bounds
117
+ cap, from its per-run positions and per-round work, which scale with the
118
+ runs it covers.
119
+ - A branch admitted after a workspace subscription opened ends the stream at
120
+ that branch's own expiry: the expiry interrupt re-arms when reconciliation
121
+ lowers the deadline, and a page checks the deadline before it emits, so a
122
+ journal read that spans the expiry serves nothing past it and a stalled read
123
+ cannot hold the subscription open.
124
+ - An open subscription ends with `unauthorized` when the capability that
125
+ authorized it expires. Authorization was one-shot at open, so a share-link
126
+ holder could read past its own expiry by staying connected. That now covers a
127
+ branch a workspace subscription discovers AFTER it opened: reconciliation
128
+ carries the admitting capability's expiry into the subscription's deadline
129
+ rather than reducing the admission to a yes.
130
+ - Catch-up paging defers its cursor snapshot, so every page past the first asks
131
+ from where the previous one ended instead of repeating the previous request.
132
+ - Catch-up pages are validated the way live frames already were: scope,
133
+ per-run ordering, progress past the requested cursor, and cursor uniqueness,
134
+ refused as `protocol_violation` before any cursor moves.
135
+ - A workspace subscription reconciles its covered run set against
136
+ `RunCatalog.list` every round, so a run the catalog gains becomes visible
137
+ without an announcement and a run it stops naming is no longer queried.
138
+ - A workspace tail reads one bounded page per run per round, so a permanently
139
+ busy run wakes the next round instead of holding its fan-out slot.
140
+ - A read shares its `limit` across the runs it covers before any run takes a
141
+ second helping. Filling in run order let a producer that stayed one page
142
+ ahead take every slot of every page, so `done` never became true and a
143
+ bootstrapping follower never reached the runs behind it.
144
+ - The server deduplicates `RunCatalog.list` at the seam. A host catalog that
145
+ named one run twice had it read twice from the same served position, so a
146
+ read page carried its entries twice and a subscription opened two concurrent
147
+ tails of it emitting identical frames.
148
+ - `BranchCommands` keys its ledger and permit per branch, and `BranchPresence`
149
+ keys its roster per branch, so ids sharing a delimiter no longer collide and
150
+ one branch's replay no longer stalls another's writes.
151
+ - `BranchCommands.replay` ends its walk on an empty page, matching the guard
152
+ the workspace tail already carried.
153
+ - An admission conflict on a command whose receipt the bounded ledger has
154
+ already evicted reads the branch's durable history for it, so it is reported
155
+ as the duplicate it is instead of as a journal contradicting its own conflict
156
+ report.
157
+ - `Branch.MintShare` refuses a parent capability that expired mid-request
158
+ rather than minting a link that can never authorize.
159
+ - `BranchShare` and `WorkspaceShare` verify from a snapshot taken at entry, so
160
+ claims cannot widen while Web Crypto is in flight.
161
+ - Share claims are refused with `invalid_request` when they do not survive
162
+ UTF-8, and length prefixes count UTF-8 bytes rather than UTF-16 units.
163
+ - `encodedByteLength` is total: a value with no JSON text costs the bytes
164
+ `null` occupies, and a value JSON refuses measures as infinite so it trips
165
+ every ceiling rather than throwing.
166
+ - `branchOfRunId` returns `null` for the bare branch prefix instead of
167
+ branding an empty string as a `BranchId`.
168
+ - `RunCatalog.layerStatic` answers `list` with a fresh array, matching the
169
+ other implementations.
170
+ - `BranchShare.makeNoop` and `WorkspaceShare.makeNoop` fail `mint` instead of
171
+ dying, matching their declared type and the rest of the package.
172
+ - `SyncError.is` validates the code, the message, and the `resync` invariant
173
+ rather than trusting a bare tag, and answers rather than raising when a
174
+ field it reads throws.
175
+ - `SubscribeOptions.credit` and `SyncClient.make`'s `bootstrapLimit` are
176
+ refused locally with `invalid_request` when they exceed the wire maxima. A
177
+ value only the wire schema rejected surfaced as a transport failure and the
178
+ follow retried it under backoff forever.
179
+ - `SyncError.message` is bounded like `cause`, and a journal failure's rendered
180
+ code is bounded too, so no host string can be the largest thing an error
181
+ carries.
182
+ - A journal failure whose code this boundary also declares crosses as that
183
+ code: `journal_closed` as `closed`, `queue_overflow` as `backpressure`, and
184
+ `decode_failed` as itself, on the read path and the branch write path alike.
185
+ Collapsing every code but `compacted` into `unknown` threw away a
186
+ classification the wire already carried, so a follower could not tell a
187
+ shut-down journal from an unexplained fault. A code with no counterpart here
188
+ stays `unknown`, and the journal's own message stays refused.
189
+ - `apply` and `onResync` are handed values the committed position is
190
+ snapshotted from, so a consumer callback can no longer choose the cursor it
191
+ is being handed an entry for.
192
+ - `Branch.WatchRoster` compares rosters by an injective rendering and
193
+ deduplicates its initial emission, so a change the old delimiter-joined key
194
+ hid is emitted and an unchanged roster is not sent twice.
195
+ - `HeartbeatFrame` is documented as reserved: no server emits one, so a client
196
+ must not wait for it.
197
+ - Documentation is package-owned and generated: `docs/pages/api/sync.md` and
198
+ the protocol section of `docs/pages/concepts/sync.md` come from this
199
+ package's JSDoc and `docs/`.
200
+
201
+ ## [0.1.0] - 2026-08-05
202
+
203
+ ### Added
204
+
205
+ - Added the workspace read-path sync protocol with its server, client, and
206
+ run catalog.
207
+ - Added the shared branch protocol, presence, projection, commands, share, and
208
+ RPCs, with authorization enforced on the server.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 William Cory and the Smithers Flows contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,209 @@
1
- # Temporary Holding Version
1
+ # @smthrs/sync
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Release candidate scope, host requirements and compatibility review are defined in the [library support policy](https://github.com/smithersai/smithers/blob/main/RELEASE_SUPPORT.md).
4
+
5
+ This package declares `effect` as an exact
6
+ `4.0.0-rc.115` peer dependency. Keep the application on that version so
7
+ all Smithers packages share one Effect runtime.
8
+
9
+ **Documentation:** https://smithers-sync.smithers.sh
10
+
11
+ Browser-safe, read-only replication of canonical
12
+ [`@smthrs/journal`](https://journal.smithers.sh) entries. It defines the wire
13
+ protocol, the RPC group, the server, and the replay-then-follow client.
14
+
15
+ The read path, `SyncRpcs` served by `SyncServer` and followed by `SyncClient`,
16
+ never writes to a journal, so a follower cannot corrupt what it reads.
17
+ `BranchCommands` is the separate write surface: it appends admitted branch
18
+ commands to a branch's journal run after verifying a write-scoped
19
+ `BranchShare` capability.
20
+
21
+ ## Install
22
+
23
+ Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
24
+
25
+ `effect` is a peer dependency at exactly `4.0.0-rc.115`. Two copies of `effect`
26
+ in one program are two sets of service tags, so a client built against one copy
27
+ cannot be provided to a program holding the other.
28
+
29
+ Node.js 26.4.0 or later, or a browser with Web Crypto. The package ships as
30
+ both ESM and CommonJS with TypeScript declarations.
31
+
32
+ ## Follow a run
33
+
34
+ One `subscribe` call replays a run's durable history and then follows it live,
35
+ through a single stream:
36
+
37
+ ```ts
38
+ import type { JournalEvent } from "@smthrs/journal"
39
+ import * as SyncClient from "@smthrs/sync/SyncClient"
40
+ import * as Effect from "effect/Effect"
41
+ import * as Layer from "effect/Layer"
42
+ import * as Stream from "effect/Stream"
43
+ import * as RpcClient from "effect/unstable/rpc/RpcClient"
44
+ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
45
+
46
+ const runId = "build-42" as JournalEvent.RunId
47
+
48
+ export const follow = Effect.gen(function*() {
49
+ const sync = yield* SyncClient.SyncClient
50
+ yield* sync.subscribe({ scope: { _tag: "Run", runId }, cursors: [] }).pipe(
51
+ Stream.runForEach((entry) => Effect.logInfo(`${entry.seq} ${entry.eventType}`))
52
+ )
53
+ })
54
+
55
+ export const clientLayer = SyncClient.layer.pipe(
56
+ Layer.provide(RpcClient.layerProtocolSocket()),
57
+ Layer.provide(RpcSerialization.layerJson)
58
+ )
59
+ ```
60
+
61
+ The socket that protocol runs over comes from your platform: a WebSocket in a
62
+ browser, `@effect/platform-node`'s socket layer in Node. Pass
63
+ `{ _tag: "Workspace" }` instead of a run scope to follow every run the server
64
+ lists and your credential covers.
65
+
66
+ The serving side is `SyncServer` over a journal and a run catalog.
67
+ [`@smthrs/gateway`](https://gateway.smithers.sh) already mounts it on
68
+ `POST /sync` and `/sync/ws`, so a follower against a Smithers gateway writes
69
+ only the code above.
70
+
71
+ `Read` pages durable entries, then the client subscribes from exclusive per-run
72
+ cursors. A non-contiguous journal sequence is valid; `SyncGapError` means the
73
+ server skipped beyond the interval covered by the client's cursor.
74
+
75
+ `sync.progress` reports two cursor sets: `delivered` holds delivery bookmarks,
76
+ and `applied` holds what successful `apply` or `onResync` callbacks
77
+ acknowledged. Those callbacks fail with your own error types, which the
78
+ subscription's error channel carries unchanged. Persist state and its cursor
79
+ together inside those callbacks. An applying subscriber never skips entries
80
+ merely because an earlier subscriber received them.
81
+
82
+ ## Authorization
83
+
84
+ Authorization is fail-closed along two boundaries, and both are consulted on
85
+ every request. Branch runs authorize through the branch share capability
86
+ carried in each request (`BranchShare`); a share link grants exactly its
87
+ branch's run. Non-branch runs and workspace listings authorize through the
88
+ authenticated workspace principal (`SyncPrincipal`, default anonymous), which
89
+ `SyncAuth.layer` establishes over RPC by verifying the `WorkspaceShare`
90
+ capability presented in the `flows-sync-workspace` request header. A connection
91
+ with no valid credential is refused every non-branch read.
92
+
93
+ Both authorities take `Redacted` keyrings, lead their signed encoding with a
94
+ scheme label, and carry a rotation-ready `kid` inside the signed claims. An
95
+ open subscription ends with `unauthorized` when the credential that opened it
96
+ expires, because a stream authorized once at open is otherwise the one thing a
97
+ signed expiry cannot revoke. See
98
+ [Authorization](https://smithers-sync.smithers.sh/concepts/authorization/).
99
+
100
+ ## Branch collaboration
101
+
102
+ A branch is one shared live document whose durable state is exactly one journal
103
+ run (`BranchProtocol.branchRunId`), so multiplayer reuses the canonical `seq`,
104
+ cursors, gap detection, and resumable follow rather than introducing a second
105
+ source of truth. Presence is a lease and is never journalled; commands are
106
+ admitted through a client-minted idempotency key; every branch operation after
107
+ `Branch.CreateBranch` authorizes through a signed, expiring, branch-scoped
108
+ share capability.
109
+
110
+ The native gateway mounts the existing branch presence RPCs at `POST /branch`
111
+ when its authenticated runtime bridge is configured. The Go install resolves
112
+ membership, branch and socket session before forwarding; this host adapter uses
113
+ the same `BranchPresence` lease roster. Branch commands and share-link bootstrap
114
+ remain unserved on that mount. See
115
+ [Branch collaboration](https://smithers-sync.smithers.sh/concepts/branches/).
116
+
117
+ ## Bounds
118
+
119
+ Every in-flight surface is bounded: entries per page, frames per subscription,
120
+ journal reads open per workspace subscription, bytes per frame. What one
121
+ follower holds open is a function of the configured bound, not of how far
122
+ behind it has fallen. Bookkeeping and periodic work still scale with the
123
+ workspace: a workspace subscription keeps a position for every run it has
124
+ covered, and its catalog-wide round, once per `tailIntervalMs` or on an
125
+ announcement, reads one page per listed run under the concurrency bound. A
126
+ wake for a local append reads only the run it named. Every numeric option is
127
+ validated where it enters: a value that is not a positive safe integer fails
128
+ the constructor with `invalid_request` rather than quietly disabling the
129
+ comparison it configures.
130
+ The table of bounds and defaults is in the
131
+ [API reference](https://smithers-sync.smithers.sh/reference/api/#bounds).
132
+
133
+ Both change feeds slide rather than block, and neither is a source of truth.
134
+ `RunCatalog.list` and `BranchPresence.list` are the authoritative state, and
135
+ every reader re-lists on a cadence of its own, so a dropped notification costs
136
+ latency and never state.
137
+
138
+ ## Rewinds and cursor generations
139
+
140
+ `RunCursor` and `EntriesFrame` carry a `generation`. Server responses always
141
+ include it, zero included, and a client refuses one that omits it; a persisted
142
+ request cursor may omit it for generation zero. SQL journals persist it
143
+ independently of sequence numbers. Rewind increments it atomically with
144
+ truncation, so a follower at 100 cannot silently discard a replacement entry at
145
+ 51 after rewinding to 50.
146
+
147
+ Both reads and subscriptions fail with `SyncError.code = "lineage_changed"`
148
+ when generations differ. Live run and workspace subscriptions also check while
149
+ idle, within `tailIntervalMs`. This failure is terminal: rebuild the projection
150
+ from the current retained history through the archive boundary, create a fresh
151
+ sync client, and resume from the server error's
152
+ `rewind: { runId, generation, afterSeq }`. For `afterSeq: -1`, omit the cursor
153
+ to replay the entire current history. Keep the generation with every persisted
154
+ cursor. Compaction recovery remains separate.
155
+
156
+ Append-only journal adapters may omit `Journal.Service.generation`. Any adapter
157
+ that truncates or replaces history must implement it and advance the generation
158
+ in its truncation transaction. Upgrade sync clients and servers together;
159
+ older clients cannot detect generation changes. See
160
+ [Rewind generations](https://smithers-sync.smithers.sh/reference/api/#rewind-generations).
161
+
162
+ ## Public API
163
+
164
+ The root exports these namespaces, also available from matching
165
+ `@smthrs/sync/*` subpaths. The
166
+ [API reference](https://smithers-sync.smithers.sh/reference/api/) documents
167
+ each one.
168
+
169
+ | Namespace | What it owns |
170
+ | ------------------ | --------------------------------------------------------------------------------------------------------------- |
171
+ | `SyncError` | `ErrorCode`, `SyncError` with the structural guard `SyncError.is`, and terminal `SyncGapError`. |
172
+ | `SyncProtocol` | Scopes, cursors, `Resync`, request and response schemas, frames, and the size and request limits. |
173
+ | `SyncRpcs` | The read-path RPC group (`Sync.Read`, `Sync.Subscribe`, `Sync.Snapshot`) and the `SyncAuth` middleware service. |
174
+ | `SyncAuth` | Implementations of that middleware: `layer`, `layerClient`, and the header codec. |
175
+ | `SyncPrincipal` | The per-request identity reference, its constructors, and `layerWorkspace` for in-process owners. |
176
+ | `WorkspaceShare` | The workspace capability authority over a `Redacted` keyring with `kid` rotation. |
177
+ | `RunCatalog` | The workspace run set a subscription reconciles against, in-memory, polling, and static forms. |
178
+ | `SyncServer` | The read-path implementation, its policy, and `layerHandlers`. |
179
+ | `SyncClient` | The browser-safe replay-then-follow client and its subscription options. |
180
+ | `BranchProtocol` | The branch vocabulary: ids, claims, capabilities, submissions, receipts, and the run-id mapping. |
181
+ | `BranchShare` | The branch capability authority over a `Redacted` keyring with `kid` rotation. |
182
+ | `BranchIds` | The port branch and capability ids are minted through. |
183
+ | `BranchCommands` | Idempotent command admission onto a branch's journal run. |
184
+ | `BranchPresence` | The ephemeral, lease-expiring roster. |
185
+ | `BranchProjection` | The fold from branch commands to a document view. |
186
+ | `BranchRpcs` | The branch collaboration wire group. |
187
+ | `BranchServer` | The handler layer that projects the branch services onto that group. |
188
+
189
+ Public test subpaths are `@smthrs/sync/test/TestSocket` (`FrameFilter`,
190
+ `TestFaults`, `Pair`, `makePair`) and `@smthrs/sync/test/TestSync`
191
+ (`layerTest`, `layerTrustAllAsOwner`, `layerNoop`, `connect`).
192
+
193
+ ## Documentation
194
+
195
+ - [Installation](https://smithers-sync.smithers.sh/installation/)
196
+ - [Quickstart](https://smithers-sync.smithers.sh/quickstart/)
197
+ - [Scopes and cursors](https://smithers-sync.smithers.sh/concepts/scopes-and-cursors/)
198
+ - [Replay then follow](https://smithers-sync.smithers.sh/concepts/replay-then-follow/)
199
+ - [Authorization](https://smithers-sync.smithers.sh/concepts/authorization/)
200
+ - [Follow a run](https://smithers-sync.smithers.sh/guides/follow-a-run/)
201
+ - [Serve the read path](https://smithers-sync.smithers.sh/guides/serve-the-read-path/)
202
+ - [Wire protocol](https://smithers-sync.smithers.sh/protocol/)
203
+ - [API reference](https://smithers-sync.smithers.sh/reference/api/)
204
+ - [Troubleshooting](https://smithers-sync.smithers.sh/troubleshooting/)
205
+ - [Optional long soak](docs/guides/run-a-long-soak.md)
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Idempotent admission of collaborative commands onto a branch document.
3
+ *
4
+ * Three different things look identical from the server's side — an optimistic
5
+ * client re-sending after a timeout, a reconnecting client flushing its
6
+ * outbox, and two people pressing the same button at once — and all three must
7
+ * produce exactly one durable command. The client-minted `commandId` is the
8
+ * idempotency key for all three.
9
+ *
10
+ * The exactly-once constraint is durable, not process-local: every append
11
+ * carries the producer identity `(branch run, commandSourceId(commandId),
12
+ * commandSourceSeq)`, which the journal enforces inside its own write
13
+ * transaction. Two independently constructed servers that race the same
14
+ * command therefore collide in the journal — one appends, the other receives
15
+ * a duplicate receipt or an idempotency conflict and resolves the canonical
16
+ * sequence by replaying the branch (audit finding F-14). The in-memory
17
+ * ledger, permit, and replay cursor are a fast path only: they answer known
18
+ * duplicates without a journal write and keep a restarted server from
19
+ * re-executing history, but correctness never depends on them.
20
+ *
21
+ * @since 0.1.0
22
+ */
23
+ import { Journal } from "@smthrs/journal";
24
+ import * as Context from "effect/Context";
25
+ import * as Effect from "effect/Effect";
26
+ import * as Layer from "effect/Layer";
27
+ import type * as Scope from "effect/Scope";
28
+ import { type BranchId, type CommandId, CommandReceipt, CommandSubmission, SubmitRequest } from "./BranchProtocol.ts";
29
+ import * as BranchShare from "./BranchShare.ts";
30
+ import { SyncError } from "./SyncError.ts";
31
+ /**
32
+ * Branch command admission operations.
33
+ *
34
+ * @category models
35
+ * @since 0.1.0
36
+ */
37
+ export interface Service {
38
+ readonly submit: (request: SubmitRequest) => Effect.Effect<CommandReceipt, SyncError>;
39
+ }
40
+ declare const BranchCommands_base: Context.ServiceClass<BranchCommands, "@smthrs/sync/BranchCommands", Service>;
41
+ /**
42
+ * The branch command ledger.
43
+ *
44
+ * @category services
45
+ * @since 0.1.0
46
+ */
47
+ export declare class BranchCommands extends BranchCommands_base {
48
+ }
49
+ /**
50
+ * Constructs a command ledger that admits nothing.
51
+ *
52
+ * @category constructors
53
+ * @since 0.1.0
54
+ */
55
+ export declare const makeNoop: (overrides?: Partial<Service>) => Service;
56
+ /**
57
+ * Provides a command ledger that admits nothing.
58
+ *
59
+ * @category layers
60
+ * @since 0.1.0
61
+ */
62
+ export declare const layerNoop: Layer.Layer<BranchCommands>;
63
+ /**
64
+ * Receipts one branch keeps in memory before the oldest are evicted.
65
+ *
66
+ * The ledger is a fast path: it answers a known duplicate without a journal
67
+ * write, and losing an entry costs a round trip, never correctness, because
68
+ * the journal's own producer identity is the durable exactly-once constraint
69
+ * and returns a `Duplicate` receipt for a command already admitted. Retaining
70
+ * one receipt per command forever meant a process serving a hundred branches
71
+ * of a hundred thousand commands held ten million of them, so the fast path
72
+ * is bounded and the durable path is what makes it safe to bound.
73
+ *
74
+ * @category constants
75
+ * @since 1.0.0-rc.0
76
+ */
77
+ export declare const defaultLedgerCapacity = 4096;
78
+ /**
79
+ * Inclusive UTF-8 JSON submission ceiling. The separate sync entry ceiling
80
+ * leaves room for the durable envelope around every admitted command.
81
+ *
82
+ * @category constants
83
+ * @since 1.0.0-rc.0
84
+ */
85
+ export declare const defaultMaxCommandBytes: number;
86
+ /**
87
+ * Milliseconds a branch may have no active submissions before its in-memory state is
88
+ * dropped.
89
+ *
90
+ * The receipt ledger is bounded per branch; this bounds the branches. A
91
+ * dropped branch re-hydrates on its next submission exactly as it does after
92
+ * a restart, so eviction costs a journal read, never correctness. The default
93
+ * is the longest lifetime a branch capability may carry, so a branch is only
94
+ * dropped once every capability minted at its last touch can have expired.
95
+ *
96
+ * @category constants
97
+ * @since 1.0.0-rc.0
98
+ */
99
+ export declare const defaultBranchIdleMs: number;
100
+ /**
101
+ * Entries one branch's first-touch hydration reads before it stops.
102
+ *
103
+ * Hydration is the only full history read that ever sat on the WRITE path: the
104
+ * first submission a process makes to a branch paged that branch's whole
105
+ * durable log, 256 entries at a time, while holding the branch's admission
106
+ * permit, before its own append was attempted. A hundred-thousand-entry branch
107
+ * therefore charged its next writer four hundred journal reads for a fast path
108
+ * that keeps four thousand receipts.
109
+ *
110
+ * The budget defaults to {@link defaultLedgerCapacity} because hydration
111
+ * cannot seed more receipts than the ledger holds: everything a longer walk
112
+ * records displaces something the same walk already recorded. What the extra
113
+ * reads buy is a FRESHER selection — the tail of history rather than its head
114
+ * — and the price is an unbounded wait on somebody's write. This trades that
115
+ * back: the walk stops, the cursor stays where it stopped, and a later replay
116
+ * continues forward from there.
117
+ *
118
+ * A branch longer than the budget starts with a partly cold fast path and
119
+ * nothing else. Correctness never rested on the ledger: the journal's producer
120
+ * identity is the durable exactly-once constraint, an exact retry receives a
121
+ * `Duplicate` receipt carrying the canonical sequence, and an
122
+ * `idempotency_conflict` is resolved by a read of the whole history that the
123
+ * ledger's own bound already made necessary.
124
+ *
125
+ * @category constants
126
+ * @since 1.0.0-rc.0
127
+ */
128
+ export declare const defaultHydrationLimit = 4096;
129
+ /**
130
+ * Admission policy.
131
+ *
132
+ * The default caps one encoded command submission at 1 MiB, one branch's
133
+ * in-memory receipt ledger at {@link defaultLedgerCapacity} commands, and one
134
+ * branch's first-touch hydration at {@link defaultHydrationLimit} entries.
135
+ *
136
+ * @category models
137
+ * @since 0.1.0
138
+ */
139
+ export interface Options {
140
+ /**
141
+ * Largest encoded journal entry followers accept. Defaults to
142
+ * {@link SyncProtocol.defaultMaxFrameBytes}. Configure the server and client
143
+ * with the same or a larger ceiling. Admission reserves the full durable
144
+ * envelope, including generated sequence and timestamp fields, before writing.
145
+ */
146
+ readonly maxFrameBytes?: number | undefined;
147
+ /**
148
+ * Largest encoded command submission admitted, in bytes. Defaults to
149
+ * {@link defaultMaxCommandBytes}.
150
+ */
151
+ readonly maxCommandBytes?: number | undefined;
152
+ /**
153
+ * Receipts one branch keeps in memory before the oldest are evicted.
154
+ * Defaults to {@link defaultLedgerCapacity}.
155
+ */
156
+ readonly ledgerCapacity?: number | undefined;
157
+ /**
158
+ * Entries one branch's first-touch hydration reads before it stops.
159
+ * Defaults to {@link defaultHydrationLimit}.
160
+ */
161
+ readonly hydrationLimit?: number | undefined;
162
+ /**
163
+ * Milliseconds a branch may have no active submissions before its in-memory state is
164
+ * dropped. Defaults to {@link defaultBranchIdleMs}.
165
+ */
166
+ readonly branchIdleMs?: number | undefined;
167
+ }
168
+ /**
169
+ * Constructs the journal-backed branch command ledger under an explicit
170
+ * policy. The caller scope owns cached states and their idle-expiration timers.
171
+ *
172
+ * A submission whose encoded form exceeds the command ceiling is refused with
173
+ * `frame_too_large` before anything is appended, so one oversized `args`
174
+ * cannot enter the branch journal and poison every follower that replays it.
175
+ *
176
+ * First-touch hydration reads at most `hydrationLimit` entries, so the length
177
+ * of a branch's history is not charged to the next writer's latency; see
178
+ * {@link defaultHydrationLimit} for why the fast path may be seeded partially
179
+ * without any effect on what the ledger admits.
180
+ *
181
+ * Every option is validated as a positive safe integer at construction: the
182
+ * TypeScript type says `number`, and `NaN` silently disabled the ceiling it
183
+ * was compared against.
184
+ *
185
+ * @category constructors
186
+ * @since 0.1.0
187
+ */
188
+ export declare const makeLiveWith: (options?: Options) => Effect.Effect<Service, SyncError, Journal.Journal | BranchShare.BranchShare | Scope.Scope>;
189
+ /**
190
+ * Constructs the journal-backed branch command ledger with default policy.
191
+ * The caller scope owns cached states and their idle-expiration timers.
192
+ *
193
+ * @category constructors
194
+ * @since 0.1.0
195
+ */
196
+ export declare const makeLive: Effect.Effect<Service, never, Journal.Journal | BranchShare.BranchShare | Scope.Scope>;
197
+ /**
198
+ * Provides the journal-backed branch command ledger.
199
+ *
200
+ * @category layers
201
+ * @since 0.1.0
202
+ */
203
+ export declare const layer: Layer.Layer<BranchCommands, never, Journal.Journal | BranchShare.BranchShare>;
204
+ /**
205
+ * Provides the journal-backed branch command ledger under an explicit policy.
206
+ * Fails with `invalid_request` when an option is not a positive safe integer.
207
+ *
208
+ * @category layers
209
+ * @since 0.1.0
210
+ */
211
+ export declare const layerWith: (options: Options) => Layer.Layer<BranchCommands, SyncError, Journal.Journal | BranchShare.BranchShare>;
212
+ /**
213
+ * Builds a submission, filling in the fields a plain command never sets, and
214
+ * refusing a field the schema forbids with a `SyncError` rather than a defect.
215
+ *
216
+ * @category constructors
217
+ * @since 0.1.0
218
+ */
219
+ export declare const submission: (fields: {
220
+ readonly branchId: BranchId;
221
+ readonly commandId: CommandId;
222
+ readonly participantId: CommandSubmission["participantId"];
223
+ readonly name: string;
224
+ readonly args?: string;
225
+ readonly target?: string;
226
+ }) => Effect.Effect<CommandSubmission, SyncError>;
227
+ export {};
228
+ //# sourceMappingURL=BranchCommands.d.ts.map