@serfab/cadre-core 0.9.0 → 0.10.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 (162) hide show
  1. package/README.md +4 -2
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +918 -53
  9. package/dist/cadre-node.js +1843 -190
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-database.d.ts +990 -50
  15. package/dist/control-database.js +1459 -108
  16. package/dist/control-database.js.map +1 -1
  17. package/dist/control-formation-recorder.d.ts +89 -10
  18. package/dist/control-formation-recorder.js +129 -14
  19. package/dist/control-formation-recorder.js.map +1 -1
  20. package/dist/control-schema.d.ts +1 -1
  21. package/dist/control-schema.js +774 -210
  22. package/dist/control-schema.js.map +1 -1
  23. package/dist/control-stream.d.ts +38 -4
  24. package/dist/control-stream.js +70 -4
  25. package/dist/control-stream.js.map +1 -1
  26. package/dist/control-write-retry.d.ts +151 -0
  27. package/dist/control-write-retry.js +411 -0
  28. package/dist/control-write-retry.js.map +1 -0
  29. package/dist/delegate-admission.d.ts +134 -0
  30. package/dist/delegate-admission.js +244 -0
  31. package/dist/delegate-admission.js.map +1 -0
  32. package/dist/device-token.d.ts +11 -11
  33. package/dist/device-token.js +14 -13
  34. package/dist/device-token.js.map +1 -1
  35. package/dist/ed25519-key.d.ts +26 -0
  36. package/dist/ed25519-key.js +56 -1
  37. package/dist/ed25519-key.js.map +1 -1
  38. package/dist/file-durable-slot.d.ts +20 -0
  39. package/dist/file-durable-slot.js +53 -0
  40. package/dist/file-durable-slot.js.map +1 -0
  41. package/dist/formation-approval.d.ts +157 -0
  42. package/dist/formation-approval.js +375 -0
  43. package/dist/formation-approval.js.map +1 -0
  44. package/dist/fs-atomic.d.ts +38 -0
  45. package/dist/fs-atomic.js +97 -0
  46. package/dist/fs-atomic.js.map +1 -0
  47. package/dist/identity-key.d.ts +49 -0
  48. package/dist/identity-key.js +81 -0
  49. package/dist/identity-key.js.map +1 -0
  50. package/dist/index.d.ts +16 -6
  51. package/dist/index.js +39 -5
  52. package/dist/index.js.map +1 -1
  53. package/dist/key-store-file.d.ts +2 -24
  54. package/dist/key-store-file.js +10 -77
  55. package/dist/key-store-file.js.map +1 -1
  56. package/dist/membership-connection-gater.d.ts +146 -0
  57. package/dist/membership-connection-gater.js +180 -0
  58. package/dist/membership-connection-gater.js.map +1 -0
  59. package/dist/node-local-snapshot.d.ts +109 -0
  60. package/dist/node-local-snapshot.js +182 -0
  61. package/dist/node-local-snapshot.js.map +1 -0
  62. package/dist/peer-authorization.d.ts +135 -18
  63. package/dist/peer-authorization.js +152 -21
  64. package/dist/peer-authorization.js.map +1 -1
  65. package/dist/peer-record.d.ts +21 -8
  66. package/dist/peer-record.js +26 -10
  67. package/dist/peer-record.js.map +1 -1
  68. package/dist/relay-addrs.d.ts +45 -0
  69. package/dist/relay-addrs.js +103 -0
  70. package/dist/relay-addrs.js.map +1 -0
  71. package/dist/relay-reservation.d.ts +266 -0
  72. package/dist/relay-reservation.js +613 -0
  73. package/dist/relay-reservation.js.map +1 -0
  74. package/dist/seed-bootstrap.d.ts +221 -56
  75. package/dist/seed-bootstrap.js +328 -188
  76. package/dist/seed-bootstrap.js.map +1 -1
  77. package/dist/seed-trust-policy.d.ts +47 -21
  78. package/dist/seed-trust-policy.js +24 -15
  79. package/dist/seed-trust-policy.js.map +1 -1
  80. package/dist/strand-addr-protocol.d.ts +42 -13
  81. package/dist/strand-addr-protocol.js +58 -52
  82. package/dist/strand-addr-protocol.js.map +1 -1
  83. package/dist/strand-backfill.d.ts +109 -0
  84. package/dist/strand-backfill.js +279 -0
  85. package/dist/strand-backfill.js.map +1 -0
  86. package/dist/strand-formation-manager.d.ts +38 -9
  87. package/dist/strand-formation-manager.js +120 -17
  88. package/dist/strand-formation-manager.js.map +1 -1
  89. package/dist/strand-formation-protocol.d.ts +103 -4
  90. package/dist/strand-formation-protocol.js +232 -4
  91. package/dist/strand-formation-protocol.js.map +1 -1
  92. package/dist/strand-instance-manager.d.ts +39 -1
  93. package/dist/strand-instance-manager.js +92 -14
  94. package/dist/strand-instance-manager.js.map +1 -1
  95. package/dist/strand-member-registry.d.ts +5 -5
  96. package/dist/strand-member-registry.js +18 -10
  97. package/dist/strand-member-registry.js.map +1 -1
  98. package/dist/strand-membership-writer.d.ts +482 -88
  99. package/dist/strand-membership-writer.js +757 -136
  100. package/dist/strand-membership-writer.js.map +1 -1
  101. package/dist/strand-solicitation.d.ts +99 -33
  102. package/dist/strand-solicitation.js +100 -52
  103. package/dist/strand-solicitation.js.map +1 -1
  104. package/dist/strand-transport-key.d.ts +40 -0
  105. package/dist/strand-transport-key.js +56 -0
  106. package/dist/strand-transport-key.js.map +1 -0
  107. package/dist/strand-wake-protocol.d.ts +9 -7
  108. package/dist/strand-wake-protocol.js +22 -46
  109. package/dist/strand-wake-protocol.js.map +1 -1
  110. package/dist/strand-watcher.d.ts +17 -2
  111. package/dist/strand-watcher.js +47 -3
  112. package/dist/strand-watcher.js.map +1 -1
  113. package/dist/trusted-owner-store-file.d.ts +12 -0
  114. package/dist/trusted-owner-store-file.js +39 -0
  115. package/dist/trusted-owner-store-file.js.map +1 -0
  116. package/dist/trusted-owner-store.d.ts +64 -0
  117. package/dist/trusted-owner-store.js +126 -0
  118. package/dist/trusted-owner-store.js.map +1 -0
  119. package/dist/types.d.ts +275 -20
  120. package/dist/types.js +13 -0
  121. package/dist/types.js.map +1 -1
  122. package/package.json +19 -8
  123. package/src/bootstrap-peer-store-file.ts +44 -0
  124. package/src/bootstrap-peer-store.ts +172 -0
  125. package/src/cadre-node.ts +1992 -202
  126. package/src/control-authorization.ts +114 -0
  127. package/src/control-database.ts +1704 -146
  128. package/src/control-formation-recorder.ts +172 -19
  129. package/src/control-schema.ts +786 -222
  130. package/src/control-stream.ts +81 -4
  131. package/src/control-write-retry.ts +473 -0
  132. package/src/delegate-admission.ts +302 -0
  133. package/src/device-token.ts +18 -13
  134. package/src/ed25519-key.ts +134 -69
  135. package/src/file-durable-slot.ts +61 -0
  136. package/src/formation-approval.ts +604 -0
  137. package/src/fs-atomic.ts +107 -0
  138. package/src/identity-key.ts +109 -0
  139. package/src/index.ts +136 -5
  140. package/src/key-store-file.ts +10 -81
  141. package/src/membership-connection-gater.ts +204 -0
  142. package/src/node-local-snapshot.ts +260 -0
  143. package/src/peer-authorization.ts +199 -21
  144. package/src/peer-record.ts +28 -10
  145. package/src/relay-addrs.ts +111 -0
  146. package/src/relay-reservation.ts +823 -0
  147. package/src/seed-bootstrap.ts +397 -197
  148. package/src/seed-trust-policy.ts +58 -25
  149. package/src/strand-addr-protocol.ts +84 -53
  150. package/src/strand-backfill.ts +366 -0
  151. package/src/strand-formation-manager.ts +145 -19
  152. package/src/strand-formation-protocol.ts +309 -8
  153. package/src/strand-instance-manager.ts +111 -14
  154. package/src/strand-member-registry.ts +18 -13
  155. package/src/strand-membership-writer.ts +1430 -640
  156. package/src/strand-solicitation.ts +155 -90
  157. package/src/strand-transport-key.ts +58 -0
  158. package/src/strand-wake-protocol.ts +29 -46
  159. package/src/strand-watcher.ts +66 -3
  160. package/src/trusted-owner-store-file.ts +41 -0
  161. package/src/trusted-owner-store.ts +171 -0
  162. package/src/types.ts +292 -21
