@serfab/cadre-core 1.17.0 → 1.19.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 (148) hide show
  1. package/README.md +3 -2
  2. package/dist/cadre-invite-protocol.d.ts +35 -13
  3. package/dist/cadre-invite-protocol.js +90 -25
  4. package/dist/cadre-invite-protocol.js.map +1 -1
  5. package/dist/cadre-node.d.ts +586 -255
  6. package/dist/cadre-node.js +1182 -566
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/control-cohort.d.ts +66 -9
  9. package/dist/control-cohort.js +67 -9
  10. package/dist/control-cohort.js.map +1 -1
  11. package/dist/control-database.d.ts +451 -193
  12. package/dist/control-database.js +701 -359
  13. package/dist/control-database.js.map +1 -1
  14. package/dist/control-formation-recorder.d.ts +46 -43
  15. package/dist/control-formation-recorder.js +77 -78
  16. package/dist/control-formation-recorder.js.map +1 -1
  17. package/dist/control-protocol-guard.d.ts +6 -0
  18. package/dist/control-protocol-guard.js +14 -2
  19. package/dist/control-protocol-guard.js.map +1 -1
  20. package/dist/control-schema-version.d.ts +48 -31
  21. package/dist/control-schema-version.js +67 -45
  22. package/dist/control-schema-version.js.map +1 -1
  23. package/dist/control-schema.d.ts +8 -8
  24. package/dist/control-schema.js +16 -16
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-write-retry.d.ts +42 -7
  27. package/dist/control-write-retry.js +87 -19
  28. package/dist/control-write-retry.js.map +1 -1
  29. package/dist/enrolled-machine-store.d.ts +3 -3
  30. package/dist/enrolled-machine-store.js +3 -3
  31. package/dist/formation-token.d.ts +48 -0
  32. package/dist/formation-token.js +26 -0
  33. package/dist/formation-token.js.map +1 -0
  34. package/dist/index.d.ts +15 -14
  35. package/dist/index.js +17 -14
  36. package/dist/index.js.map +1 -1
  37. package/dist/joined-strand-store.d.ts +26 -9
  38. package/dist/joined-strand-store.js +63 -24
  39. package/dist/joined-strand-store.js.map +1 -1
  40. package/dist/link-budget.d.ts +9 -7
  41. package/dist/link-budget.js +9 -7
  42. package/dist/link-budget.js.map +1 -1
  43. package/dist/member-redemption.d.ts +15 -9
  44. package/dist/member-redemption.js +13 -8
  45. package/dist/member-redemption.js.map +1 -1
  46. package/dist/membership-view.d.ts +91 -0
  47. package/dist/membership-view.js +110 -0
  48. package/dist/membership-view.js.map +1 -0
  49. package/dist/owner-grant-protocol.d.ts +11 -7
  50. package/dist/owner-grant-protocol.js +31 -17
  51. package/dist/owner-grant-protocol.js.map +1 -1
  52. package/dist/peer-addr-book.d.ts +10 -1
  53. package/dist/peer-addr-book.js +4 -3
  54. package/dist/peer-addr-book.js.map +1 -1
  55. package/dist/peer-join-backfill.d.ts +96 -30
  56. package/dist/peer-join-backfill.js +272 -128
  57. package/dist/peer-join-backfill.js.map +1 -1
  58. package/dist/pending-join-runner.d.ts +48 -18
  59. package/dist/pending-join-runner.js +74 -56
  60. package/dist/pending-join-runner.js.map +1 -1
  61. package/dist/seed-bootstrap.d.ts +47 -40
  62. package/dist/seed-bootstrap.js +66 -61
  63. package/dist/seed-bootstrap.js.map +1 -1
  64. package/dist/self-address-update.d.ts +13 -0
  65. package/dist/self-address-update.js +19 -0
  66. package/dist/self-address-update.js.map +1 -0
  67. package/dist/strand-database.d.ts +23 -2
  68. package/dist/strand-database.js +26 -6
  69. package/dist/strand-database.js.map +1 -1
  70. package/dist/strand-first-sync-gate.d.ts +176 -89
  71. package/dist/strand-first-sync-gate.js +288 -122
  72. package/dist/strand-first-sync-gate.js.map +1 -1
  73. package/dist/strand-formation-deadlines.d.ts +9 -9
  74. package/dist/strand-formation-deadlines.js +9 -9
  75. package/dist/strand-formation-manager.d.ts +23 -17
  76. package/dist/strand-formation-manager.js +46 -44
  77. package/dist/strand-formation-manager.js.map +1 -1
  78. package/dist/strand-formation-protocol.d.ts +5 -8
  79. package/dist/strand-formation-protocol.js +101 -16
  80. package/dist/strand-formation-protocol.js.map +1 -1
  81. package/dist/strand-instance-manager.d.ts +26 -17
  82. package/dist/strand-instance-manager.js +119 -50
  83. package/dist/strand-instance-manager.js.map +1 -1
  84. package/dist/strand-membership-reconciler.d.ts +16 -13
  85. package/dist/strand-membership-reconciler.js +28 -22
  86. package/dist/strand-membership-reconciler.js.map +1 -1
  87. package/dist/strand-membership-writer.d.ts +40 -28
  88. package/dist/strand-membership-writer.js +212 -153
  89. package/dist/strand-membership-writer.js.map +1 -1
  90. package/dist/strand-move.js +5 -2
  91. package/dist/strand-move.js.map +1 -1
  92. package/dist/strand-revocation-enforcer.d.ts +134 -60
  93. package/dist/strand-revocation-enforcer.js +144 -71
  94. package/dist/strand-revocation-enforcer.js.map +1 -1
  95. package/dist/strand-solicitation.d.ts +50 -39
  96. package/dist/strand-solicitation.js +10 -3
  97. package/dist/strand-solicitation.js.map +1 -1
  98. package/dist/strand-watcher.d.ts +80 -13
  99. package/dist/strand-watcher.js +152 -48
  100. package/dist/strand-watcher.js.map +1 -1
  101. package/dist/table-change-watch.d.ts +63 -0
  102. package/dist/table-change-watch.js +209 -0
  103. package/dist/table-change-watch.js.map +1 -0
  104. package/dist/timeout-scheduler.d.ts +4 -3
  105. package/dist/timeout-scheduler.js +4 -3
  106. package/dist/timeout-scheduler.js.map +1 -1
  107. package/dist/types.d.ts +90 -49
  108. package/dist/types.js +4 -4
  109. package/dist/types.js.map +1 -1
  110. package/package.json +8 -8
  111. package/src/cadre-invite-protocol.ts +119 -31
  112. package/src/cadre-node.ts +1289 -588
  113. package/src/control-cohort.ts +86 -9
  114. package/src/control-database.ts +848 -403
  115. package/src/control-formation-recorder.ts +359 -353
  116. package/src/control-protocol-guard.ts +16 -2
  117. package/src/control-schema-version.ts +101 -51
  118. package/src/control-schema.ts +16 -16
  119. package/src/control-write-retry.ts +91 -19
  120. package/src/delegate-admission.ts +351 -351
  121. package/src/enrolled-machine-store.ts +3 -3
  122. package/src/formation-token.ts +63 -0
  123. package/src/index.ts +30 -18
  124. package/src/joined-strand-store.ts +69 -24
  125. package/src/link-budget.ts +9 -7
  126. package/src/member-redemption.ts +20 -13
  127. package/src/membership-view.ts +144 -0
  128. package/src/owner-grant-protocol.ts +33 -18
  129. package/src/peer-addr-book.ts +4 -3
  130. package/src/peer-join-backfill.ts +961 -761
  131. package/src/pending-join-runner.ts +76 -53
  132. package/src/seed-bootstrap.ts +72 -67
  133. package/src/self-address-update.ts +19 -0
  134. package/src/strand-database.ts +352 -322
  135. package/src/strand-first-sync-gate.ts +334 -134
  136. package/src/strand-formation-deadlines.ts +227 -227
  137. package/src/strand-formation-manager.ts +731 -725
  138. package/src/strand-formation-protocol.ts +122 -24
  139. package/src/strand-instance-manager.ts +2092 -2026
  140. package/src/strand-membership-reconciler.ts +31 -25
  141. package/src/strand-membership-writer.ts +2093 -2019
  142. package/src/strand-move.ts +5 -2
  143. package/src/strand-revocation-enforcer.ts +740 -654
  144. package/src/strand-solicitation.ts +61 -34
  145. package/src/strand-watcher.ts +175 -56
  146. package/src/table-change-watch.ts +285 -0
  147. package/src/timeout-scheduler.ts +29 -28
  148. package/src/types.ts +89 -51
