@serfab/cadre-core 0.7.1 → 0.9.0

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