@@ -1,6 +1,31 @@
1
1
  import debug from 'debug';
2
- import { FormationListener, dialFormation, isValidResponderCreatesResult } from './strand-formation-protocol.js';
2
+ import { fromString as uint8ArrayFromString } from 'uint8arrays';
3
+ import { FormationApprovalError } from './formation-approval.js';
4
+ // control-database does not import this manager, so this import introduces no cycle.
5
+ import { FormationAbortedError, InvitationExhaustedError } from './control-database.js';
6
+ import { canonicalJson } from './canonical-json.js';
7
+ import { FormationListener, INVALID_TOKEN_REASON, PROVISION_RESPONSE_TRAVEL_MARGIN_MS, dialFormation, isValidResponderCreatesResult } from './strand-formation-protocol.js';
3
8
  const log = debug('sereus:cadre:formation-manager');
9
+ /**
10
+ * Largest serialized disclosure a responder will accept (8 KiB). The disclosure is
11
+ * attacker-influenced text destined for a replicated table (`FormationUsage.Disclosure`)
12
+ * and for the approval hook's signed digest — the cap bounds both, and is enforced BEFORE
13
+ * any database read or hook contact so an oversized blob costs nothing downstream.
14
+ */
15
+ const MAX_DISCLOSURE_BYTES = 8 * 1024;
16
+ /**
17
+ * Rejection reason a would-be joiner is told for each approval-failure category
18
+ * (see {@link FormationApprovalFailure}). Distinct per category on purpose: mapping any
19
+ * of these onto the generic 'Formation conflict, retry' would tell an operator to retry
20
+ * what retrying can never fix (e.g. a non-enrolled validation key).
21
+ */
22
+ const APPROVAL_REJECTION_REASONS = {
23
+ refused: 'Formation approval refused',
24
+ unavailable: 'Formation approval unavailable, retry',
25
+ malformed: 'Formation approval invalid',
26
+ unenrolled: 'Formation approval key is not enrolled',
27
+ misconfigured: 'Formation approval misconfigured'
28
+ };
4
29
  /**
5
30
  * StrandFormationManager drives the native cadre-core formation transport
6
31
  * (`strand-formation-protocol.ts`) from cadre-core's strand-solicitation interfaces.
@@ -28,10 +53,11 @@ export class StrandFormationManager {
28
53
  this.listener = new FormationListener({
29
54
  validateToken: (token) => this.validateToken(token),
30
55
  validateDisclosure: (token, disclosure) => this.validateDisclosure(token, disclosure),
31
- provisionStrand: (token, initiatorPartyId, disclosure) => this.provisionAsResponder(token, initiatorPartyId, disclosure),
56
+ provisionStrand: (contact, signal) => this.provisionAsResponder(contact, signal),
32
57
  getResponderIdentity: () => ({ partyId: this.partyId, cadrePeerAddrs: this.cadrePeerAddrs }),
33
58
  sessionTimeoutMs: this.config.sessionTimeoutMs,
34
59
  stepTimeoutMs: this.config.stepTimeoutMs,
60
+ provisionTimeoutMs: this.config.provisionTimeoutMs,
35
61
  maxConcurrentSessions: this.config.maxConcurrentSessions
36
62
  });
37
63
  log('StrandFormationManager created for party: %s', this.partyId);
@@ -64,14 +90,19 @@ export class StrandFormationManager {
64
90
  * Form a strand with a responder via an open invitation (initiator side).
65
91
  *
66
92
  * Builds a contact message carrying the real token + disclosure + this party's
67
- * real cadre addresses, dials the responder over the native protocol, and
68
- * validates the responder's result before returning.
93
+ * real cadre addresses + the joiner's consent (minted and signed by the
94
+ * solicitation layer — this manager only places it on the contact), dials the
95
+ * responder over the native protocol, and validates the responder's result
96
+ * before returning.
69
97
  */
