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

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
@@ -0,0 +1,323 @@
1
+ /**
2
+ * The branch collaboration contract: a branch is one shared live document.
3
+ *
4
+ * A branch has no store of its own. Its durable state is exactly one journal
5
+ * run — `branchRunId` is the only mapping — so every replication guarantee the
6
+ * rest of this package already provides (canonical per-run `seq`, exclusive
7
+ * cursors, gap detection, resumable follow) carries over unchanged, and
8
+ * multiplayer never introduces a second source of truth.
9
+ *
10
+ * Ephemeral collaboration state (presence, cursors) deliberately has no
11
+ * representation here beyond the wire shapes: it is leased, never journalled.
12
+ *
13
+ * @since 0.1.0
14
+ */
15
+ import * as JournalEvent from "@smthrs/journal/JournalEvent";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Schema from "effect/Schema";
18
+ /**
19
+ * Schema for the identifier of one collaboratively edited branch.
20
+ *
21
+ * @category schemas
22
+ * @since 0.1.0
23
+ */
24
+ export const BranchId = Schema.NonEmptyString.pipe(Schema.brand("@smthrs/sync/BranchProtocol/BranchId"));
25
+ /**
26
+ * Schema for the identifier of one connected client.
27
+ *
28
+ * @category schemas
29
+ * @since 0.1.0
30
+ */
31
+ export const ParticipantId = Schema.NonEmptyString.pipe(Schema.brand("@smthrs/sync/BranchProtocol/ParticipantId"));
32
+ /**
33
+ * Schema for a client-minted command identifier.
34
+ *
35
+ * The identifier is minted once per user intent and reused across optimistic
36
+ * application, retransmission, and concurrent submission, which is what makes
37
+ * it the idempotency key rather than a correlation id.
38
+ *
39
+ * @category schemas
40
+ * @since 0.1.0
41
+ */
42
+ export const CommandId = Schema.NonEmptyString.pipe(Schema.brand("@smthrs/sync/BranchProtocol/CommandId"));
43
+ /**
44
+ * Schema for the access a share capability grants.
45
+ *
46
+ * @category schemas
47
+ * @since 0.1.0
48
+ */
49
+ export const Access = Schema.Literals(["read", "write"]);
50
+ /**
51
+ * The journal run backing a branch document.
52
+ *
53
+ * The prefix keeps branch runs from colliding with engine runs in the same
54
+ * workspace journal, and makes the mapping reversible for tooling.
55
+ *
56
+ * @category constructors
57
+ * @since 0.1.0
58
+ */
59
+ export const branchRunId = (branchId) => `${branchRunPrefix}${branchId}`;
60
+ /** The one prefix {@link branchRunId} writes and {@link branchOfRunId} reads. */
61
+ const branchRunPrefix = "flows/branch/";
62
+ /**
63
+ * The branch a journal run belongs to, or `null` for a non-branch run.
64
+ *
65
+ * The bare prefix is a non-branch run, not a branch with an empty id.
66
+ * `BranchId` is a branded `NonEmptyString`, so branding `""` would produce a
67
+ * value the brand forbids and hand it to `share.verify` as the branch under
68
+ * authorization. Refusing it here keeps the brand's promise true everywhere
69
+ * downstream of this function.
70
+ *
71
+ * @category constructors
72
+ * @since 0.1.0
73
+ */
74
+ export const branchOfRunId = (runId) => {
75
+ if (!runId.startsWith(branchRunPrefix))
76
+ return null;
77
+ const rest = runId.slice(branchRunPrefix.length);
78
+ return rest.length === 0 ? null : rest;
79
+ };
80
+ /**
81
+ * The journal producer identity of one admitted command.
82
+ *
83
+ * `(runId, sourceId, sourceSeq)` is the journal's own durable idempotency
84
+ * key. Deriving the source from the client-minted `commandId` — with the
85
+ * fixed {@link commandSourceSeq} — turns that key into the branch's
86
+ * exactly-once admission constraint: two server instances racing the same
87
+ * command collide inside the journal's own transaction, so one appends and
88
+ * the other reads back the canonical sequence, instead of both appending
89
+ * (audit finding F-14). The branch run id already scopes the key, so equal
90
+ * command ids on different branches never collide.
91
+ *
92
+ * @category constructors
93
+ * @since 0.1.0
94
+ */
95
+ export const commandSourceId = (commandId) => `flows/branch-command/${commandId}`;
96
+ /**
97
+ * The fixed producer sequence of an admitted command.
98
+ *
99
+ * A command's identity lives entirely in {@link commandSourceId}, so its
100
+ * producer sequence is always 0. It must be supplied explicitly: letting the
101
+ * journal allocate would hand a resubmission after a lost response sequence 1,
102
+ * a fresh identity, and therefore a second durable append.
103
+ *
104
+ * @category constructors
105
+ * @since 0.1.0
106
+ */
107
+ export const commandSourceSeq = 0;
108
+ /**
109
+ * The single durable event type a branch document is built from.
110
+ *
111
+ * Chat, edits, and flow launches are all commands, so the branch journal has
112
+ * exactly one event shape and the projection has exactly one decoder.
113
+ *
114
+ * @category models
115
+ * @since 0.1.0
116
+ */
117
+ export const CommandEvent = "flows/branch/command";
118
+ /**
119
+ * Schema for the claims a share capability carries.
120
+ *
121
+ * `kid` names the key that signed the claims and is itself signed, so a
122
+ * verifier both selects the right key out of a rotating keyring and refuses a
123
+ * capability whose key name was swapped after minting. It matches
124
+ * `WorkspaceShare.WorkspaceClaims`: the two authorities are one scheme, and a
125
+ * branch link that could not survive a secret rotation was the only place they
126
+ * differed.
127
+ *
128
+ * @category schemas
129
+ * @since 0.1.0
130
+ */
131
+ export class ShareClaims extends Schema.Class("@smthrs/sync/BranchProtocol/ShareClaims")({
132
+ kid: Schema.NonEmptyString,
133
+ branchId: BranchId,
134
+ capabilityId: Schema.NonEmptyString,
135
+ access: Access,
136
+ issuedAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
137
+ expiresAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
138
+ }) {
139
+ }
140
+ /**
141
+ * Schema for a signed, expiring, branch-scoped share capability.
142
+ *
143
+ * @category schemas
144
+ * @since 0.1.0
145
+ */
146
+ export class ShareCapability extends Schema.Class("@smthrs/sync/BranchProtocol/ShareCapability")({
147
+ claims: ShareClaims,
148
+ signature: Schema.String
149
+ }) {
150
+ }
151
+ /**
152
+ * Schema for a participant's caret inside one embedded card.
153
+ *
154
+ * @category schemas
155
+ * @since 0.1.0
156
+ */
157
+ export class Cursor extends Schema.Class("@smthrs/sync/BranchProtocol/Cursor")({
158
+ cardId: Schema.NonEmptyString,
159
+ offset: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
160
+ }) {
161
+ }
162
+ /**
163
+ * BranchCard-compatible display coordinates; paths are metadata only.
164
+ *
165
+ * @category schemas
166
+ * @since 1.0.0-rc.1
167
+ */
168
+ export const PresenceWhere = Schema.Union([
169
+ Schema.Struct({
170
+ kind: Schema.Literal("file"),
171
+ path: Schema.NonEmptyString,
172
+ line: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0)))
173
+ }),
174
+ Schema.Struct({ kind: Schema.Literal("terminal"), id: Schema.NonEmptyString }),
175
+ Schema.Struct({ kind: Schema.Literal("step"), label: Schema.NonEmptyString }),
176
+ Schema.Struct({ kind: Schema.Literal("branch") })
177
+ ]);
178
+ // Optional fields preserve decoding of recorded branch-protocol frames.
179
+ const presenceFields = {
180
+ sessionId: Schema.optionalKey(Schema.NonEmptyString),
181
+ kind: Schema.optionalKey(Schema.Literals(["person", "agent"])),
182
+ where: Schema.optionalKey(PresenceWhere),
183
+ watching: Schema.optionalKey(Schema.NonEmptyString),
184
+ for_member: Schema.optionalKey(Schema.NonEmptyString),
185
+ agentKind: Schema.optionalKey(Schema.Literals(["smithers", "coding", "reviewer", "claude-code", "codex", "external"])),
186
+ via: Schema.optionalKey(Schema.Literals(["ssh", "terminal", "cli"])),
187
+ runId: Schema.optionalKey(Schema.NonEmptyString)
188
+ };
189
+ /**
190
+ * Schema for one live participant on a branch.
191
+ *
192
+ * `leaseExpiresAtMs` is the whole lifetime contract: presence is a lease, so a
193
+ * client that disconnects without saying so disappears on its own.
194
+ *
195
+ * @category schemas
196
+ * @since 0.1.0
197
+ */
198
+ export class Participant extends Schema.Class("@smthrs/sync/BranchProtocol/Participant")({
199
+ branchId: BranchId,
200
+ participantId: ParticipantId,
201
+ displayName: Schema.NonEmptyString,
202
+ cursor: Schema.NullOr(Cursor),
203
+ ...presenceFields,
204
+ leaseExpiresAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
205
+ }) {
206
+ }
207
+ /**
208
+ * Schema for a capability-bearing request for one branch's roster.
209
+ *
210
+ * @category schemas
211
+ * @since 0.1.0
212
+ */
213
+ export const RosterRequest = Schema.Struct({
214
+ capability: ShareCapability,
215
+ branchId: BranchId,
216
+ /** Only the authenticated host adapter consumes this readiness assertion. */
217
+ sourcesReady: Schema.optionalKey(Schema.Boolean)
218
+ });
219
+ /**
220
+ * Schema for a capability-bearing request to drop one participant.
221
+ *
222
+ * @category schemas
223
+ * @since 0.1.0
224
+ */
225
+ export const LeaveRequest = Schema.Struct({
226
+ ...RosterRequest.fields,
227
+ participantId: ParticipantId,
228
+ sessionId: Schema.optionalKey(Schema.NonEmptyString)
229
+ });
230
+ /**
231
+ * Schema for one participant's announcement of itself on a branch.
232
+ *
233
+ * The same shape serves join, heartbeat, and cursor movement: an announcement
234
+ * is idempotent, so a client that reconnects simply announces again.
235
+ *
236
+ * @category schemas
237
+ * @since 0.1.0
238
+ */
239
+ export const Announcement = Schema.Struct({
240
+ ...presenceFields,
241
+ capability: ShareCapability,
242
+ branchId: BranchId,
243
+ participantId: ParticipantId,
244
+ displayName: Schema.NonEmptyString,
245
+ cursor: Schema.NullOr(Cursor)
246
+ });
247
+ /**
248
+ * Schema for one submitted command or flow invocation.
249
+ *
250
+ * Every user action on a shared branch arrives here first — chat messages are
251
+ * the `branch.say` command, renames are `branch.rename`, and a flow launch is
252
+ * that flow's own name. `target` names the shared field a command durably
253
+ * edits and is `""` for commands that only append.
254
+ *
255
+ * @category schemas
256
+ * @since 0.1.0
257
+ */
258
+ export class CommandSubmission extends Schema.Class("@smthrs/sync/BranchProtocol/CommandSubmission")({
259
+ branchId: BranchId,
260
+ commandId: CommandId,
261
+ participantId: ParticipantId,
262
+ name: Schema.NonEmptyString,
263
+ args: Schema.String,
264
+ target: Schema.String
265
+ }) {
266
+ }
267
+ /**
268
+ * Schema for a capability-bearing command submission.
269
+ *
270
+ * @category schemas
271
+ * @since 0.1.0
272
+ */
273
+ export const SubmitRequest = Schema.Struct({ capability: ShareCapability, submission: CommandSubmission });
274
+ /**
275
+ * The durable payload stored for an admitted command.
276
+ *
277
+ * Distinct from the wire request: `args` and `target` carry decoding defaults,
278
+ * so a row written before those fields existed still decodes rather than
279
+ * failing the whole ledger rebuild.
280
+ *
281
+ * `capabilityId` is the verified id of the share capability that submitted the
282
+ * command, stamped by `BranchCommands.submit` and never read from the client.
283
+ * `participantId` is whatever the client claimed, so attribute a command to
284
+ * its `capabilityId`. A row admitted before the field existed decodes with
285
+ * `""`, which no capability can carry.
286
+ *
287
+ * @category schemas
288
+ * @since 0.1.0
289
+ */
290
+ export class CommandEventPayload extends Schema.Class("@smthrs/sync/BranchProtocol/CommandEventPayload")({
291
+ commandId: CommandId,
292
+ participantId: ParticipantId,
293
+ name: Schema.NonEmptyString,
294
+ args: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(""))),
295
+ target: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(""))),
296
+ capabilityId: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed("")))
297
+ }) {
298
+ }
299
+ /**
300
+ * The command that appends a collaborative chat message.
301
+ *
302
+ * @category models
303
+ * @since 0.1.0
304
+ */
305
+ export const SayCommand = "branch.say";
306
+ /**
307
+ * Schema for the outcome of admitting a command.
308
+ *
309
+ * `duplicate` is a success, not a failure: it reports the canonical sequence
310
+ * the original submission already occupies so a retransmitting client can
311
+ * settle its optimistic row instead of executing the command twice.
312
+ *
313
+ * @category schemas
314
+ * @since 0.1.0
315
+ */
316
+ export class CommandReceipt extends Schema.Class("@smthrs/sync/BranchProtocol/CommandReceipt")({
317
+ branchId: BranchId,
318
+ commandId: CommandId,
319
+ status: Schema.Literals(["admitted", "duplicate"]),
320
+ seq: JournalEvent.Seq
321
+ }) {
322
+ }
323
+ //# sourceMappingURL=BranchProtocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchProtocol.js","sourceRoot":"","sources":["../../src/BranchProtocol.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The branch collaboration contract: a branch is one shared live document.\n *\n * A branch has no store of its own. Its durable state is exactly one journal\n * run — `branchRunId` is the only mapping — so every replication guarantee the\n * rest of this package already provides (canonical per-run `seq`, exclusive\n * cursors, gap detection, resumable follow) carries over unchanged, and\n * multiplayer never introduces a second source of truth.\n *\n * Ephemeral collaboration state (presence, cursors) deliberately has no\n * representation here beyond the wire shapes: it is leased, never journalled.\n *\n * @since 0.1.0\n */\n\nimport * as JournalEvent from \"@smthrs/journal/JournalEvent\"\nimport * as Effect from \"effect/Effect\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * Schema for the identifier of one collaboratively edited branch.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const BranchId = Schema.NonEmptyString.pipe(Schema.brand(\"@smthrs/sync/BranchProtocol/BranchId\"))\n\n/**\n * Identifier of one collaboratively edited branch.\n *\n * @category models\n * @since 0.1.0\n */\nexport type BranchId = typeof BranchId.Type\n\n/**\n * Schema for the identifier of one connected client.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const ParticipantId = Schema.NonEmptyString.pipe(Schema.brand(\"@smthrs/sync/BranchProtocol/ParticipantId\"))\n\n/**\n * Identifier of one connected client.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ParticipantId = typeof ParticipantId.Type\n\n/**\n * Schema for a client-minted command identifier.\n *\n * The identifier is minted once per user intent and reused across optimistic\n * application, retransmission, and concurrent submission, which is what makes\n * it the idempotency key rather than a correlation id.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const CommandId = Schema.NonEmptyString.pipe(Schema.brand(\"@smthrs/sync/BranchProtocol/CommandId\"))\n\n/**\n * Client-minted command identifier used as the idempotency key.\n *\n * @category models\n * @since 0.1.0\n */\nexport type CommandId = typeof CommandId.Type\n\n/**\n * Schema for the access a share capability grants.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Access = Schema.Literals([\"read\", \"write\"])\n\n/**\n * The access a share capability grants.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Access = typeof Access.Type\n\n/**\n * The journal run backing a branch document.\n *\n * The prefix keeps branch runs from colliding with engine runs in the same\n * workspace journal, and makes the mapping reversible for tooling.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const branchRunId = (branchId: BranchId): JournalEvent.RunId =>\n `${branchRunPrefix}${branchId}` as JournalEvent.RunId\n\n/** The one prefix {@link branchRunId} writes and {@link branchOfRunId} reads. */\nconst branchRunPrefix = \"flows/branch/\"\n\n/**\n * The branch a journal run belongs to, or `null` for a non-branch run.\n *\n * The bare prefix is a non-branch run, not a branch with an empty id.\n * `BranchId` is a branded `NonEmptyString`, so branding `\"\"` would produce a\n * value the brand forbids and hand it to `share.verify` as the branch under\n * authorization. Refusing it here keeps the brand's promise true everywhere\n * downstream of this function.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const branchOfRunId = (runId: JournalEvent.RunId): BranchId | null => {\n if (!runId.startsWith(branchRunPrefix)) return null\n const rest = runId.slice(branchRunPrefix.length)\n return rest.length === 0 ? null : rest as BranchId\n}\n\n/**\n * The journal producer identity of one admitted command.\n *\n * `(runId, sourceId, sourceSeq)` is the journal's own durable idempotency\n * key. Deriving the source from the client-minted `commandId` — with the\n * fixed {@link commandSourceSeq} — turns that key into the branch's\n * exactly-once admission constraint: two server instances racing the same\n * command collide inside the journal's own transaction, so one appends and\n * the other reads back the canonical sequence, instead of both appending\n * (audit finding F-14). The branch run id already scopes the key, so equal\n * command ids on different branches never collide.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const commandSourceId = (commandId: CommandId): JournalEvent.SourceId =>\n `flows/branch-command/${commandId}` as JournalEvent.SourceId\n\n/**\n * The fixed producer sequence of an admitted command.\n *\n * A command's identity lives entirely in {@link commandSourceId}, so its\n * producer sequence is always 0. It must be supplied explicitly: letting the\n * journal allocate would hand a resubmission after a lost response sequence 1,\n * a fresh identity, and therefore a second durable append.\n *\n * @category constructors\n * @since 0.1.0\n */\nexport const commandSourceSeq: JournalEvent.SourceSeq = 0 as JournalEvent.SourceSeq\n\n/**\n * The single durable event type a branch document is built from.\n *\n * Chat, edits, and flow launches are all commands, so the branch journal has\n * exactly one event shape and the projection has exactly one decoder.\n *\n * @category models\n * @since 0.1.0\n */\nexport const CommandEvent = \"flows/branch/command\"\n\n/**\n * Schema for the claims a share capability carries.\n *\n * `kid` names the key that signed the claims and is itself signed, so a\n * verifier both selects the right key out of a rotating keyring and refuses a\n * capability whose key name was swapped after minting. It matches\n * `WorkspaceShare.WorkspaceClaims`: the two authorities are one scheme, and a\n * branch link that could not survive a secret rotation was the only place they\n * differed.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ShareClaims extends Schema.Class<ShareClaims>(\"@smthrs/sync/BranchProtocol/ShareClaims\")({\n kid: Schema.NonEmptyString,\n branchId: BranchId,\n capabilityId: Schema.NonEmptyString,\n access: Access,\n issuedAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n expiresAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n}) {}\n\n/**\n * Schema for a signed, expiring, branch-scoped share capability.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class ShareCapability extends Schema.Class<ShareCapability>(\"@smthrs/sync/BranchProtocol/ShareCapability\")({\n claims: ShareClaims,\n signature: Schema.String\n}) {}\n\n/**\n * Schema for a participant's caret inside one embedded card.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class Cursor extends Schema.Class<Cursor>(\"@smthrs/sync/BranchProtocol/Cursor\")({\n cardId: Schema.NonEmptyString,\n offset: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n}) {}\n\n/**\n * BranchCard-compatible display coordinates; paths are metadata only.\n *\n * @category schemas\n * @since 1.0.0-rc.1\n */\nexport const PresenceWhere = Schema.Union([\n Schema.Struct({\n kind: Schema.Literal(\"file\"),\n path: Schema.NonEmptyString,\n line: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0)))\n }),\n Schema.Struct({ kind: Schema.Literal(\"terminal\"), id: Schema.NonEmptyString }),\n Schema.Struct({ kind: Schema.Literal(\"step\"), label: Schema.NonEmptyString }),\n Schema.Struct({ kind: Schema.Literal(\"branch\") })\n])\n\n// Optional fields preserve decoding of recorded branch-protocol frames.\nconst presenceFields = {\n sessionId: Schema.optionalKey(Schema.NonEmptyString),\n kind: Schema.optionalKey(Schema.Literals([\"person\", \"agent\"])),\n where: Schema.optionalKey(PresenceWhere),\n watching: Schema.optionalKey(Schema.NonEmptyString),\n for_member: Schema.optionalKey(Schema.NonEmptyString),\n agentKind: Schema.optionalKey(\n Schema.Literals([\"smithers\", \"coding\", \"reviewer\", \"claude-code\", \"codex\", \"external\"])\n ),\n via: Schema.optionalKey(Schema.Literals([\"ssh\", \"terminal\", \"cli\"])),\n runId: Schema.optionalKey(Schema.NonEmptyString)\n}\n\n/**\n * Schema for one live participant on a branch.\n *\n * `leaseExpiresAtMs` is the whole lifetime contract: presence is a lease, so a\n * client that disconnects without saying so disappears on its own.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class Participant extends Schema.Class<Participant>(\"@smthrs/sync/BranchProtocol/Participant\")({\n branchId: BranchId,\n participantId: ParticipantId,\n displayName: Schema.NonEmptyString,\n cursor: Schema.NullOr(Cursor),\n ...presenceFields,\n leaseExpiresAtMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))\n}) {}\n\n/**\n * Schema for a capability-bearing request for one branch's roster.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const RosterRequest = Schema.Struct({\n capability: ShareCapability,\n branchId: BranchId,\n /** Only the authenticated host adapter consumes this readiness assertion. */\n sourcesReady: Schema.optionalKey(Schema.Boolean)\n})\n\n/**\n * A capability-bearing request for one branch's roster.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RosterRequest = typeof RosterRequest.Type\n\n/**\n * Schema for a capability-bearing request to drop one participant.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const LeaveRequest = Schema.Struct({\n ...RosterRequest.fields,\n participantId: ParticipantId,\n sessionId: Schema.optionalKey(Schema.NonEmptyString)\n})\n\n/**\n * A capability-bearing request to drop one participant.\n *\n * @category models\n * @since 0.1.0\n */\nexport type LeaveRequest = typeof LeaveRequest.Type\n\n/**\n * Schema for one participant's announcement of itself on a branch.\n *\n * The same shape serves join, heartbeat, and cursor movement: an announcement\n * is idempotent, so a client that reconnects simply announces again.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const Announcement = Schema.Struct({\n ...presenceFields,\n capability: ShareCapability,\n branchId: BranchId,\n participantId: ParticipantId,\n displayName: Schema.NonEmptyString,\n cursor: Schema.NullOr(Cursor)\n})\n\n/**\n * One participant's announcement of itself on a branch.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Announcement = typeof Announcement.Type\n\n/**\n * Schema for one submitted command or flow invocation.\n *\n * Every user action on a shared branch arrives here first — chat messages are\n * the `branch.say` command, renames are `branch.rename`, and a flow launch is\n * that flow's own name. `target` names the shared field a command durably\n * edits and is `\"\"` for commands that only append.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class CommandSubmission extends Schema.Class<CommandSubmission>(\n \"@smthrs/sync/BranchProtocol/CommandSubmission\"\n)({\n branchId: BranchId,\n commandId: CommandId,\n participantId: ParticipantId,\n name: Schema.NonEmptyString,\n args: Schema.String,\n target: Schema.String\n}) {}\n\n/**\n * Schema for a capability-bearing command submission.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const SubmitRequest = Schema.Struct({ capability: ShareCapability, submission: CommandSubmission })\n\n/**\n * A capability-bearing command submission.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SubmitRequest = typeof SubmitRequest.Type\n\n/**\n * The durable payload stored for an admitted command.\n *\n * Distinct from the wire request: `args` and `target` carry decoding defaults,\n * so a row written before those fields existed still decodes rather than\n * failing the whole ledger rebuild.\n *\n * `capabilityId` is the verified id of the share capability that submitted the\n * command, stamped by `BranchCommands.submit` and never read from the client.\n * `participantId` is whatever the client claimed, so attribute a command to\n * its `capabilityId`. A row admitted before the field existed decodes with\n * `\"\"`, which no capability can carry.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class CommandEventPayload extends Schema.Class<CommandEventPayload>(\n \"@smthrs/sync/BranchProtocol/CommandEventPayload\"\n)({\n commandId: CommandId,\n participantId: ParticipantId,\n name: Schema.NonEmptyString,\n args: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(\"\"))),\n target: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(\"\"))),\n capabilityId: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(\"\")))\n}) {}\n\n/**\n * The command that appends a collaborative chat message.\n *\n * @category models\n * @since 0.1.0\n */\nexport const SayCommand = \"branch.say\"\n\n/**\n * Schema for the outcome of admitting a command.\n *\n * `duplicate` is a success, not a failure: it reports the canonical sequence\n * the original submission already occupies so a retransmitting client can\n * settle its optimistic row instead of executing the command twice.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport class CommandReceipt extends Schema.Class<CommandReceipt>(\"@smthrs/sync/BranchProtocol/CommandReceipt\")({\n branchId: BranchId,\n commandId: CommandId,\n status: Schema.Literals([\"admitted\", \"duplicate\"]),\n seq: JournalEvent.Seq\n}) {}\n"]}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The branch collaboration wire contract: the RPC projection of the write
3
+ * and presence paths that complement the read-only {@link SyncRpcs}.
4
+ *
5
+ * Authorization has two stages. Opening a branch is the bootstrap: no
6
+ * capability can exist yet, so `CreateBranch` requires an authenticated
7
+ * workspace principal supplied by the group's {@link SyncAuth} middleware,
8
+ * and its handler refuses any other principal.
9
+ *
10
+ * Every later procedure carries its own share capability and authorizes
11
+ * through {@link BranchShare}: past the bootstrap the capability IS the
12
+ * credential, and cross-branch or expired capabilities are refused by the
13
+ * same service boundary the in-process callers use.
14
+ *
15
+ * @since 0.1.0
16
+ */
17
+ import * as Schema from "effect/Schema";
18
+ import { Rpc, RpcGroup } from "effect/unstable/rpc";
19
+ import { CommandReceipt, Participant, ShareCapability } from "./BranchProtocol.ts";
20
+ import { SyncError } from "./SyncError.ts";
21
+ import { SyncAuth } from "./SyncRpcs.ts";
22
+ /**
23
+ * Maximum lifetime the branch bootstrap may mint.
24
+ *
25
+ * @category constants
26
+ * @since 0.1.0
27
+ */
28
+ export declare const maximumBranchTtlMs: number;
29
+ /**
30
+ * Schema for a request to open a new shared branch.
31
+ *
32
+ * The RPC authentication middleware must establish the workspace principal
33
+ * before the handler mints the first write capability.
34
+ *
35
+ * @category schemas
36
+ * @since 0.1.0
37
+ */
38
+ export declare const CreateBranchPayload: Schema.Struct<{
39
+ readonly ttlMs: Schema.Int;
40
+ }>;
41
+ /**
42
+ * A request to open a new shared branch.
43
+ *
44
+ * @category models
45
+ * @since 0.1.0
46
+ */
47
+ export type CreateBranchPayload = typeof CreateBranchPayload.Type;
48
+ /**
49
+ * Schema for the freshly opened branch and its owner capability.
50
+ *
51
+ * @category schemas
52
+ * @since 0.1.0
53
+ */
54
+ export declare const CreateBranchResponse: Schema.Struct<{
55
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
56
+ readonly capability: typeof ShareCapability;
57
+ }>;
58
+ /**
59
+ * The freshly opened branch and its owner capability.
60
+ *
61
+ * @category models
62
+ * @since 0.1.0
63
+ */
64
+ export type CreateBranchResponse = typeof CreateBranchResponse.Type;
65
+ /**
66
+ * Schema for a request to mint a share link for a branch.
67
+ *
68
+ * The presenting capability must hold write access to the branch — only a
69
+ * collaborator can invite one — and the minted link never outlives the
70
+ * capability it was minted from.
71
+ *
72
+ * @category schemas
73
+ * @since 0.1.0
74
+ */
75
+ export declare const MintSharePayload: Schema.Struct<{
76
+ readonly capability: typeof ShareCapability;
77
+ readonly access: Schema.Literals<readonly ["read", "write"]>;
78
+ readonly ttlMs: Schema.Int;
79
+ }>;
80
+ /**
81
+ * A request to mint a share link for a branch.
82
+ *
83
+ * @category models
84
+ * @since 0.1.0
85
+ */
86
+ export type MintSharePayload = typeof MintSharePayload.Type;
87
+ /**
88
+ * Schema for one roster emission on a branch watch stream.
89
+ *
90
+ * @category schemas
91
+ * @since 0.1.0
92
+ */
93
+ export declare const RosterFrame: Schema.Struct<{
94
+ readonly participants: Schema.$Array<typeof Participant>;
95
+ }>;
96
+ /**
97
+ * One roster emission on a branch watch stream.
98
+ *
99
+ * @category models
100
+ * @since 0.1.0
101
+ */
102
+ export type RosterFrame = typeof RosterFrame.Type;
103
+ /**
104
+ * The remote procedures of branch collaboration.
105
+ *
106
+ * The capability-bearing payloads ARE the `BranchProtocol` request schemas the
107
+ * branch services take, not copies of them. Four wire payloads used to
108
+ * re-declare the message their service already owned, and the announce copy
109
+ * had already drifted: it accepted `displayName: ""` where the service
110
+ * requires a non-empty name, so a wire-legal announce reached a `Participant`
111
+ * constructor and threw a defect outside the declared `SyncError` channel.
112
+ * Naming one schema on both sides makes that drift impossible rather than
113
+ * fixed once, and keeps this group free of the services themselves.
114
+ *
115
+ * @category groups
116
+ * @since 0.1.0
117
+ */
118
+ export declare const BranchRpcs: RpcGroup.RpcGroup<Rpc.Rpc<"Branch.CreateBranch", Schema.Struct<{
119
+ readonly ttlMs: Schema.Int;
120
+ }>, Schema.Struct<{
121
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
122
+ readonly capability: typeof ShareCapability;
123
+ }>, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.MintShare", Schema.Struct<{
124
+ readonly capability: typeof ShareCapability;
125
+ readonly access: Schema.Literals<readonly ["read", "write"]>;
126
+ readonly ttlMs: Schema.Int;
127
+ }>, typeof ShareCapability, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.Submit", Schema.Struct<{
128
+ readonly capability: typeof ShareCapability;
129
+ readonly submission: typeof import("./BranchProtocol.ts").CommandSubmission;
130
+ }>, typeof CommandReceipt, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.Announce", Schema.Struct<{
131
+ readonly capability: typeof ShareCapability;
132
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
133
+ readonly participantId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/ParticipantId">;
134
+ readonly displayName: Schema.NonEmptyString;
135
+ readonly cursor: Schema.NullOr<typeof import("./BranchProtocol.ts").Cursor>;
136
+ readonly sessionId: Schema.optionalKey<Schema.NonEmptyString>;
137
+ readonly kind: Schema.optionalKey<Schema.Literals<readonly ["person", "agent"]>>;
138
+ readonly where: Schema.optionalKey<Schema.Union<readonly [Schema.Struct<{
139
+ readonly kind: Schema.Literal<"file">;
140
+ readonly path: Schema.NonEmptyString;
141
+ readonly line: Schema.optionalKey<Schema.Int>;
142
+ }>, Schema.Struct<{
143
+ readonly kind: Schema.Literal<"terminal">;
144
+ readonly id: Schema.NonEmptyString;
145
+ }>, Schema.Struct<{
146
+ readonly kind: Schema.Literal<"step">;
147
+ readonly label: Schema.NonEmptyString;
148
+ }>, Schema.Struct<{
149
+ readonly kind: Schema.Literal<"branch">;
150
+ }>]>>;
151
+ readonly watching: Schema.optionalKey<Schema.NonEmptyString>;
152
+ readonly for_member: Schema.optionalKey<Schema.NonEmptyString>;
153
+ readonly agentKind: Schema.optionalKey<Schema.Literals<readonly ["smithers", "coding", "reviewer", "claude-code", "codex", "external"]>>;
154
+ readonly via: Schema.optionalKey<Schema.Literals<readonly ["ssh", "terminal", "cli"]>>;
155
+ readonly runId: Schema.optionalKey<Schema.NonEmptyString>;
156
+ }>, typeof Participant, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.Leave", Schema.Struct<{
157
+ readonly participantId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/ParticipantId">;
158
+ readonly sessionId: Schema.optionalKey<Schema.NonEmptyString>;
159
+ readonly capability: typeof ShareCapability;
160
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
161
+ readonly sourcesReady: Schema.optionalKey<Schema.Boolean>;
162
+ }>, Schema.Null, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.Roster", Schema.Struct<{
163
+ readonly capability: typeof ShareCapability;
164
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
165
+ readonly sourcesReady: Schema.optionalKey<Schema.Boolean>;
166
+ }>, Schema.$Array<typeof Participant>, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.PresenceOn", Schema.Struct<{
167
+ readonly capability: typeof ShareCapability;
168
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
169
+ readonly sourcesReady: Schema.optionalKey<Schema.Boolean>;
170
+ }>, Schema.Literals<readonly ["unknown", "present", "empty"]>, typeof SyncError, typeof SyncAuth, never> | Rpc.Rpc<"Branch.WatchRoster", Schema.Struct<{
171
+ readonly capability: typeof ShareCapability;
172
+ readonly branchId: Schema.brand<Schema.NonEmptyString, "@smthrs/sync/BranchProtocol/BranchId">;
173
+ readonly sourcesReady: Schema.optionalKey<Schema.Boolean>;
174
+ }>, import("effect/unstable/rpc/RpcSchema").Stream<Schema.Struct<{
175
+ readonly participants: Schema.$Array<typeof Participant>;
176
+ }>, typeof SyncError>, Schema.Never, typeof SyncAuth, never>>;
177
+ //# sourceMappingURL=BranchRpcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchRpcs.d.ts","sourceRoot":"","sources":["../../src/BranchRpcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAIL,cAAc,EAEd,WAAW,EAEX,eAAe,EAEhB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAAsB,CAAA;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;;EAK9B,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAA;AAEjE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;EAG/B,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB;;;;EAI3B,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAA;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;EAEtB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6DAqBD,CAAA"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The branch collaboration wire contract: the RPC projection of the write
3
+ * and presence paths that complement the read-only {@link SyncRpcs}.
4
+ *
5
+ * Authorization has two stages. Opening a branch is the bootstrap: no
6
+ * capability can exist yet, so `CreateBranch` requires an authenticated
7
+ * workspace principal supplied by the group's {@link SyncAuth} middleware,
8
+ * and its handler refuses any other principal.
9
+ *
10
+ * Every later procedure carries its own share capability and authorizes
11
+ * through {@link BranchShare}: past the bootstrap the capability IS the
12
+ * credential, and cross-branch or expired capabilities are refused by the
13
+ * same service boundary the in-process callers use.
14
+ *
15
+ * @since 0.1.0
16
+ */
17
+ import * as Schema from "effect/Schema";
18
+ import { Rpc, RpcGroup } from "effect/unstable/rpc";
19
+ import { Access, Announcement, BranchId, CommandReceipt, LeaveRequest, Participant, RosterRequest, ShareCapability, SubmitRequest } from "./BranchProtocol.js";
20
+ import { SyncError } from "./SyncError.js";
21
+ import { SyncAuth } from "./SyncRpcs.js";
22
+ /**
23
+ * Maximum lifetime the branch bootstrap may mint.
24
+ *
25
+ * @category constants
26
+ * @since 0.1.0
27
+ */
28
+ export const maximumBranchTtlMs = 24 * 60 * 60 * 1000;
29
+ /**
30
+ * Schema for a request to open a new shared branch.
31
+ *
32
+ * The RPC authentication middleware must establish the workspace principal
33
+ * before the handler mints the first write capability.
34
+ *
35
+ * @category schemas
36
+ * @since 0.1.0
37
+ */
38
+ export const CreateBranchPayload = Schema.Struct({
39
+ ttlMs: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(maximumBranchTtlMs))
40
+ });
41
+ /**
42
+ * Schema for the freshly opened branch and its owner capability.
43
+ *
44
+ * @category schemas
45
+ * @since 0.1.0
46
+ */
47
+ export const CreateBranchResponse = Schema.Struct({
48
+ branchId: BranchId,
49
+ capability: ShareCapability
50
+ });
51
+ /**
52
+ * Schema for a request to mint a share link for a branch.
53
+ *
54
+ * The presenting capability must hold write access to the branch — only a
55
+ * collaborator can invite one — and the minted link never outlives the
56
+ * capability it was minted from.
57
+ *
58
+ * @category schemas
59
+ * @since 0.1.0
60
+ */
61
+ export const MintSharePayload = Schema.Struct({
62
+ capability: ShareCapability,
63
+ access: Access,
64
+ ttlMs: Schema.Int.check(Schema.isGreaterThan(0))
65
+ });
66
+ /**
67
+ * Schema for one roster emission on a branch watch stream.
68
+ *
69
+ * @category schemas
70
+ * @since 0.1.0
71
+ */
72
+ export const RosterFrame = Schema.Struct({
73
+ participants: Schema.Array(Participant)
74
+ });
75
+ /**
76
+ * The remote procedures of branch collaboration.
77
+ *
78
+ * The capability-bearing payloads ARE the `BranchProtocol` request schemas the
79
+ * branch services take, not copies of them. Four wire payloads used to
80
+ * re-declare the message their service already owned, and the announce copy
81
+ * had already drifted: it accepted `displayName: ""` where the service
82
+ * requires a non-empty name, so a wire-legal announce reached a `Participant`
83
+ * constructor and threw a defect outside the declared `SyncError` channel.
84
+ * Naming one schema on both sides makes that drift impossible rather than
85
+ * fixed once, and keeps this group free of the services themselves.
86
+ *
87
+ * @category groups
88
+ * @since 0.1.0
89
+ */
90
+ export const BranchRpcs = RpcGroup.make(Rpc.make("Branch.CreateBranch", {
91
+ payload: CreateBranchPayload,
92
+ success: CreateBranchResponse,
93
+ error: SyncError
94
+ }), Rpc.make("Branch.MintShare", { payload: MintSharePayload, success: ShareCapability, error: SyncError }), Rpc.make("Branch.Submit", { payload: SubmitRequest, success: CommandReceipt, error: SyncError }), Rpc.make("Branch.Announce", { payload: Announcement, success: Participant, error: SyncError }), Rpc.make("Branch.Leave", { payload: LeaveRequest, success: Schema.Null, error: SyncError }), Rpc.make("Branch.Roster", {
95
+ payload: RosterRequest,
96
+ success: Schema.Array(Participant),
97
+ error: SyncError
98
+ }), Rpc.make("Branch.PresenceOn", {
99
+ payload: RosterRequest,
100
+ success: Schema.Literals(["unknown", "present", "empty"]),
101
+ error: SyncError
102
+ }), Rpc.make("Branch.WatchRoster", { payload: RosterRequest, success: RosterFrame, error: SyncError, stream: true })).middleware(SyncAuth);
103
+ //# sourceMappingURL=BranchRpcs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BranchRpcs.js","sourceRoot":"","sources":["../../src/BranchRpcs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The branch collaboration wire contract: the RPC projection of the write\n * and presence paths that complement the read-only {@link SyncRpcs}.\n *\n * Authorization has two stages. Opening a branch is the bootstrap: no\n * capability can exist yet, so `CreateBranch` requires an authenticated\n * workspace principal supplied by the group's {@link SyncAuth} middleware,\n * and its handler refuses any other principal.\n *\n * Every later procedure carries its own share capability and authorizes\n * through {@link BranchShare}: past the bootstrap the capability IS the\n * credential, and cross-branch or expired capabilities are refused by the\n * same service boundary the in-process callers use.\n *\n * @since 0.1.0\n */\n\nimport * as Schema from \"effect/Schema\"\nimport { Rpc, RpcGroup } from \"effect/unstable/rpc\"\nimport {\n Access,\n Announcement,\n BranchId,\n CommandReceipt,\n LeaveRequest,\n Participant,\n RosterRequest,\n ShareCapability,\n SubmitRequest\n} from \"./BranchProtocol.ts\"\nimport { SyncError } from \"./SyncError.ts\"\nimport { SyncAuth } from \"./SyncRpcs.ts\"\n\n/**\n * Maximum lifetime the branch bootstrap may mint.\n *\n * @category constants\n * @since 0.1.0\n */\nexport const maximumBranchTtlMs = 24 * 60 * 60 * 1000\n\n/**\n * Schema for a request to open a new shared branch.\n *\n * The RPC authentication middleware must establish the workspace principal\n * before the handler mints the first write capability.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const CreateBranchPayload = Schema.Struct({\n ttlMs: Schema.Int.check(\n Schema.isGreaterThan(0),\n Schema.isLessThanOrEqualTo(maximumBranchTtlMs)\n )\n})\n\n/**\n * A request to open a new shared branch.\n *\n * @category models\n * @since 0.1.0\n */\nexport type CreateBranchPayload = typeof CreateBranchPayload.Type\n\n/**\n * Schema for the freshly opened branch and its owner capability.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const CreateBranchResponse = Schema.Struct({\n branchId: BranchId,\n capability: ShareCapability\n})\n\n/**\n * The freshly opened branch and its owner capability.\n *\n * @category models\n * @since 0.1.0\n */\nexport type CreateBranchResponse = typeof CreateBranchResponse.Type\n\n/**\n * Schema for a request to mint a share link for a branch.\n *\n * The presenting capability must hold write access to the branch — only a\n * collaborator can invite one — and the minted link never outlives the\n * capability it was minted from.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const MintSharePayload = Schema.Struct({\n capability: ShareCapability,\n access: Access,\n ttlMs: Schema.Int.check(Schema.isGreaterThan(0))\n})\n\n/**\n * A request to mint a share link for a branch.\n *\n * @category models\n * @since 0.1.0\n */\nexport type MintSharePayload = typeof MintSharePayload.Type\n\n/**\n * Schema for one roster emission on a branch watch stream.\n *\n * @category schemas\n * @since 0.1.0\n */\nexport const RosterFrame = Schema.Struct({\n participants: Schema.Array(Participant)\n})\n\n/**\n * One roster emission on a branch watch stream.\n *\n * @category models\n * @since 0.1.0\n */\nexport type RosterFrame = typeof RosterFrame.Type\n\n/**\n * The remote procedures of branch collaboration.\n *\n * The capability-bearing payloads ARE the `BranchProtocol` request schemas the\n * branch services take, not copies of them. Four wire payloads used to\n * re-declare the message their service already owned, and the announce copy\n * had already drifted: it accepted `displayName: \"\"` where the service\n * requires a non-empty name, so a wire-legal announce reached a `Participant`\n * constructor and threw a defect outside the declared `SyncError` channel.\n * Naming one schema on both sides makes that drift impossible rather than\n * fixed once, and keeps this group free of the services themselves.\n *\n * @category groups\n * @since 0.1.0\n */\nexport const BranchRpcs = RpcGroup.make(\n Rpc.make(\"Branch.CreateBranch\", {\n payload: CreateBranchPayload,\n success: CreateBranchResponse,\n error: SyncError\n }),\n Rpc.make(\"Branch.MintShare\", { payload: MintSharePayload, success: ShareCapability, error: SyncError }),\n Rpc.make(\"Branch.Submit\", { payload: SubmitRequest, success: CommandReceipt, error: SyncError }),\n Rpc.make(\"Branch.Announce\", { payload: Announcement, success: Participant, error: SyncError }),\n Rpc.make(\"Branch.Leave\", { payload: LeaveRequest, success: Schema.Null, error: SyncError }),\n Rpc.make(\"Branch.Roster\", {\n payload: RosterRequest,\n success: Schema.Array(Participant),\n error: SyncError\n }),\n Rpc.make(\"Branch.PresenceOn\", {\n payload: RosterRequest,\n success: Schema.Literals([\"unknown\", \"present\", \"empty\"]),\n error: SyncError\n }),\n Rpc.make(\"Branch.WatchRoster\", { payload: RosterRequest, success: RosterFrame, error: SyncError, stream: true })\n).middleware(SyncAuth)\n"]}