@@ -35,15 +35,15 @@ const log = debug('sereus:cadre:formation-deadlines');
35
35
  * trip, budgeted as a request over an open stream (2) for the same headroom as every other
36
36
  * one-exchange read in `link-budget.ts`.
37
37
  * - **validation** and the reads inside **provisionWork** are budgeted at two consulting
38
- * reads each, not one per read the code issues. What actually runs: validation is three
39
- * control reads (`isTokenValid` reads the invite; `isTokenUsed` reads it again and counts
40
- * its usages) plus whatever the disclosure validator does; the bound provisioning path
41
- * (`StrandFormationManager.provisionAsResponder`) reads the invite and the strand row to
42
- * resolve the host strand, the invite again, the approver's `ValidationKey` and the seat
43
- * count around the outside approval hook (an HTTP call, so its own
44
- * `DEFAULT_APPROVAL_TIMEOUT_MS` is link-independent and added flat), the strand row and its
45
- * `StrandPartyKey` to issue the joiner's membership invite, and the host strand's stamp
46
- * before the `FormationUsage` write; then one strand-database commit issues the invite.
38
+ * reads each, not one per read the code issues. What actually runs: validation is two
39
+ * control reads (`readToken` reads the invite and, for a metered unexpired one, counts its
40
+ * usages) plus whatever the disclosure validator does; the bound provisioning path
41
+ * (`StrandFormationManager.provisionAsResponder`) acts on that read, reads the strand row
42
+ * with its stamp to resolve the host strand, the approver's `ValidationKey` and the seat
43
+ * count around the outside approval hook when the invite names one (an HTTP call, so its
44
+ * own `DEFAULT_APPROVAL_TIMEOUT_MS` is link-independent and added flat), and the host
45
+ * strand's `StrandPartyKey` to issue the joiner's membership invite; then one
46
+ * strand-database commit issues the invite.
47
47
  * Every one of those is a read of this party's own control database, which in steady state