70
- async formStrand(invitation, disclosure, node) {
98
+ async formStrand(invitation, disclosure, consent, node) {
71
99
  log('Forming strand with invitation token: %s', invitation.token);
72
100
  const contact = {
73
101
  token: invitation.token,
74
102
  partyId: disclosure.partyId ?? this.partyId,
103
+ peerKey: consent.peerKey,
104
+ usageStampId: consent.usageStampId,
105
+ peerSignature: consent.peerSignature,
75
106
  disclosure,
76
107
  cadrePeerAddrs: this.cadrePeerAddrs
77
108
  };
@@ -83,6 +114,7 @@ export class StrandFormationManager {
83
114
  validateResponse: (response) => this.validateResponse(invitation, disclosure, response),
84
115
  sessionTimeoutMs: this.config.sessionTimeoutMs,
85
116
  stepTimeoutMs: this.config.stepTimeoutMs,
117
+ provisionTimeoutMs: this.initiatorProvisionTimeoutMs(),
86
118
  protocolId: this.config.protocolId
87
119
  });
88
120
  log('Strand formed: %s', provision.strand.strandId);
@@ -106,6 +138,19 @@ export class StrandFormationManager {
106
138
  getActiveSessionCounts() {
107
139
  return { listeners: this.listener.activeCount, dialers: this.dialerSessions };
108
140
  }
141
+ /**
142
+ * Derive the initiator's await-response budget from the configured RESPONDER budget —
143
+ * never the same number (see `StrandFormationManagerConfig.provisionTimeoutMs`). Mirrors
144
+ * `resolveProvisionTimeoutMs`'s own "`0`/negative means unset" rule so an unset config
145
+ * still lets both sides fall back to their own independent defaults; must NOT hardcode
146
+ * `DEFAULT_PROVISION_TIMEOUT_MS` here, or the per-role clamping downstream is defeated.
147
+ * A value too large for the session is clamped per role, and the responder's ceiling holds
148
+ * the same margin back, so the ordering survives there too.
149
+ */
150
+ initiatorProvisionTimeoutMs() {
151
+ const host = this.config.provisionTimeoutMs;
152
+ return host && host > 0 ? host + PROVISION_RESPONSE_TRAVEL_MARGIN_MS : undefined;
153
+ }
109
154
  // ── Responder-side hooks ─────────────────────────────────────────────────────
110
155
  async validateToken(token) {
111
156
  if (!this.formationUsageRecorder) {
@@ -145,14 +190,29 @@ export class StrandFormationManager {
145
190
  * deferred `StrandExists` CHECK at commit and drop the frame.
146
191
  * - **unbound** (no binding): the responder-provisions fallback — see {@link provisionUnbound}.
147
192
  *
148
- * Defense-in-depth: known provisioning/redeem failures (notably the concurrent
149
- * `(Token, UseNumber)` PK collision when two redemptions of one unbound single-use invite
150
- * race) are caught and mapped to a logged, retry-suggesting rejection rather than a thrown
151
- * insert + a silently-closed stream. The LOG-before-reject keeps this a deliberate
152
- * internal-error→protocol-rejection conversion (AGENTS.md: don't eat exceptions silently),
153
- * not control-flow-by-exception.
193
+ * Defense-in-depth: known provisioning/redeem failures are caught and mapped to a logged
194
+ * protocol rejection rather than a thrown insert + a silently-closed stream. The mapping is
195
+ * per-failure, NOT blanket retry-suggesting: an exhausted invitation
196
+ * ({@link InvitationExhaustedError}, raised by `ControlDatabase` whenever a redemption's use
197
+ * number is past the invite's seat budget — including a first attempt racing another
198
+ * redemption of the same token on this node, not only a retry) is reported as `'Invalid
199
+ * token'`, because retrying it can never succeed. The
200
+ * `(Token, UseNumber)` PK collision that concurrent redemptions of one invite used to
201
+ * surface here is now retried a layer down and no longer reaches this method. The
202
+ * LOG-before-reject keeps this a deliberate internal-error→protocol-rejection conversion
203
+ * (AGENTS.md: don't eat exceptions silently), not control-flow-by-exception.
154
204
  */
155
- async provisionAsResponder(token, initiatorPartyId, _disclosure) {
205
+ async provisionAsResponder(contact, signal) {
206
+ const token = contact.token;
207
+ // CANONICALLY serialized — both sides derive the identical string: this is the exact
208
+ // text the joiner signed (consent), the approver signs (vouch), and the recorder writes
209
+ // to `FormationUsage.Disclosure` — a re-serialization anywhere below would break the
210
+ // signature-over-stored-bytes invariant.
211
+ const disclosureText = canonicalJson(contact.disclosure);
212
+ if (uint8ArrayFromString(disclosureText, 'utf8').byteLength > MAX_DISCLOSURE_BYTES) {
213
+ log('Disclosure over %d bytes; rejecting token %s', MAX_DISCLOSURE_BYTES, token);
214
+ return { approved: false, reason: 'Disclosure too large' };
215
+ }
156
216
  const recorder = this.formationUsageRecorder;
157
217
  const resolved = recorder?.resolveStrand
158
218
  ? await recorder.resolveStrand(token)
@@ -161,7 +221,15 @@ export class StrandFormationManager {
161
221
  switch (resolved.kind) {
162
222
  case 'bound': {
163
223
  // recorder is guaranteed non-null here: only resolveStrand can yield 'bound'.
164
- await recorder.recordUsage(token, initiatorPartyId, resolved.strandId);
224
+ await recorder.recordUsage({
225
+ token,
226
+ peerKey: contact.peerKey,
227
+ peerSignature: contact.peerSignature,
228
+ usageStampId: contact.usageStampId,
229
+ strandId: resolved.strandId,
230
+ disclosure: disclosureText,
231
+ signal
232
+ });
165
233
  return this.approve({
166
234
  strand: { strandId: resolved.strandId, createdBy: 'responder' },
167
235
  memberPrivateKey: resolved.memberPrivateKey ?? undefined,
@@ -173,10 +241,36 @@ export class StrandFormationManager {
173
241
  return { approved: false, reason: 'Host strand not yet available on this responder' };
174
242
  }
175
243
  case 'unbound':
176
- return await this.provisionUnbound(token, initiatorPartyId);
244
+ return await this.provisionUnbound(contact, disclosureText, signal);
177
245
  }
178
246
  }
179
247
  catch (err) {
248
+ if (err instanceof FormationAbortedError) {
249
+ // The listener's timeout path owns the reply for an abandoned provisioning; mapping
250
+ // this onto 'Formation conflict, retry' would misreport it as a conflict.
251
+ throw err;
252
+ }
253
+ if (err instanceof FormationApprovalError) {
254
+ log('approval failed (%s) for token %s: %o', err.failure, token, err);
255
+ return { approved: false, reason: APPROVAL_REJECTION_REASONS[err.failure] };
256
+ }
257
+ if (err instanceof InvitationExhaustedError) {
258
+ // Shares `INVALID_TOKEN_REASON` with the up-front `validateToken` rejection so the loser
259
+ // of the race that exposed the spent invite — same-node, first attempt, or a retry that
260
+ // ran out — sees exactly what a non-racing latecomer sees. No wire-visible distinction between "invalid" and "exhausted" — the
261
+ // operator signal lives in this log line instead.
262
+ // NOTE: if a joining client ever has to tell "never valid" from "used up" WITHOUT node
263
+ // logs, that is a new protocol reason string, not a local change here.
264
+ log('invitation exhausted for token %s: use #%d past limit of %d', err.token, err.useNumber, err.totalUses);
265
+ return { approved: false, reason: INVALID_TOKEN_REASON };
266
+ }
267
+ // An approval obtained before landing here is RETRIED, not discarded: the database
268
+ // layer re-presents the same approver sign-off under a fresh use number when it loses
269
+ // one to a writer its local write queue does not serialize — another node of the cadre,
270
+ // another `Database` handle over the same store (see
271
+ // `ControlDatabase.withUseNumberRetry`). Reaching this catch-all means either that retry
272
+ // ran out (surfaced above as `InvitationExhaustedError`) or the failure was never
273
+ // retryable to begin with.
180
274
  log('provisionAsResponder failed for token %s: %o', token, err);
181
275
  return { approved: false, reason: 'Formation conflict, retry' };
182
276
  }
@@ -202,11 +296,20 @@ export class StrandFormationManager {
202
296
  * the responder-provisions placeholder as failure). The broader "should this fallback exist
203
297
  * at all?" question lives in backlog `formation-initiatorcreates-cover-or-remove`.
204
298
  */
205
- async provisionUnbound(token, initiatorPartyId) {
299
+ async provisionUnbound(contact, disclosureText, signal) {
300
+ const token = contact.token;
206
301
  const recorder = this.formationUsageRecorder;
207
302
  if (recorder?.provisionAndRecord) {
208
303
  const sAppId = await this.resolveInviteSAppId(token);
209
- const provisioned = await recorder.provisionAndRecord(token, initiatorPartyId, sAppId);
304
+ const provisioned = await recorder.provisionAndRecord({
305
+ token,
306
+ peerKey: contact.peerKey,
307
+ peerSignature: contact.peerSignature,
308
+ usageStampId: contact.usageStampId,
309
+ sAppId,
310
+ disclosure: disclosureText,
311
+ signal
312
+ });
210
313
  return this.approve({
211
314
  strand: { strandId: provisioned.strandId, createdBy: 'responder' },
212
315
  memberPrivateKey: provisioned.memberPrivateKey ?? undefined,
@@ -215,7 +318,7 @@ export class StrandFormationManager {
215
318
  }
216
319
  if (this.strandProvisioner) {
217
320
  const sAppId = await this.resolveInviteSAppId(token);
218
- const result = await this.strandProvisioner.provisionStrand(sAppId, initiatorPartyId, this.partyId);
321
+ const result = await this.strandProvisioner.provisionStrand(sAppId, contact.partyId, this.partyId);
219
322
  return this.approve({
220
323
  strand: { strandId: result.strandId, createdBy: 'responder' },
221
324
  dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
@@ -1 +1 @@
1
- {"version":3,"file":"strand-formation-manager.js","sourceRoot":"","sources":["../src/strand-formation-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-formation-manager.js","sourceRoot":"","sources":["../src/strand-formation-manager.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,30 @@ import type { Libp2p } from '@libp2p/interface';
21
21
  import type { StrandFormationDisclosure } from './types.js';
22
22
  /** Protocol id for the native formation transport (parallel to `/sereus/seed/1.0.0`). */
23
23
  export declare const FORMATION_PROTOCOL = "/sereus/formation/1.0.0";
24
+ /**
25
+ * Rejection reason for an invitation that cannot be redeemed at all — unknown, expired, or
26
+ * fully spent. Shared with `StrandFormationManager`, which reports an
27
+ * `InvitationExhaustedError` (an invite spent out from under a redemption already in flight)
28
+ * with this SAME wording on purpose: a joiner that lost the race must be indistinguishable
29
+ * from a non-racing latecomer, so the two sites must never drift apart.
30
+ */
31
+ export declare const INVALID_TOKEN_REASON = "Invalid token";
32
+ /**
33
+ * Wire-latency margin (ms) added to the responder's provisioning budget to get the
34
+ * initiator's await-response budget — the travel time for the result frame to come back
35
+ * after the responder finishes (see {@link DEFAULT_PROVISION_TIMEOUT_MS}).
36
+ * `StrandFormationManager` reuses this constant so a CONFIGURED budget preserves the same
37
+ * margin the defaults do, instead of applying the same number to both sides and racing the
38
+ * initiator's own timeout against the responder's clean rejection reply. The responder's
39
+ * clamp ({@link provisionCeilingMs}) holds the same margin back, so the ordering survives a
40
+ * budget large enough that BOTH roles get clamped.
41
+ *
42
+ * NOTE: 3 s is a fixed assumption about how long the result frame takes to travel back. If
43
+ * formation ever runs over paths where that frame can take longer (a slow circuit relay, a
44
+ * congested mobile link), the margin is too thin and the initiator's timeout races the
45
+ * responder's reply again — raise it, or derive it from an observed round-trip.
46
+ */
47
+ export declare const PROVISION_RESPONSE_TRAVEL_MARGIN_MS = 3000;
24
48
  export type FormationParty = 'initiator' | 'responder';
25
49
  export interface FormationStrandInfo {
26
50
  strandId: string;
@@ -62,8 +86,20 @@ export type ResponderProvisionOutcome = {
62
86
  export interface FormationContactMessage {
63
87
  /** The real invitation token. */
64
88
  token: string;
65
- /** Initiator's member key (peer id). */
89
+ /** Initiator's member key (peer id) — human-facing identity in logs and `FormStrandResult.memberKey`. */
66
90
  partyId: string;
91
+ /**
92
+ * Initiator's base64url ed25519 public key — the key behind `partyId`, written to
93
+ * `FormationUsage.PeerKey` and covered by BOTH signed digests.
94
+ */
95
+ peerKey: string;
96
+ /** Joiner-minted single-use nonce for THIS redemption; both signed digests cover it. */
97
+ usageStampId: string;
98
+ /**
99
+ * The joiner's signature over the `'consent'` digest (see `formationConsentMessage`
100
+ * in control-database.ts) — proof the named peer agreed to this redemption.
101
+ */
102
+ peerSignature: string;
67
103
  /** The real disclosure, carried verbatim. */
68
104
  disclosure: StrandFormationDisclosure;
69
105
  /** Initiator's real multiaddrs. */
@@ -104,14 +140,19 @@ export interface FormationListenerOptions {
104
140
  validateDisclosure(token: string, disclosure: StrandFormationDisclosure): Promise<boolean>;
105
141
  /**
106
142
  * Provision (or, for provision-then-record, resolve + record consent against) the
107
- * strand (`responderCreates`) for the given initiator. The REAL token is threaded
108
- * in so the hook can map it to the bound host strand and its membership key.
143
+ * strand (`responderCreates`) for the given initiator. The WHOLE contact message is
144
+ * threaded in: the hook needs the REAL token to map the bound host strand, and the
145
+ * joiner's `peerKey`/`usageStampId`/`peerSignature` to write the consent columns.
109
146
  *
110
147
  * Returns a {@link ResponderProvisionOutcome}: the hook may REJECT post-validation
111
148
  * (e.g. an unconverged host strand) and the listener turns that into a clean,
112
149
  * non-disclosing `approved: false` reply rather than dropping the result frame.
150
+ *
151
+ * `signal` is aborted when the listener's work budget expires: a hook that observes it
152
+ * BEFORE writing abandons the redemption and leaves the invite unspent. Optional so
153
+ * signal-unaware hooks (tests, mocks) stay assignable.
113
154
  */
114
- provisionStrand(token: string, initiatorPartyId: string, disclosure: StrandFormationDisclosure): Promise<ResponderProvisionOutcome>;
155
+ provisionStrand(contact: FormationContactMessage, signal?: AbortSignal): Promise<ResponderProvisionOutcome>;
115
156
  /** Responder identity, disclosed only AFTER token + disclosure validation passes. */
116
157
  getResponderIdentity(): {
117
158
  partyId: string;
@@ -119,6 +160,14 @@ export interface FormationListenerOptions {
119
160
  };
120
161
  sessionTimeoutMs?: number;
121
162
  stepTimeoutMs?: number;
163
+ /**
164
+ * Budget for the `provisionStrand` hook call — distinct from `stepTimeoutMs` because
165
+ * provisioning is real work, not a bare wire read/write. Default 12 s; `0`/unset uses
166
+ * the default; a value that would outlive the session is clamped ({@link
167
+ * provisionCeilingMs}). See {@link DEFAULT_PROVISION_TIMEOUT_MS} for the full ordering
168
+ * rationale.
169
+ */
170
+ provisionTimeoutMs?: number;
122
171
  maxConcurrentSessions?: number;
123
172
  }
124
173
  /**
@@ -131,6 +180,8 @@ export declare class FormationListener {
131
180
  private readonly options;
132
181
  private readonly sessionTimeoutMs;
133
182
  private readonly stepTimeoutMs;
183
+ private readonly provisionWorkMs;
184
+ private readonly provisionGraceMs;
134
185
  private readonly maxConcurrentSessions;
135
186
  private readonly registered;
136
187
  private activeSessions;
@@ -141,6 +192,48 @@ export declare class FormationListener {
141
192
  register(node: Libp2p, protocolId?: string): void;
142
193
  unregister(node: Libp2p, protocolId?: string): void;
143
194
  private handleStream;
195
+ /**
196
+ * Run the provisioning hook under its WORK budget — the configured provisioning budget minus
197
+ * the trailing settle grace ({@link splitProvisionBudget}, {@link PROVISION_SETTLE_GRACE_MS}).
198
+ *
199
+ * When the work budget expires the hook's signal is aborted, then
200
+ * {@link settleWithinGrace} waits out the grace for the aborted work to settle anyway.
201
+ * Returns `undefined` for "timed out" — which the caller turns into a retryable rejection
202
+ * frame — and rethrows every other failure so it reaches the internal-error path.
203
+ *
204
+ * Deliberately not `withDeadline`: that exposes neither the timed-out flag nor the signal
205
+ * outside its `op`, and both are needed here.
206
+ */
207
+ private provision;
208
+ /**
209
+ * Wait out the settle grace for an ABORTED provisioning, adopting a late success.
210
+ *
211
+ * A hook that observed the abort before issuing its `FormationUsage` insert rejects (with
212
+ * `FormationAbortedError`) and leaves the invite unspent → reported as a timeout. A hook
213
+ * that had already written cannot be un-written (the table is append-only), so if it lands
214
+ * inside the grace its outcome is ADOPTED — the joiner is told the truth (approved, or the
215
+ * hook's own rejection reason) rather than "timed out" over a spent invite.
216
+ *
217
+ * NOTE: one window stays open — a work budget that expires after the `FormationUsage`
218
+ * insert was issued AND a commit that outlasts the grace. The joiner is told 'timed out'
219
+ * while its one-time invite is in fact spent, and since every retry mints a fresh keypair
220
+ * no recovery path can match it. Nothing can un-spend an append-only row, so the late-settle
221
+ * logging below is the observability for it; widen the grace only if it is seen in the wild.
222
+ */
223
+ private settleWithinGrace;
224
+ /**
225
+ * Joiner-consent pre-check — the legibility twin of the schema's
226
+ * `FormationUsage.PeerConsented` CHECK, exactly as `verifyFormationApproval` is for the
227
+ * approver's vouch. The database constraint stays the authority; this turns a bad consent
228
+ * into a clean 'Invalid joiner consent' rejection instead of an opaque CHECK failure at
229
+ * commit. Runs BEFORE token/disclosure validation: it is the cheapest check (pure local
230
+ * crypto, no DB read) and a rejection discloses nothing.
231
+ *
232
+ * Also pins `partyId` to `peerKey`: an ed25519 peer id is an identity multihash of the
233
+ * very key, and this is the ONLY layer that can check the match — SQL cannot unwrap a
234
+ * multihash, and `partyId` itself is never stored.
235
+ */
236
+ private isJoinerConsentValid;
144
237
  private runSession;
145
238
  }
146
239
  export interface FormationDialOptions {
@@ -152,6 +245,12 @@ export interface FormationDialOptions {
152
245
  validateResponse(response: FormationResultMessage): Promise<boolean>;
153
246
  sessionTimeoutMs?: number;
154
247
  stepTimeoutMs?: number;
248
+ /**
249
+ * Budget for the `await-response` read only (the responder's provisioning + reply travel
250
+ * time) — `dial-connect` keeps using `stepTimeoutMs`. Default 15 s; `0`/unset uses the
251
+ * default. See {@link DEFAULT_PROVISION_TIMEOUT_MS} for the full ordering rationale.
252
+ */
253
+ provisionTimeoutMs?: number;
155
254
  protocolId?: string;
156
255
  }
157
256
  /**