@serfab/cadre-core 0.7.1 → 0.8.1

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 (166) hide show
  1. package/README.md +9 -8
  2. package/dist/authority-key.d.ts +41 -0
  3. package/dist/authority-key.js +52 -0
  4. package/dist/authority-key.js.map +1 -0
  5. package/dist/cadre-node.d.ts +770 -11
  6. package/dist/cadre-node.js +1900 -88
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/canonical-datetime.d.ts +33 -0
  9. package/dist/canonical-datetime.js +38 -0
  10. package/dist/canonical-datetime.js.map +1 -0
  11. package/dist/canonical-json.d.ts +12 -0
  12. package/dist/canonical-json.js +33 -0
  13. package/dist/canonical-json.js.map +1 -0
  14. package/dist/control-cohort.d.ts +63 -0
  15. package/dist/control-cohort.js +80 -0
  16. package/dist/control-cohort.js.map +1 -0
  17. package/dist/control-database.d.ts +259 -5
  18. package/dist/control-database.js +541 -128
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +89 -0
  21. package/dist/control-formation-recorder.js +139 -0
  22. package/dist/control-formation-recorder.js.map +1 -0
  23. package/dist/control-schema.d.ts +12 -0
  24. package/dist/control-schema.js +199 -0
  25. package/dist/control-schema.js.map +1 -0
  26. package/dist/control-stream.d.ts +54 -0
  27. package/dist/control-stream.js +104 -0
  28. package/dist/control-stream.js.map +1 -0
  29. package/dist/device-token.d.ts +50 -0
  30. package/dist/device-token.js +66 -0
  31. package/dist/device-token.js.map +1 -0
  32. package/dist/diagnostics/connection-path.d.ts +118 -0
  33. package/dist/diagnostics/connection-path.js +204 -0
  34. package/dist/diagnostics/connection-path.js.map +1 -0
  35. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  36. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  37. package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
  38. package/dist/hibernation-manager.d.ts +79 -0
  39. package/dist/hibernation-manager.js +173 -23
  40. package/dist/hibernation-manager.js.map +1 -1
  41. package/dist/index.d.ts +25 -4
  42. package/dist/index.js +59 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/key-store-file.d.ts +52 -0
  45. package/dist/key-store-file.js +194 -0
  46. package/dist/key-store-file.js.map +1 -0
  47. package/dist/key-store.d.ts +75 -0
  48. package/dist/key-store.js +66 -0
  49. package/dist/key-store.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +26 -0
  51. package/dist/peer-authorization.js +40 -0
  52. package/dist/peer-authorization.js.map +1 -0
  53. package/dist/peer-record.d.ts +89 -0
  54. package/dist/peer-record.js +117 -0
  55. package/dist/peer-record.js.map +1 -0
  56. package/dist/push-credentials.d.ts +34 -0
  57. package/dist/push-credentials.js +81 -0
  58. package/dist/push-credentials.js.map +1 -0
  59. package/dist/push-fanout.d.ts +137 -0
  60. package/dist/push-fanout.js +197 -0
  61. package/dist/push-fanout.js.map +1 -0
  62. package/dist/push-notifier-apns.d.ts +53 -0
  63. package/dist/push-notifier-apns.js +195 -0
  64. package/dist/push-notifier-apns.js.map +1 -0
  65. package/dist/push-notifier-fcm.d.ts +38 -0
  66. package/dist/push-notifier-fcm.js +183 -0
  67. package/dist/push-notifier-fcm.js.map +1 -0
  68. package/dist/push-notifier-shared.d.ts +18 -0
  69. package/dist/push-notifier-shared.js +25 -0
  70. package/dist/push-notifier-shared.js.map +1 -0
  71. package/dist/push-notifier.d.ts +71 -0
  72. package/dist/push-notifier.js +50 -0
  73. package/dist/push-notifier.js.map +1 -0
  74. package/dist/schema-verification.d.ts +11 -2
  75. package/dist/schema-verification.js +15 -4
  76. package/dist/schema-verification.js.map +1 -1
  77. package/dist/seed-bootstrap.d.ts +197 -9
  78. package/dist/seed-bootstrap.js +396 -137
  79. package/dist/seed-bootstrap.js.map +1 -1
  80. package/dist/seed-trust-policy.d.ts +63 -0
  81. package/dist/seed-trust-policy.js +56 -0
  82. package/dist/seed-trust-policy.js.map +1 -0
  83. package/dist/strand-addr-protocol.d.ts +132 -0
  84. package/dist/strand-addr-protocol.js +313 -0
  85. package/dist/strand-addr-protocol.js.map +1 -0
  86. package/dist/strand-cohort.d.ts +54 -0
  87. package/dist/strand-cohort.js +38 -0
  88. package/dist/strand-cohort.js.map +1 -0
  89. package/dist/strand-database.d.ts +54 -6
  90. package/dist/strand-database.js +88 -100
  91. package/dist/strand-database.js.map +1 -1
  92. package/dist/strand-formation-manager.d.ts +79 -19
  93. package/dist/strand-formation-manager.js +196 -104
  94. package/dist/strand-formation-manager.js.map +1 -1
  95. package/dist/strand-formation-protocol.d.ts +162 -0
  96. package/dist/strand-formation-protocol.js +279 -0
  97. package/dist/strand-formation-protocol.js.map +1 -0
  98. package/dist/strand-instance-manager.d.ts +73 -4
  99. package/dist/strand-instance-manager.js +167 -41
  100. package/dist/strand-instance-manager.js.map +1 -1
  101. package/dist/strand-member-key.d.ts +29 -0
  102. package/dist/strand-member-key.js +38 -0
  103. package/dist/strand-member-key.js.map +1 -0
  104. package/dist/strand-member-registry.d.ts +98 -0
  105. package/dist/strand-member-registry.js +129 -0
  106. package/dist/strand-member-registry.js.map +1 -0
  107. package/dist/strand-membership-writer.d.ts +361 -0
  108. package/dist/strand-membership-writer.js +449 -0
  109. package/dist/strand-membership-writer.js.map +1 -0
  110. package/dist/strand-solicitation.d.ts +77 -2
  111. package/dist/strand-solicitation.js +25 -4
  112. package/dist/strand-solicitation.js.map +1 -1
  113. package/dist/strand-wake-payload.d.ts +30 -0
  114. package/dist/strand-wake-payload.js +20 -0
  115. package/dist/strand-wake-payload.js.map +1 -0
  116. package/dist/strand-wake-protocol.d.ts +114 -0
  117. package/dist/strand-wake-protocol.js +257 -0
  118. package/dist/strand-wake-protocol.js.map +1 -0
  119. package/dist/strand-watcher.d.ts +6 -2
  120. package/dist/strand-watcher.js +57 -17
  121. package/dist/strand-watcher.js.map +1 -1
  122. package/dist/types.d.ts +406 -23
  123. package/dist/types.js +22 -5
  124. package/dist/types.js.map +1 -1
  125. package/package.json +14 -8
  126. package/src/authority-key.ts +68 -0
  127. package/src/cadre-node.ts +2937 -948
  128. package/src/canonical-datetime.ts +39 -0
  129. package/src/canonical-json.ts +30 -0
  130. package/src/control-cohort.ts +103 -0
  131. package/src/control-database.ts +931 -383
  132. package/src/control-formation-recorder.ts +151 -0
  133. package/src/control-schema.ts +198 -0
  134. package/src/control-stream.ts +128 -0
  135. package/src/device-token.ts +89 -0
  136. package/src/diagnostics/connection-path.ts +277 -0
  137. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  138. package/src/hibernation-manager.ts +197 -28
  139. package/src/index.ts +273 -79
  140. package/src/key-store-file.ts +203 -0
  141. package/src/key-store.ts +99 -0
  142. package/src/peer-authorization.ts +53 -0
  143. package/src/peer-record.ts +146 -0
  144. package/src/push-credentials.ts +84 -0
  145. package/src/push-fanout.ts +272 -0
  146. package/src/push-notifier-apns.ts +243 -0
  147. package/src/push-notifier-fcm.ts +221 -0
  148. package/src/push-notifier-shared.ts +28 -0
  149. package/src/push-notifier.ts +93 -0
  150. package/src/schema-verification.ts +18 -4
  151. package/src/seed-bootstrap.ts +464 -188
  152. package/src/seed-trust-policy.ts +107 -0
  153. package/src/strand-addr-protocol.ts +410 -0
  154. package/src/strand-cohort.ts +77 -0
  155. package/src/strand-database.ts +113 -141
  156. package/src/strand-formation-manager.ts +233 -142
  157. package/src/strand-formation-protocol.ts +391 -0
  158. package/src/strand-instance-manager.ts +207 -56
  159. package/src/strand-member-key.ts +39 -0
  160. package/src/strand-member-registry.ts +177 -0
  161. package/src/strand-membership-writer.ts +640 -0
  162. package/src/strand-solicitation.ts +93 -4
  163. package/src/strand-wake-payload.ts +32 -0
  164. package/src/strand-wake-protocol.ts +331 -0
  165. package/src/strand-watcher.ts +61 -17
  166. package/src/types.ts +1012 -582