48
48
  * touches held blocks and consults nobody (`cadre-consistency.md` → "Deadlines Over
49
49
  * Optimystic's Reads and Commits"), so `R` is charged as headroom for the ones that do
@@ -1,6 +1,6 @@
1
1
  import type { Libp2p } from '@libp2p/interface';
2
2
  import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite } from './types.js';
3
- import type { DisclosureValidator, FormationUsageRecorder, StrandProvisioner, FormationResponseValidator } from './strand-solicitation.js';
3
+ import type { BoundHostStrand, DisclosureValidator, FormationUsageRecorder, StrandProvisioner, FormationResponseValidator } from './strand-solicitation.js';
4
4
  /**
5
5
  * How long a formation-issued strand membership invitation stays redeemable (7 days).
6
6
  * The responder's {@link StrandFormationManagerOptions.issueMembershipInvite} hook stamps
@@ -35,11 +35,12 @@ export declare const MEMBERSHIP_INVITE_UNAVAILABLE_REASON = "Strand membership i
35
35
  export declare const HOST_STRAND_MUST_BE_RECREATED_REASON = "Host strand must be recreated";
36
36
  /**
37
37
  * Responder-side seam that issues a bound joiner's membership invitation — see
38
- * {@link StrandFormationManagerOptions.issueMembershipInvite} for the contract. `signal`
39
- * is the responder's provisioning budget: the issuer stops waiting (and throws) once it
40
- * aborts.
38
+ * {@link StrandFormationManagerOptions.issueMembershipInvite} for the contract. `host` is the
39
+ * host strand as the session resolved it, so the issuer reads no `Strand` row of its own.
40
+ * `signal` is the responder's provisioning budget: the issuer stops waiting (and throws) once
41
+ * it aborts.
41
42
  */
42
- export type MembershipInviteIssuer = (strandId: string, signal?: AbortSignal) => Promise<StrandMembershipInvite | null>;
43
+ export type MembershipInviteIssuer = (host: BoundHostStrand, signal?: AbortSignal) => Promise<StrandMembershipInvite | null>;
43
44
  /**
44
45
  * Configuration for StrandFormationManager
45
46
  */
