@serfab/cadre-core 0.9.0 → 0.11.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 (193) hide show
  1. package/README.md +255 -253
  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 +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  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-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
@@ -9,6 +9,14 @@ export interface StrandFormationManagerConfig {
9
9
  sessionTimeoutMs?: number;
10
10
  /** Step timeout in milliseconds */
11
11
  stepTimeoutMs?: number;
12
+ /**
13
+ * Provisioning budget in milliseconds for the RESPONDER's `provisionStrand` hook call.
14
+ * The initiator's `await-response` wait is derived automatically from this value plus
15
+ * `PROVISION_RESPONSE_TRAVEL_MARGIN_MS` (`strand-formation-protocol.ts`) — never set
16
+ * directly — so the ordering documented there (approval hook < responder provisioning <
17
+ * initiator await-response < session) cannot collapse when this is configured.
18
+ */
19
+ provisionTimeoutMs?: number;
12
20
  /** Maximum concurrent sessions */
13
21
  maxConcurrentSessions?: number;
14
22
  /** Enable debug logging */
@@ -73,10 +81,16 @@ export declare class StrandFormationManager {
73
81
  * Form a strand with a responder via an open invitation (initiator side).
74
82
  *
75
83
  * Builds a contact message carrying the real token + disclosure + this party's
76
- * real cadre addresses, dials the responder over the native protocol, and
77
- * validates the responder's result before returning.
84
+ * real cadre addresses + the joiner's consent (minted and signed by the
85
+ * solicitation layer — this manager only places it on the contact), dials the
86
+ * responder over the native protocol, and validates the responder's result
87
+ * before returning.
78
88
  */
79
- formStrand(invitation: OpenInvitation, disclosure: StrandFormationDisclosure, node: Libp2p): Promise<FormStrandResult>;
89
+ formStrand(invitation: OpenInvitation, disclosure: StrandFormationDisclosure, consent: {
90
+ peerKey: string;
91
+ usageStampId: string;
92
+ peerSignature: string;
93
+ }, node: Libp2p): Promise<FormStrandResult>;
80
94
  /**
81
95
  * Get the number of active sessions
82
96
  */
@@ -84,6 +98,16 @@ export declare class StrandFormationManager {
84
98
  listeners: number;
85
99
  dialers: number;
86
100
  };
101
+ /**
102
+ * Derive the initiator's await-response budget from the configured RESPONDER budget —
103
+ * never the same number (see `StrandFormationManagerConfig.provisionTimeoutMs`). Mirrors
104
+ * `resolveProvisionTimeoutMs`'s own "`0`/negative means unset" rule so an unset config
105
+ * still lets both sides fall back to their own independent defaults; must NOT hardcode
106
+ * `DEFAULT_PROVISION_TIMEOUT_MS` here, or the per-role clamping downstream is defeated.
107
+ * A value too large for the session is clamped per role, and the responder's ceiling holds
108
+ * the same margin back, so the ordering survives there too.
109
+ */
110
+ private initiatorProvisionTimeoutMs;
87
111
  private validateToken;
88
112
  private validateDisclosure;
89
113
  /**
@@ -101,10 +125,15 @@ export declare class StrandFormationManager {
101
125
  * deferred `StrandExists` CHECK at commit and drop the frame.
102
126
  * - **unbound** (no binding): the responder-provisions fallback — see {@link provisionUnbound}.
103
127
  *
104
- * Defense-in-depth: known provisioning/redeem failures (notably the concurrent
105
- * `(Token, UseNumber)` PK collision when two redemptions of one unbound single-use invite
106
- * race) are caught and mapped to a logged, retry-suggesting rejection rather than a thrown
107
- * insert + a silently-closed stream. The LOG-before-reject keeps this a deliberate
128
+ * Defense-in-depth: known provisioning/redeem failures are caught and mapped to a logged
129
+ * protocol rejection rather than a thrown insert + a silently-closed stream. The mapping is
130
+ * per-failure, NOT blanket retry-suggesting: an exhausted invitation
131
+ * ({@link InvitationExhaustedError}, raised by `ControlDatabase` when the count of recorded
132
+ * uses has reached the invite's seat budget — which is how the loser of a same-node race
133
+ * surfaces, the local write queue having serialized the two writes) is reported as
134
+ * `'Invalid token'`, because retrying it can never succeed. Concurrent redemptions never
135
+ * contend for a shared row key — each writes under its own `UsageStampId` — so there is no
136
+ * key collision to surface here at all. The LOG-before-reject keeps this a deliberate
108
137
  * internal-error→protocol-rejection conversion (AGENTS.md: don't eat exceptions silently),
109
138
  * not control-flow-by-exception.
110
139
  */
@@ -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
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` when the count of recorded
197
+ * uses has reached the invite's seat budget — which is how the loser of a same-node race
198
+ * surfaces, the local write queue having serialized the two writes) is reported as
199
+ * `'Invalid token'`, because retrying it can never succeed. Concurrent redemptions never
200
+ * contend for a shared row key — each writes under its own `UsageStampId` — so there is no
201
+ * key collision to surface here at all. The LOG-before-reject keeps this a deliberate
152
202
  * internal-error→protocol-rejection conversion (AGENTS.md: don't eat exceptions silently),
153
203
  * 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,33 @@ 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 sees exactly what a non-racing latecomer
260
+ // 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: %d of %d use(s) already recorded', err.token, err.usesRecorded, err.totalUses);
265
+ return { approved: false, reason: INVALID_TOKEN_REASON };
266
+ }
267
+ // An approval is never discarded to a lost key race: each redemption writes under its
268
+ // own `UsageStampId`, so no other writer can take its row key. Reaching this catch-all
269
+ // means the failure was never retryable at the database layer to begin with (or the
270
+ // transient-cluster retry inside `ControlDatabase.lockedWithRetry` ran out).
180
271
  log('provisionAsResponder failed for token %s: %o', token, err);
181
272
  return { approved: false, reason: 'Formation conflict, retry' };
182
273
  }
@@ -202,11 +293,20 @@ export class StrandFormationManager {
202
293
  * the responder-provisions placeholder as failure). The broader "should this fallback exist
203
294
  * at all?" question lives in backlog `formation-initiatorcreates-cover-or-remove`.
204
295
  */
205
- async provisionUnbound(token, initiatorPartyId) {
296
+ async provisionUnbound(contact, disclosureText, signal) {
297
+ const token = contact.token;
206
298
  const recorder = this.formationUsageRecorder;
207
299
  if (recorder?.provisionAndRecord) {
208
300
  const sAppId = await this.resolveInviteSAppId(token);
209
- const provisioned = await recorder.provisionAndRecord(token, initiatorPartyId, sAppId);
301
+ const provisioned = await recorder.provisionAndRecord({
302
+ token,
303
+ peerKey: contact.peerKey,
304
+ peerSignature: contact.peerSignature,
305
+ usageStampId: contact.usageStampId,
306
+ sAppId,
307
+ disclosure: disclosureText,
308
+ signal
309
+ });
210
310
  return this.approve({
211
311
  strand: { strandId: provisioned.strandId, createdBy: 'responder' },
212
312
  memberPrivateKey: provisioned.memberPrivateKey ?? undefined,
@@ -215,7 +315,7 @@ export class StrandFormationManager {
215
315
  }
216
316
  if (this.strandProvisioner) {
217
317
  const sAppId = await this.resolveInviteSAppId(token);
218
- const result = await this.strandProvisioner.provisionStrand(sAppId, initiatorPartyId, this.partyId);
318
+ const result = await this.strandProvisioner.provisionStrand(sAppId, contact.partyId, this.partyId);
219
319
  return this.approve({
220
320
  strand: { strandId: result.strandId, createdBy: 'responder' },
221
321
  dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
@@ -1 +1 @@
1
- {"version":3,"file":"strand-formation-manager.js","sourceRoot":"","sources":["../src/strand-formation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,6BAA6B,EAK9B,MAAM,gCAAgC,CAAC;AAExC,MAAM,GAAG,GAAG,KAAK,CAAC,gCAAgC,CAAC,CAAC;AAsCpD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,sBAAsB;IAYjC,YAAY,OAAsC;QAHjC,oBAAe,GAAG,IAAI,GAAG,EAAU,CAAC;QAC7C,mBAAc,GAAG,CAAC,CAAC;QAGzB,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC;QACvD,IAAI,CAAC,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC;QAC7D,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;QACnD,IAAI,CAAC,0BAA0B,GAAG,OAAO,CAAC,0BAA0B,CAAC;QACrE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC;QACnD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QAEnC,IAAI,CAAC,QAAQ,GAAG,IAAI,iBAAiB,CAAC;YACpC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;YACnD,kBAAkB,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,CAAC;YACrF,eAAe,EAAE,CAAC,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,EAAE,CACvD,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,EAAE,UAAU,CAAC;YAChE,oBAAoB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;YAC5F,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;YAC9C,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;YACxC,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,qBAAqB;SACzD,CAAC,CAAC;QAEH,GAAG,CAAC,8CAA8C,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,IAAY,EAAE,UAAmB;QACjD,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,GAAG,CAAC,yBAAyB,CAAC,CAAC;YAC/B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,GAAG,CAAC,iCAAiC,CAAC,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,IAAY,EAAE,UAAmB;QACnD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACrE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CACd,UAA0B,EAC1B,UAAqC,EACrC,IAAY;QAEZ,GAAG,CAAC,0CAA0C,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QAElE,MAAM,OAAO,GAA4B;YACvC,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,OAAO,EAAE,UAAU,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO;YAC3C,UAAU;YACV,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC,CAAC;QAEF,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE;gBAC1C,OAAO;gBACP,cAAc,EAAE,UAAU,CAAC,SAAS;gBACpC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC;gBACvF,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;gBAC9C,aAAa,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa;gBACxC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;aACnC,CAAC,CAAC;YAEH,GAAG,CAAC,mBAAmB,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAEpD,OAAO;gBACL,SAAS,EAAE,OAAO,CAAC,OAAO;gBAC1B,gBAAgB,EAAE,EAAE;gBACpB,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ;gBACnC,4FAA4F;gBAC5F,qFAAqF;gBACrF,+EAA+E;gBAC/E,gBAAgB,EAAE,SAAS,CAAC,gBAAgB;aAC7C,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,sBAAsB;QACpB,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;IAChF,CAAC;IAED,gFAAgF;IAExE,KAAK,CAAC,aAAa,CAAC,KAAa;QACvC,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACjC,8CAA8C;YAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACzE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtB,GAAG,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;YAChC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1B,CAAC;QAED,IAAI,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;YACrC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1B,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,KAAa,EAAE,UAAqC;QACnF,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9B,oDAAoD;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACK,KAAK,CAAC,oBAAoB,CAChC,KAAa,EACb,gBAAwB,EACxB,WAAsC;QAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC7C,MAAM,QAAQ,GAAuB,QAAQ,EAAE,aAAa;YAC1D,CAAC,CAAC,MAAM,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;YACrC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAExB,IAAI,CAAC;YACH,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACtB,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,8EAA8E;oBAC9E,MAAM,QAAS,CAAC,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;oBACxE,OAAO,IAAI,CAAC,OAAO,CAAC;wBAClB,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE;wBAC/D,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,IAAI,SAAS;wBACxD,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;qBAC5D,CAAC,CAAC;gBACL,CAAC;gBACD,KAAK,SAAS,CAAC,CAAC,CAAC;oBACf,GAAG,CAAC,wEAAwE,EAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;oBACxG,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,iDAAiD,EAAE,CAAC;gBACxF,CAAC;gBACD,KAAK,SAAS;oBACZ,OAAO,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,8CAA8C,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;QAClE,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACK,KAAK,CAAC,gBAAgB,CAC5B,KAAa,EACb,gBAAwB;QAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,sBAAsB,CAAC;QAC7C,IAAI,QAAQ,EAAE,kBAAkB,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC;YACvF,OAAO,IAAI,CAAC,OAAO,CAAC;gBAClB,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE;gBAClE,gBAAgB,EAAE,WAAW,CAAC,gBAAgB,IAAI,SAAS;gBAC3D,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;aAC5D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACpG,OAAO,IAAI,CAAC,OAAO,CAAC;gBAClB,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE;gBAC7D,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;aAC5D,CAAC,CAAC;QACL,CAAC;QAED,4FAA4F;QAC5F,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAClF,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE;YAC5C,gBAAgB,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,iFAAiF;IACzE,OAAO,CAAC,MAAgC;QAC9C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,mBAAmB,CAAC,KAAa;QAC7C,IAAI,CAAC,IAAI,CAAC,sBAAsB;YAAE,OAAO,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,CAAC;IACxC,CAAC;IAED,gFAAgF;IAExE,KAAK,CAAC,gBAAgB,CAC5B,UAA0B,EAC1B,UAAqC,EACrC,QAAgC;QAEhC,IAAI,IAAI,CAAC,0BAA0B,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,0BAA0B,CAAC,gBAAgB,CAAC,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,OAAO,6BAA6B,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAAsC;IAEtC,OAAO,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC"}
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
  /**