@@ -1,7 +1,36 @@
1
1
  import { Database } from '@quereus/quereus';
2
2
  import type { Libp2p } from '@libp2p/interface';
3
3
  import type { IRepo } from '@optimystic/db-core';
4
- import type { StrandRow } from './types.js';
4
+ import type { StrandRow, PeerAddressRecord, DeviceTokenRecord } from './types.js';
5
+ /**
6
+ * Build the canonical authorization message that authority signatures are bound to.
7
+ *
8
+ * The message is a SINGLE framed SHA-256 digest over the ordered tuple of fields (the
9
+ * crypto plugin's injective multi-field encoding), in a fixed field order, with the
10
+ * single-use StampId as the final field:
11
+ *
12
+ * message = sha256(encodeFields([field_1, field_2, ..., StampId])) // raw digest bytes
13
+ *
14
+ * ed25519 signs these raw digest bytes DIRECTLY (no second hash). The SQL constraints
15
+ * verify the identical bytes with one variadic call
16
+ * (`verify(digest(field_1, ..., StampId), context.Signature, A.Key, 'ed25519')`): SQL
17
+ * `digest(...)` returns the base64url string of the same digest, which `verify`'s default
18
+ * base64url input encoding decodes back to those raw bytes — so signer and verifier
19
+ * operate on the same bytes. Every field is TEXT on both sides (the SQL columns are
20
+ * `cast(... as text)` / `coalesce(...,'')`; the TS args are strings), so the per-field
21
+ * type tags agree. This binds the signature to the row contents (not a bare stamp),
22
+ * closing the captured-stamp replay / privilege-escalation hole. Single source of truth:
23
+ * every signed writer (and every test/harness signer) MUST build the message through this
24
+ * function in the schema's field order, or `verify` will reject the row.
25
+ */
26
+ export declare function buildAuthorizationMessage(fields: string[]): Uint8Array;
27
+ /**
28
+ * Names of the CadreControl tables that {@link ControlDatabase.countRows} can
29
+ * read. Constraining to this union keeps the dynamic `from` clause off the
30
+ * SQL-injection surface and hands callers (e.g. the integration harness's
31
+ * `waitForControlSync`) a typo-proof table argument.
32
+ */
33
+ export type ControlTable = 'AuthorityKey' | 'ValidationKey' | 'Strand' | 'CadrePeer' | 'DeviceToken' | 'FormationInvite' | 'FormationUsage';
5
34
  export interface ControlDatabaseConfig {
6
35
  /** Party ID for the control network */
7
36
  partyId: string;
@@ -35,25 +64,250 @@ export declare class ControlDatabase {
35
64
  * Query all strands from the control database
36
65
  */
37
66
  queryStrands(): Promise<StrandRow[]>;
67
+ /**
68
+ * Read a single strand row by id, or null when absent. Single-row sibling of
69
+ * {@link queryStrands}; the responder uses it to read a host strand's
70
+ * `MemberPrivateKey` (the closed-strand read-gating secret) for delivery to a
71
+ * validated invitee during provision-then-record formation.
72
+ */
73
+ queryStrand(strandId: string): Promise<StrandRow | null>;
74
+ /**
75
+ * Count rows in a CadreControl table as seen by THIS database instance.
76
+ *
77
+ * `table` is validated against {@link CONTROL_TABLES} before it is interpolated
78
+ * into the `from` clause: the names are not user input, but the check keeps the
79
+ * dynamic query off the injection surface and fails loudly on a typo instead of
80
+ * emitting a malformed statement. The count reflects only the rows this node's
81
+ * control DB has converged on — in the integration harness that is the authority
82
+ * node (one ControlDatabase per party), i.e. the authoritative control-network
83
+ * view, not a per-drone convergence guarantee.
84
+ */
85
+ countRows(table: ControlTable): Promise<number>;
38
86
  /**
39
87
  * Get the underlying database for advanced queries
40
88
  */
41
89
  getDatabase(): Database;
90
+ /**
91
+ * Check whether any authority key exists in the control database.
92
+ * Used to decide whether a fresh-party genesis insert is required.
93
+ */
94
+ hasAuthorityKey(): Promise<boolean>;
95
+ /**
96
+ * Idempotent genesis: insert `key` as the founding authority key only when
97
+ * the party has none yet. Returns true if it inserted, false if an authority
98
+ * key already existed (so a repeat `--authority` start is a no-op).
99
+ */
100
+ ensureAuthorityKey(key: string): Promise<boolean>;
101
+ /**
102
+ * Collect every authority key (`CadreControl.AuthorityKey.Key`) as a set.
103
+ *
104
+ * This is the steady-state trust anchor for seeds: a seed's signer key is
105
+ * trusted only if it is already enrolled here (see `SeedTrustPolicy`). It is
106
+ * also the authority-identity source for `queryPeers`, decoupling authority
107
+ * status from the libp2p transport peer ID.
108
+ */
109
+ getAuthorityKeys(): Promise<Set<string>>;
110
+ /**
111
+ * Enumerate the CadrePeer rows (cadre membership) for admin/membership reads.
112
+ */
113
+ queryCadrePeers(): Promise<Array<{
114
+ peerId: string;
115
+ multiaddr: string | null;
116
+ }>>;
117
+ /**
118
+ * Read a single peer's address record (the full `CadrePeer` row) by PeerId.
119
+ *
120
+ * Returns null when no row exists. Missing/legacy column values are coalesced
121
+ * to their empty form (`''` key/sig, `[]` addrs, `0` stamp) so the caller's
122
+ * verify/freshness gates uniformly reject an unpublished or malformed row.
123
+ * The split `addrs` re-join to the exact stored `Multiaddr` (split-on-`,` is
124
+ * the inverse of join-on-`,`), so the resolver re-verifies over the same bytes
125
+ * the publisher signed.
126
+ */
127
+ queryPeerRecord(peerId: string): Promise<PeerAddressRecord | null>;
128
+ /**
129
+ * Apply a peer's own self-signed address-record update to an existing row.
130
+ *
131
+ * Authorization is carried entirely by the record: the `Sig` column (verified
132
+ * by the `AuthorizedUpdate` self-branch against the stored `PublicKey`) plus
133
+ * the strictly-increasing `UpdatedAt`. No authority key is involved, so this
134
+ * is the refresh path for any member — authority or drone — once its row
135
+ * exists. `PublicKey` is intentionally not in the SET list (it is immutable on
136
+ * self-update and the constraint enforces `new.PublicKey = old.PublicKey`).
137
+ */
138
+ updateSelfPeerRecord(record: PeerAddressRecord): Promise<void>;
139
+ /**
140
+ * Read a single peer's device push token (the full `DeviceToken` row) by PeerId.
141
+ *
142
+ * Returns null when no row exists. Missing/legacy column values are coalesced to
143
+ * their empty form (`''` token/sig, `0` stamp) so the caller's verify/freshness
144
+ * gates uniformly reject an unpublished or malformed row. `platform` is returned
145
+ * verbatim (the resolver validates it against {@link PushPlatform} and re-verifies
146
+ * the self-signature, which covers the platform field).
147
+ */
148
+ queryDeviceToken(peerId: string): Promise<DeviceTokenRecord | null>;
149
+ /**
150
+ * Apply a peer's own self-signed device-token update to an existing row.
151
+ *
152
+ * Authorization is carried entirely by the record: the `Sig` column (verified by
153
+ * the `DeviceToken.AuthorizedUpdate` self-branch against the stored
154
+ * `CadrePeer.PublicKey`) plus the strictly-increasing `UpdatedAt`. No authority key
155
+ * is involved, so this is the refresh / rotation path for any member once both its
156
+ * `CadrePeer` row (for the PublicKey) and its `DeviceToken` row exist. `PeerId` is
157
+ * intentionally not in the SET list (immutable; the constraint enforces
158
+ * `new.PeerId = old.PeerId`). Mirrors {@link updateSelfPeerRecord}.
159
+ */
160
+ updateSelfDeviceToken(record: DeviceTokenRecord): Promise<void>;
42
161
  /**
43
162
  * Insert the initial authority key (bootstrap - no existing authorities required)
44
163
  */
45
164
  insertAuthorityKey(key: string): Promise<void>;
46
165
  /**
47
- * Insert a strand into the control database using authority signature.
48
- * This starts a transaction, gets the StampId, signs it, and inserts the strand.
166
+ * Insert a strand into the control database using an authority signature.
167
+ *
168
+ * The authority signs the canonical row-bound authorization message (see
169
+ * {@link buildAuthorizationMessage}) — NOT a bare stamp — so the signature is bound to
170
+ * this strand's contents and cannot be transplanted onto an attacker-chosen row. The
171
+ * StampId is persisted as a unique column for single-use anti-replay.
49
172
  *
50
173
  * @param strandId - Unique identifier for the strand
51
174
  * @param type - Strand type: 'o' for open, 'c' for closed
52
175
  * @param authorityKey - Public key of the authorizing authority
53
- * @param signStampId - Function to sign the stamp ID with the authority's private key
176
+ * @param signMessage - Function that ed25519-signs the raw message bytes (no pre-hash)
177
+ * with the authority's private key, returning a base64url signature
54
178
  * @param memberPrivateKey - Optional private key for membership in closed strands
55
179
  */
56
- insertStrand(strandId: string, type: 'o' | 'c', authorityKey: string, signStampId: (stampId: string) => string, memberPrivateKey?: string): Promise<void>;
180
+ insertStrand(strandId: string, type: 'o' | 'c', authorityKey: string, signMessage: (message: Uint8Array) => string, memberPrivateKey?: string): Promise<void>;
181
+ /**
182
+ * Insert a validation key into the control database using an authority signature.
183
+ *
184
+ * Mirrors {@link insertStrand}: the authority signs the canonical row-bound
185
+ * authorization message over (Key, StampId), and the StampId is persisted as a unique
186
+ * column for single-use anti-replay. A `ValidationKey` authorizes verifying strand
187
+ * formation disclosures.
188
+ *
189
+ * @param key - The validation public key to enroll
190
+ * @param authorityKey - Public key of the authorizing authority
191
+ * @param signMessage - Function that ed25519-signs the raw message bytes (no pre-hash)
192
+ * with the authority's private key, returning a base64url signature
193
+ */
194
+ insertValidationKey(key: string, authorityKey: string, signMessage: (message: Uint8Array) => string): Promise<void>;
195
+ /**
196
+ * Insert an authority-signed `FormationInvite` (open invitation token).
197
+ *
198
+ * The invite is the on-network record that later authorizes an
199
+ * authority-signature-FREE `Strand` creation: an invited cadre peer redeems it
200
+ * by inserting a matching `FormationUsage` row (see {@link redeemInvitation}),
201
+ * which satisfies the consent branch of `Strand.Authorized`.
202
+ *
203
+ * Like {@link insertStrand}/{@link insertValidationKey}, the authority signs the
204
+ * canonical row-bound authorization message (see {@link buildAuthorizationMessage})
205
+ * over (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId) — NOT a
206
+ * bare stamp — so the signature is bound to this invite's contents and cannot be
207
+ * transplanted onto an attacker-chosen row. The StampId is persisted as a unique
208
+ * column for single-use anti-replay. `FormationInvite.AuthorizedAddOrRemove` gates
209
+ * both insert and delete; its `coalesce(new, old)` verify binds the NEW row on
210
+ * insert and the OLD row on delete.
211
+ *
212
+ * `StrandId` binds the invite to a pre-existing host strand (provision-then-record):
213
+ * when set, a responder redeeming this token records a `FormationUsage` against that
214
+ * strand and returns it (see {@link ControlFormationUsageRecorder.resolveStrand}); a
215
+ * null `StrandId` (the default) leaves the legacy responder-provisions path in place.
216
+ * Like ValidationUrl it is a nullable bound field, signed as `''` when absent.
217
+ *
218
+ * The ExpiresAt and TotalUses message fields must byte-match what the (auto-deferred,
219
+ * because it has a subquery) CHECK sees AFTER column coercion: TotalUses becomes a
220
+ * decimal string (`String(totalUses)` ⇔ `cast(new.TotalUses as text)`) and ExpiresAt
221
+ * becomes the engine's canonical `PlainDateTime` string — sourced here from
222
+ * {@link canonicalDatetime} (a `select datetime(?)` round-trip) rather than a hand-rolled
223
+ * ISO formatter, so signer and verifier agree exactly. A null ExpiresAt / TotalUses /
224
+ * ValidationUrl signs as `''`, matching the schema's `coalesce(..., '')`.
225
+ *
226
+ * @param token - Invitation token (the `FormationInvite` primary key)
227
+ * @param sAppId - The sApp a redeemed strand will use
228
+ * @param authorityKey - Public key of the authorizing authority
229
+ * @param signMessage - ed25519-signs the raw message bytes (no pre-hash),
230
+ * returning a base64url signature — the same callback shape {@link insertStrand} uses
231
+ * @param options - Optional `expiresAtMs` (epoch ms), `totalUses`, `validationUrl`,
232
+ * `strandId` (bind to a pre-existing host strand for provision-then-record)
233
+ */
234
+ insertFormationInvite(token: string, sAppId: string, authorityKey: string, signMessage: (message: Uint8Array) => string, options?: {
235
+ expiresAtMs?: number;
236
+ totalUses?: number;
237
+ validationUrl?: string;
238
+ strandId?: string;
239
+ }): Promise<void>;
240
+ /**
241
+ * Redeem a `FormationInvite` by inserting the `Strand` row and a matching
242
+ * `FormationUsage` row **atomically, in one transaction**.
243
+ *
244
+ * The two CHECK constraints are mutually circular under immediate evaluation:
245
+ * `Strand.Authorized`'s consent branch requires the `FormationUsage` row, while
246
+ * `FormationUsage.StrandExists` requires the `Strand` row. Both CHECKs contain
247
+ * subqueries, so Quereus auto-defers them to transaction commit — wrapping both
248
+ * inserts in a single explicit `begin … commit` lets both deferred CHECKs see
249
+ * both rows at commit. The strand is authorised WITHOUT an authority signature
250
+ * (the `FormationUsage` branch of `Strand.Authorized`) but still gets a fresh,
251
+ * unique `StampId` column to satisfy the not-null/unique anti-replay column.
252
+ *
253
+ * `UseNumber` is computed as `max(UseNumber)+1` for the token (the `Monotonic`
254
+ * constraint); callers redeeming concurrently against the same token must
255
+ * serialise, since the next use number is read before the insert.
256
+ */
257
+ redeemInvitation(params: {
258
+ token: string;
259
+ strandId: string;
260
+ type?: 'o' | 'c';
261
+ memberPrivateKey?: string;
262
+ disclosure?: string;
263
+ peerId?: string;
264
+ peerSignature?: string;
265
+ nowMs?: number;
266
+ validationKey?: string;
267
+ validationSignature?: string;
268
+ }): Promise<void>;
269
+ /**
270
+ * Record a `FormationUsage` against an **already-existing** `Strand` (no strand
271
+ * insert). This is the redemption path when the strand was provisioned
272
+ * separately (e.g. authority-signed) and the consent record is added after the
273
+ * fact: the single insert auto-commits, and the deferred `StrandExists` CHECK
274
+ * is satisfied by the pre-existing committed strand row. Returns the assigned
275
+ * `UseNumber`.
276
+ *
277
+ * Use {@link redeemInvitation} instead when the strand must be created by
278
+ * consent atomically with the usage.
279
+ */
280
+ recordFormationUsage(params: {
281
+ token: string;
282
+ strandId: string;
283
+ disclosure?: string;
284
+ peerId?: string;
285
+ peerSignature?: string;
286
+ nowMs?: number;
287
+ validationKey?: string;
288
+ validationSignature?: string;
289
+ }): Promise<number>;
290
+ /** Parameterised `FormationUsage` insert shared by redeem + record paths. */
291
+ private execFormationUsageInsert;
292
+ /**
293
+ * Read a `FormationInvite` row by token, or null when absent. `expiresAtMs` is
294
+ * the parsed epoch-ms of the stored `datetime` (null when the invite never
295
+ * expires); the caller compares it against the wall clock for freshness.
296
+ */
297
+ queryFormationInvite(token: string): Promise<{
298
+ token: string;
299
+ sAppId: string;
300
+ expiresAtMs: number | null;
301
+ totalUses: number | null;
302
+ validationUrl: string | null;
303
+ strandId: string | null;
304
+ } | null>;
305
+ /**
306
+ * Count `FormationUsage` rows recorded against a token (uses consumed so far).
307
+ */
308
+ countFormationUsage(token: string): Promise<number>;
309
+ /** Next `UseNumber` for a token = max(existing)+1, per the `Monotonic` constraint. */
310
+ private nextUseNumber;
57
311
  /**
58
312
  * Close the database and cleanup resources
59
313
  */