@@ -113,7 +114,8 @@ export interface StrandFormationManagerOptions {
113
114
  * `StrandPartyKey` lookup.
114
115
  *
115
116
  * Contract: return the issued invitation for a CLOSED host strand; return `null`
116
- * for an open one (no members, nothing to invite into); THROW when a closed strand's
117
+ * for an open one (no members, nothing to invite into), told apart by the resolved
118
+ * host's `strandType`; THROW when a closed strand's
117
119
  * invitation cannot be issued (runtime not live, no party key, strand DB write
118
120
  * rejected) — the manager then rejects the whole redemption with
119
121
  * {@link MEMBERSHIP_INVITE_UNAVAILABLE_REASON} BEFORE recording consent, so the
@@ -212,9 +214,10 @@ export declare class StrandFormationManager {
212
214
  */
213
215
  private initiatorProvisionTimeoutMs;
214
216
  /**
215
- * An expired or used-up invitation is `'token-spent'` (final); one with no row here is
216
- * `'token-unknown'` (retryable — the row may not have replicated to this machine yet), and
217
- * so is one from a recorder that does not say why.
217
+ * The session's one read of its token ({@link FormationUsageRecorder.readToken}), classified
218
+ * by {@link classifyTokenRead}: an expired or used-up invitation is `'token-spent'` (final);
219
+ * one with no row here is `'token-unknown'` (retryable — the row may not have replicated to
220
+ * this machine yet). A valid check carries the read on to {@link provisionAsResponder}.
218
221
  */
219
222
  private validateToken;
220
223
  private validateDisclosure;
@@ -224,9 +227,12 @@ export declare class StrandFormationManager {
224
227
  * {@link ResponderProvisionOutcome} with `approved: false` — `runSession` turns that
225
228
  * into a clean, non-disclosing reply instead of a dropped result frame.
226
229
  *
230
+ * Every step acts on `read`, the token as {@link validateToken} read it for this session,
231
+ * and on the host strand as resolved from it; none reads the invite or the `Strand` row again.
232
+ *
227
233
  * - **bound** (host strand present): provision-then-record, in three steps —
228
234
  * (1) authorize ({@link authorizeBoundUsage}: the outside approval when the invite
229
- * demands one, and a seat pre-check), so a refused join writes nothing into the host
235
+ * demands one, and a seat re-check after it), so a refused join writes nothing into the host
230
236
  * strand; (2) issue the joiner's single-use strand membership invitation via the wired
231
237
  * `issueMembershipInvite` seam ({@link issueBoundMembershipInvite}; a closed strand whose
232
238
  * invitation cannot be issued rejects retryably HERE, before any consent row spends the
@@ -252,6 +258,12 @@ export declare class StrandFormationManager {
252
258
  * not control-flow-by-exception.
253
259
  */
254
260
  private provisionAsResponder;
261
+ /**
262
+ * The session's token read. {@link validateToken} takes one whenever a recorder is wired and
263
+ * the listener hands it over, so a missing one is a wiring fault: refused rather than letting
264
+ * the redemption fall through to a path that records no usage.
265
+ */
266
+ private requireRead;
255
267
  /**
256
268
  * Authorize a BOUND redemption without writing it, through the recorder's optional
257
269
  * {@link FormationUsageRecorder.authorizeUsage}. A recorder without it has nothing to ask up
@@ -288,7 +300,7 @@ export declare class StrandFormationManager {
288
300
  * 2. else `strandProvisioner` present → the mock/transport-test contract: provision a bare
289
301
  * strand, NO inline usage write (those callers record usage explicitly via
290
302
  * `recordFormationComplete`, or assert transport invariants only). Threads the invite's
291
- * REAL `sAppId`.
303
+ * REAL `sAppId` from the session's read (empty when there is none).
292
304
  * 3. else → a structural placeholder (no recorder + no provisioner ⇒ no single-use semantics
293
305
  * exist to enforce).
294
306
  *
@@ -304,12 +316,6 @@ export declare class StrandFormationManager {
304
316
  private provisionUnbound;
305
317
  /** Wrap a provision result as an approving {@link ResponderProvisionOutcome}. */
306
318
  private approve;
307
- /**
308
- * The invite's authoritative `sAppId` for the responder-provisions fallback, read back
309
- * from the recorder's `isTokenValid` invitation (the real `FormationInvite.sAppId`).
310
- * Empty string when no recorder is wired or the invitation omits it.
311
- */
312
- private resolveInviteSAppId;
313
319
  private validateResponse;
314
320
  }
315
321
  /**
@@ -6,6 +6,7 @@ import { FormationAbortedError, InvitationExhaustedError } from './control-datab
6
6
  import { PreSplitStrandIdentityError } from './strand-membership-writer.js';
7
7
  import { mintPlaceholderStrandId } from './strand-id.js';
8
8
  import { canonicalJson } from './canonical-json.js';
9
+ import { classifyTokenRead } from './formation-token.js';
9
10
  import { FormationListener, INVALID_TOKEN_REASON, dialFormationByMachine, isValidResponderCreatesResult, isWellFormedMembershipInvite } from './strand-formation-protocol.js';
10
11
  import { formationDeadlines } from './strand-formation-deadlines.js';
11
12
  const log = debug('sereus:cadre:formation-manager');
@@ -91,7 +92,7 @@ export class StrandFormationManager {
91
92
  this.listener = new FormationListener({
92
93
  validateToken: (token) => this.validateToken(token),
93
94
  validateDisclosure: (token, disclosure) => this.validateDisclosure(token, disclosure),
94
- provisionStrand: (contact, signal) => this.provisionAsResponder(contact, signal),
95
+ provisionStrand: (contact, signal, read) => this.provisionAsResponder(contact, signal, read),
95
96
  getResponderIdentity: () => ({ partyId: this.partyId, cadrePeerAddrs: this.currentCadrePeerAddrs() }),
96
97
  // Forwarded only when wired, so `FormationListenerOptions.resolveStrandAddrs`
97
98
  // stays genuinely absent (and the listener short-circuits) for an unwired manager.
@@ -227,25 +228,21 @@ export class StrandFormationManager {
227
228
  }
228
229
  // ── Responder-side hooks ─────────────────────────────────────────────────────
229
230
  /**
230
- * An expired or used-up invitation is `'token-spent'` (final); one with no row here is
231
- * `'token-unknown'` (retryable — the row may not have replicated to this machine yet), and
232
- * so is one from a recorder that does not say why.
231
+ * The session's one read of its token ({@link FormationUsageRecorder.readToken}), classified
232
+ * by {@link classifyTokenRead}: an expired or used-up invitation is `'token-spent'` (final);
233
+ * one with no row here is `'token-unknown'` (retryable — the row may not have replicated to
234
+ * this machine yet). A valid check carries the read on to {@link provisionAsResponder}.
233
235
  */
234
236
  async validateToken(token) {
235
237
  if (!this.formationUsageRecorder) {
236
238
  // No recorder configured — accept all tokens.
237
239
  return { valid: true };
238
240
  }
239
- const tokenCheck = await this.formationUsageRecorder.isTokenValid(token);
240
- if (!tokenCheck.valid) {
241
- log('Token invalid (%s): %s', tokenCheck.reason ?? 'unknown', token);
242
- return { valid: false, code: tokenCheck.reason === 'expired' ? 'token-spent' : 'token-unknown' };
241
+ const check = classifyTokenRead(await this.formationUsageRecorder.readToken(token), Date.now());
242
+ if (!check.valid) {
243
+ log('Token refused (%s): %s', check.code, token);
243
244
  }
244
- if (await this.formationUsageRecorder.isTokenUsed(token)) {
245
- log('Token already used: %s', token);
246
- return { valid: false, code: 'token-spent' };
247
- }
248
- return { valid: true };
245
+ return check;
249
246
  }
250
247
  async validateDisclosure(token, disclosure) {
251
248
  if (!this.disclosureValidator) {
@@ -260,9 +257,12 @@ export class StrandFormationManager {
260
257
  * {@link ResponderProvisionOutcome} with `approved: false` — `runSession` turns that
261
258
  * into a clean, non-disclosing reply instead of a dropped result frame.
262
259
  *
260
+ * Every step acts on `read`, the token as {@link validateToken} read it for this session,
261
+ * and on the host strand as resolved from it; none reads the invite or the `Strand` row again.
262
+ *
263
263
  * - **bound** (host strand present): provision-then-record, in three steps —
264
264
  * (1) authorize ({@link authorizeBoundUsage}: the outside approval when the invite
265
- * demands one, and a seat pre-check), so a refused join writes nothing into the host
265
+ * demands one, and a seat re-check after it), so a refused join writes nothing into the host
266
266
  * strand; (2) issue the joiner's single-use strand membership invitation via the wired
267
267
  * `issueMembershipInvite` seam ({@link issueBoundMembershipInvite}; a closed strand whose
268
268
  * invitation cannot be issued rejects retryably HERE, before any consent row spends the
@@ -287,7 +287,7 @@ export class StrandFormationManager {
287
287
  * internal-error→protocol-rejection conversion (AGENTS.md: don't eat exceptions silently),
288
288
  * not control-flow-by-exception.
289
289
  */
290
- async provisionAsResponder(contact, signal) {
290
+ async provisionAsResponder(contact, signal, read) {
291
291
  const token = contact.token;
292
292
  // CANONICALLY serialized — both sides derive the identical string: this is the exact
293
293
  // text the joiner signed (consent), the approver signs (vouch), and the recorder writes
@@ -300,12 +300,13 @@ export class StrandFormationManager {
300
300
  }
301
301
  const recorder = this.formationUsageRecorder;
302
302
  const resolved = recorder?.resolveStrand
303
- ? await recorder.resolveStrand(token)
303
+ ? await recorder.resolveStrand(this.requireRead(token, read))
304
304
  : { kind: 'unbound' };
305
305
  try {
306
306
  switch (resolved.kind) {
307
307
  case 'bound': {
308
- // recorder is guaranteed non-null here: only resolveStrand can yield 'bound'.
308
+ // recorder and read are guaranteed here: only resolveStrand can yield 'bound', and it
309
+ // ran with the read.
309
310
  const authorized = await this.authorizeBoundUsage(recorder, {
310
311
  token,
311
312
  peerKey: contact.peerKey,
@@ -314,16 +315,17 @@ export class StrandFormationManager {
314
315
  strandId: resolved.strandId,
315
316
  disclosure: disclosureText,
316
317
  signal
317
- });
318
+ }, read, resolved);
318
319
  // Issued only once authorized (a refused join writes nothing into the strand) and
319
320
  // BEFORE consent is recorded (an issue failure leaves the token unspent, retryable).
320
321
  // The remaining window — invite issued, then record() fails or aborts (a write that
321
- // exhausted its retries, an abort, or the same-node seat race noted at
322
- // `ControlFormationUsageRecorder.assertSeatFree`) — orphans a
323
- // live `Strand.Invite` no joiner ever received; bounded deliberately by its expiry
324
- // ({@link MEMBERSHIP_INVITE_TTL_MS}) rather than compensated, since nothing
322
+ // exhausted its retries, an abort, the same-node seat race noted at
323
+ // `ControlFormationUsageRecorder.assertSeatFree`, or the host strand's row removed
324
+ // since `resolveStrand` read it, which the `StrandExists` CHECK refuses at commit) —
325
+ // orphans a live `Strand.Invite` no joiner ever received; bounded deliberately by its
326
+ // expiry ({@link MEMBERSHIP_INVITE_TTL_MS}) rather than compensated, since nothing
325
327
  // here can atomically un-issue a strand-DB row.
326
- const issued = await this.issueBoundMembershipInvite(token, resolved.strandId, signal);
328
+ const issued = await this.issueBoundMembershipInvite(token, resolved, signal);
327
329
  if (!issued.ok) {
328
330
  return issued.rejection;
329
331
  }
@@ -342,7 +344,7 @@ export class StrandFormationManager {
342
344
  return { approved: false, code: 'host-strand-unavailable', reason: 'Host strand not yet available on this responder' };
343
345
  }
344
346
  case 'unbound':
345
- return await this.provisionUnbound(contact, disclosureText, signal);
347
+ return await this.provisionUnbound(contact, disclosureText, signal, read);
346
348
  }
347
349
  }
348
350
  catch (err) {
@@ -371,14 +373,25 @@ export class StrandFormationManager {
371
373
  return { approved: false, code: 'conflict', reason: 'Formation conflict, retry' };
372
374
  }
373
375
  }
376
+ /**
377
+ * The session's token read. {@link validateToken} takes one whenever a recorder is wired and
378
+ * the listener hands it over, so a missing one is a wiring fault: refused rather than letting
379
+ * the redemption fall through to a path that records no usage.
380
+ */
381
+ requireRead(token, read) {
382
+ if (!read) {
383
+ throw new Error(`Formation for token ${token} reached provisioning without the session's token read`);
384
+ }
385
+ return read;
386
+ }
374
387
  /**
375
388
  * Authorize a BOUND redemption without writing it, through the recorder's optional
376
389
  * {@link FormationUsageRecorder.authorizeUsage}. A recorder without it has nothing to ask up
377
390
  * front, so its handle defers everything to `recordUsage`.
378
391
  */
379
- async authorizeBoundUsage(recorder, params) {
392
+ async authorizeBoundUsage(recorder, params, read, host) {
380
393
  if (recorder.authorizeUsage) {
381
- return await recorder.authorizeUsage(params);
394
+ return await recorder.authorizeUsage(params, read, host);
382
395
  }
383
396
  return { record: () => recorder.recordUsage(params) };
384
397
  }
@@ -402,12 +415,13 @@ export class StrandFormationManager {
402
415
  * The LOG-before-reject keeps both a deliberate internal-error→protocol-rejection
403
416
  * conversion, same as the catch-all in {@link provisionAsResponder}.
404
417
  */
405
- async issueBoundMembershipInvite(token, strandId, signal) {
418
+ async issueBoundMembershipInvite(token, host, signal) {
406
419
  if (!this.issueMembershipInvite) {
407
420
  return { ok: true };
408
421
  }
422
+ const strandId = host.strandId;
409
423
  try {
410
- const invite = await this.issueMembershipInvite(strandId, signal);
424
+ const invite = await this.issueMembershipInvite(host, signal);
411
425
  return { ok: true, invite: invite ?? undefined };
412
426
  }
413
427
  catch (err) {
@@ -434,7 +448,7 @@ export class StrandFormationManager {
434
448
  * 2. else `strandProvisioner` present → the mock/transport-test contract: provision a bare
435
449
  * strand, NO inline usage write (those callers record usage explicitly via
436
450
  * `recordFormationComplete`, or assert transport invariants only). Threads the invite's
437
- * REAL `sAppId`.
451
+ * REAL `sAppId` from the session's read (empty when there is none).
438
452
  * 3. else → a structural placeholder (no recorder + no provisioner ⇒ no single-use semantics
439
453
  * exist to enforce).
440
454
  *
@@ -447,11 +461,11 @@ export class StrandFormationManager {
447
461
  * responder-provisions placeholder as failure). Revisit if production starts publishing
448
462
  * unbound invites, or the mock-transport tests go away.
449
463
  */
450
- async provisionUnbound(contact, disclosureText, signal) {
464
+ async provisionUnbound(contact, disclosureText, signal, read) {
451
465
  const token = contact.token;
452
466
  const recorder = this.formationUsageRecorder;
467
+ const sAppId = read?.invite?.sAppId ?? '';
453
468
  if (recorder?.provisionAndRecord) {
454
- const sAppId = await this.resolveInviteSAppId(token);
455
469
  const provisioned = await recorder.provisionAndRecord({
456
470
  token,
457
471
  peerKey: contact.peerKey,
@@ -460,7 +474,7 @@ export class StrandFormationManager {
460
474
  sAppId,
461
475
  disclosure: disclosureText,
462
476
  signal
463
- });
477
+ }, this.requireRead(token, read));
464
478
  return this.approve({
465
479
  strand: { strandId: provisioned.strandId, createdBy: 'responder' },
466
480
  memberPrivateKey: provisioned.memberPrivateKey ?? undefined,
@@ -468,7 +482,6 @@ export class StrandFormationManager {
468
482
  });
469
483
  }
470
484
  if (this.strandProvisioner) {
471
- const sAppId = await this.resolveInviteSAppId(token);
472
485
  const result = await this.strandProvisioner.provisionStrand(sAppId, contact.partyId, this.partyId);
473
486
  return this.approve({
474
487
  strand: { strandId: result.strandId, createdBy: 'responder' },
@@ -486,17 +499,6 @@ export class StrandFormationManager {
486
499
  approve(result) {
487
500
  return { approved: true, result };
488
501
  }
489
- /**
490
- * The invite's authoritative `sAppId` for the responder-provisions fallback, read back
491
- * from the recorder's `isTokenValid` invitation (the real `FormationInvite.sAppId`).
492
- * Empty string when no recorder is wired or the invitation omits it.
493
- */
494
- async resolveInviteSAppId(token) {
495
- if (!this.formationUsageRecorder)
496
- return '';
497
- const check = await this.formationUsageRecorder.isTokenValid(token);
498
- return check.invitation?.sAppId ?? '';
499
- }
500
502
  // ── Initiator-side result validation ─────────────────────────────────────────
501
503
  async validateResponse(invitation, disclosure, response) {
502
504
  if (this.formationResponseValidator) {
@@ -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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"}
@@ -20,6 +20,7 @@
20
20
  import type { Libp2p } from '@libp2p/interface';
21
21
  import type { StrandFormationDisclosure, StrandMembershipInvite } from './types.js';
22
22
  import { type FormationRejectionCode } from './strand-formation-rejection.js';
23
+ import type { FormationTokenCheck, FormationTokenRead } from './formation-token.js';
23
24
  /** Protocol id for the native formation transport (parallel to `/sereus/seed/1.0.0`). */
24
25
  export declare const FORMATION_PROTOCOL = "/sereus/formation/1.0.0";
25
26
  /**
@@ -85,13 +86,6 @@ export interface FormationRejection {
85
86
  /** Human-facing text for logs; joiners branch on `code`. */
86
87
  reason: string;
87
88
  }
88
- /** What the responder's token check yields; an invalid token names which way it failed. */
89
- export type FormationTokenCheck = {
90
- valid: true;
91
- } | {
92
- valid: false;
93
- code: Extract<FormationRejectionCode, 'token-unknown' | 'token-spent'>;
94
- };
95
89
  /** Initiator → Responder: carries the real token + disclosure + initiator cadre. */
96
90
  export interface FormationContactMessage {
97
91
  /** The real invitation token. */
@@ -201,8 +195,11 @@ export interface FormationListenerOptions {
201
195
  * `signal` is aborted when the listener's work budget expires: a hook that observes it
202
196
  * BEFORE writing abandons the redemption and leaves the invite unspent. Optional so
203
197
  * signal-unaware hooks (tests, mocks) stay assignable.
198
+ *
199
+ * `read` is the token as {@link validateToken} read it for this session, so the hook acts on
200
+ * that read instead of reading the invite again; absent when the token check read nothing.
204
201
  */
205
- provisionStrand(contact: FormationContactMessage, signal?: AbortSignal): Promise<ResponderProvisionOutcome>;
202
+ provisionStrand(contact: FormationContactMessage, signal?: AbortSignal, read?: FormationTokenRead): Promise<ResponderProvisionOutcome>;
206
203
  /** Responder identity, disclosed only AFTER token + disclosure validation passes. */
207
204
  getResponderIdentity(): {
208
205
  partyId: string;
@@ -301,10 +301,10 @@ export class FormationListener {
301
301
  * Deliberately not `withDeadline`: that exposes neither the timed-out flag nor the signal
302
302
  * outside its `op`, and both are needed here.
303
303
  */
304
- async provision(id, contact) {
304
+ async provision(id, contact, read) {
305
305
  const controller = new AbortController();
306
306
  let timedOut = false;
307
- const pending = this.options.provisionStrand(contact, controller.signal);
307
+ const pending = this.options.provisionStrand(contact, controller.signal, read);
308
308
  try {
309
309
  return await withTimeout(this.provisionWorkMs, `Formation provisioning#${id}`, () => pending, () => { timedOut = true; controller.abort(); });
310
310
  }
@@ -442,7 +442,7 @@ export class FormationListener {
442
442
  send({ approved: false, code: 'disclosure-invalid', reason: 'Invalid disclosure' });
443
443
  return;
444
444
  }
445
- const outcome = await this.provision(id, contact);
445
+ const outcome = await this.provision(id, contact, tokenResult.read);
446
446
  if (!outcome) {
447
447
  // Reported as its own retryable reason rather than falling through to the generic
448
448
  // catch below, which would report the misleading 'Internal formation error'.
@@ -488,16 +488,27 @@ export class FormationListener {
488
488
  }
489
489
  }
490
490
  }
491
+ /**
492
+ * Connections a formation dial opened, each with the number of formation exchanges still using
493
+ * it; the entry is dropped when the last one finishes and closes the connection
494
+ * ({@link releaseFormationConnection}). A connection the node already held when its dial started
495
+ * is never entered, so formation never closes someone else's connection.
496
+ *
497
+ * Counted per connection rather than per dial: two formations to one responder machine at once
498
+ * (two pending joins on invitations from one inviter) share the connection libp2p's dial queue
499
+ * opened for the first, and the first to finish must leave it to the second.
500
+ */
501
+ const formationConnectionUsers = new WeakMap();
491
502
  /**
492
503
  * Open the formation stream under the dial deadline, cancelling the dial when it expires.
493
504
  *
494
- * The deadline's signal goes into `dialProtocol`, so an expired dial is abandoned by libp2p
495
- * rather than left running. A dial that settles in the same tick the deadline fires loses
496
- * the race to the timeout rejection and would leave its stream open with nobody to close
497
- * it, so a stream that arrives after the signal aborted is reset here — the same treatment
498
- * `exchangeFrame` (`control-stream.ts`) gives a stream dialed past its deadline. A late
499
- * rejection is the abort itself, or reaches the caller through the awaited copy, so the
500
- * handler below only stops it being reported as unhandled.
505
+ * The deadline's signal goes into the dial and the stream negotiation, so an expired dial is
506
+ * abandoned by libp2p rather than left running. A dial that settles in the same tick the
507
+ * deadline fires loses the race to the timeout rejection and would leave its stream open with
508
+ * nobody to close it, so a channel that arrives after the signal aborted is reset here, and its
509
+ * connection released — the same treatment `exchangeFrame` (`control-stream.ts`) gives a stream
510
+ * dialed past its deadline. A late rejection is the abort itself, or reaches the caller through
511
+ * the awaited copy, so the handler below only stops it being reported as unhandled.
501
512
  *
502
513
  * NOTE: libp2p tries `addrs` one after another, and they all share this one dial budget. Its
503
514
  * per-address limit (`addressDialTimeout`: ten link round trips plus two admission decisions, as
@@ -515,11 +526,10 @@ export class FormationListener {
515
526
  */
516
527
  function openFormationStream(node, addrs, protocolId, dialTimeoutMs) {
517
528
  return withDeadline(dialTimeoutMs, 'Formation dial-connect', async (signal) => {
518
- const pending = node.dialProtocol(addrs, protocolId, { signal });
519
- void pending.then((stream) => {
520
- if (signal.aborted) {
521
- stream.abort(new Error('Formation dial-connect resolved after its deadline'));
522
- }
529
+ const pending = dialFormationChannel(node, addrs, protocolId, signal);
530
+ void pending.then((channel) => {
531
+ if (signal.aborted)
532
+ abandonLateChannel(channel);
523
533
  }, () => { });
524
534
  try {
525
535
  return await pending;
@@ -529,6 +539,80 @@ function openFormationStream(node, addrs, protocolId, dialTimeoutMs) {
529
539
  }
530
540
  });
531
541
  }
542
+ /**
543
+ * Dial the responder machine, then open the formation stream on the connection. The ids of the
544
+ * node's connections are read before the dial: a connection not among them was opened by this
545
+ * dial (or by another formation dial it was joined to), and is closed when formation is done
546
+ * with it. A stream that cannot be opened releases the connection at once.
547
+ *
548
+ * NOTE: a dial of the responder that something other than formation started, and this dial was
549
+ * joined to, also counts as opened by formation and is closed afterwards. Nothing else dials an
550
+ * inviter's control node before formation does; if something starts to, tell the two apart by
551
+ * the connection's own opening time rather than by this snapshot.
552
+ */
553
+ async function dialFormationChannel(node, addrs, protocolId, signal) {
554
+ const heldBefore = new Set(node.getConnections().map((connection) => connection.id));
555
+ const connection = await node.dial(addrs, { signal });
556
+ useFormationConnection(connection, heldBefore.has(connection.id));
557
+ try {
558
+ signal.throwIfAborted();
559
+ const stream = await connection.newStream(protocolId, { signal });
560
+ return { stream: stream, connection };
561
+ }
562
+ catch (error) {
563
+ void releaseFormationConnection(connection);
564
+ throw error;
565
+ }
566
+ }
567
+ function abandonLateChannel({ stream, connection }) {
568
+ stream.abort(new Error('Formation dial-connect resolved after its deadline'));
569
+ void releaseFormationConnection(connection);
570
+ }
571
+ /**
572
+ * Count one more formation exchange on `connection` when a formation dial opened it — this one
573
+ * (`heldBefore` false) or an earlier one still using it.
574
+ */
575
+ function useFormationConnection(connection, heldBefore) {
576
+ const users = formationConnectionUsers.get(connection);
577
+ if (users !== undefined)
578
+ formationConnectionUsers.set(connection, users + 1);
579
+ else if (!heldBefore)
580
+ formationConnectionUsers.set(connection, 1);
581
+ }
582
+ /**
583
+ * End one formation exchange's use of `connection`, closing it when a formation dial opened it
584
+ * and no other exchange still uses it. Never throws.
585
+ *
586
+ * Only the joiner closes: it opened the connection, so it is the side that knows the connection
587
+ * did not exist before. The joiner's and the inviter's control nodes run on different FRET
588
+ * networks, so a connection left open after formation carries nothing but FRET pings that fail,
589
+ * until FRET has been refused three times and leaves that peer alone (p2p-fret 1.2.1). FRET can
590
+ * still redial the other control node on its own while it re-probes a peer it has seen
591
+ * (`complete/fret-maintenance-probes-peers-off-its-ring`).
592
+ *
593
+ * The close is graceful, so the responder's side can finish its own stream close; a close that
594
+ * fails or overruns libp2p's close timeout is logged and the connection aborted, as libp2p's own
595
+ * `hangUp` does. When the responder is a machine of the joiner's own party the connection
596
+ * normally pre-exists (the cadre mesh) and is left alone; if this dial opened it, it is closed,
597
+ * and the mesh re-dials that sibling as after any dropped connection.
598
+ */
599
+ async function releaseFormationConnection(connection) {
600
+ const users = formationConnectionUsers.get(connection);
601
+ if (users === undefined)
602
+ return;
603
+ if (users > 1) {
604
+ formationConnectionUsers.set(connection, users - 1);
605
+ return;
606
+ }
607
+ formationConnectionUsers.delete(connection);
608
+ try {
609
+ await connection.close();
610
+ }
611
+ catch (error) {
612
+ log('closing the formation connection to %s failed; aborting it: %o', connection.remotePeer.toString(), error);
613
+ connection.abort(error instanceof Error ? error : new Error(String(error)));
614
+ }
615
+ }
532
616
  const NO_RESPONDER_ADDRS = 'No responder addresses available for formation';
533
617
  /**
534
618
  * Parse the responder addresses, skipping any entry that does not parse: the list comes from
@@ -626,7 +710,7 @@ function asUnreachable(error) {
626
710
  * included, since it is not an answer either.
627
711
  */
628
712
  async function exchangeContact(node, addrs, contact, budgets) {
629
- const stream = await openFormationStream(node, addrs, budgets.protocolId, budgets.dialTimeoutMs);
713
+ const { stream, connection } = await openFormationStream(node, addrs, budgets.protocolId, budgets.dialTimeoutMs);
630
714
  try {
631
715
  const reader = new FrameReader(stream);
632
716
  writeFrame(stream, contact);
@@ -637,6 +721,7 @@ async function exchangeContact(node, addrs, contact, budgets) {
637
721
  await stream.close();
638
722
  }
639
723
  catch { /* ignore */ }
724
+ void releaseFormationConnection(connection);
640
725
  }
641
726
  }
642
727
  /**