@serfab/cadre-core 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +9 -8
  2. package/dist/authority-key.d.ts +41 -0
  3. package/dist/authority-key.js +52 -0
  4. package/dist/authority-key.js.map +1 -0
  5. package/dist/cadre-node.d.ts +770 -11
  6. package/dist/cadre-node.js +1900 -88
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/canonical-datetime.d.ts +33 -0
  9. package/dist/canonical-datetime.js +38 -0
  10. package/dist/canonical-datetime.js.map +1 -0
  11. package/dist/canonical-json.d.ts +12 -0
  12. package/dist/canonical-json.js +33 -0
  13. package/dist/canonical-json.js.map +1 -0
  14. package/dist/control-cohort.d.ts +63 -0
  15. package/dist/control-cohort.js +80 -0
  16. package/dist/control-cohort.js.map +1 -0
  17. package/dist/control-database.d.ts +259 -5
  18. package/dist/control-database.js +541 -128
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +89 -0
  21. package/dist/control-formation-recorder.js +139 -0
  22. package/dist/control-formation-recorder.js.map +1 -0
  23. package/dist/control-schema.d.ts +12 -0
  24. package/dist/control-schema.js +199 -0
  25. package/dist/control-schema.js.map +1 -0
  26. package/dist/control-stream.d.ts +54 -0
  27. package/dist/control-stream.js +104 -0
  28. package/dist/control-stream.js.map +1 -0
  29. package/dist/device-token.d.ts +50 -0
  30. package/dist/device-token.js +66 -0
  31. package/dist/device-token.js.map +1 -0
  32. package/dist/diagnostics/connection-path.d.ts +118 -0
  33. package/dist/diagnostics/connection-path.js +204 -0
  34. package/dist/diagnostics/connection-path.js.map +1 -0
  35. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  36. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  37. package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
  38. package/dist/hibernation-manager.d.ts +79 -0
  39. package/dist/hibernation-manager.js +173 -23
  40. package/dist/hibernation-manager.js.map +1 -1
  41. package/dist/index.d.ts +25 -4
  42. package/dist/index.js +59 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/key-store-file.d.ts +52 -0
  45. package/dist/key-store-file.js +194 -0
  46. package/dist/key-store-file.js.map +1 -0
  47. package/dist/key-store.d.ts +75 -0
  48. package/dist/key-store.js +66 -0
  49. package/dist/key-store.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +26 -0
  51. package/dist/peer-authorization.js +40 -0
  52. package/dist/peer-authorization.js.map +1 -0
  53. package/dist/peer-record.d.ts +89 -0
  54. package/dist/peer-record.js +117 -0
  55. package/dist/peer-record.js.map +1 -0
  56. package/dist/push-credentials.d.ts +34 -0
  57. package/dist/push-credentials.js +81 -0
  58. package/dist/push-credentials.js.map +1 -0
  59. package/dist/push-fanout.d.ts +137 -0
  60. package/dist/push-fanout.js +197 -0
  61. package/dist/push-fanout.js.map +1 -0
  62. package/dist/push-notifier-apns.d.ts +53 -0
  63. package/dist/push-notifier-apns.js +195 -0
  64. package/dist/push-notifier-apns.js.map +1 -0
  65. package/dist/push-notifier-fcm.d.ts +38 -0
  66. package/dist/push-notifier-fcm.js +183 -0
  67. package/dist/push-notifier-fcm.js.map +1 -0
  68. package/dist/push-notifier-shared.d.ts +18 -0
  69. package/dist/push-notifier-shared.js +25 -0
  70. package/dist/push-notifier-shared.js.map +1 -0
  71. package/dist/push-notifier.d.ts +71 -0
  72. package/dist/push-notifier.js +50 -0
  73. package/dist/push-notifier.js.map +1 -0
  74. package/dist/schema-verification.d.ts +11 -2
  75. package/dist/schema-verification.js +15 -4
  76. package/dist/schema-verification.js.map +1 -1
  77. package/dist/seed-bootstrap.d.ts +197 -9
  78. package/dist/seed-bootstrap.js +396 -137
  79. package/dist/seed-bootstrap.js.map +1 -1
  80. package/dist/seed-trust-policy.d.ts +63 -0
  81. package/dist/seed-trust-policy.js +56 -0
  82. package/dist/seed-trust-policy.js.map +1 -0
  83. package/dist/strand-addr-protocol.d.ts +132 -0
  84. package/dist/strand-addr-protocol.js +313 -0
  85. package/dist/strand-addr-protocol.js.map +1 -0
  86. package/dist/strand-cohort.d.ts +54 -0
  87. package/dist/strand-cohort.js +38 -0
  88. package/dist/strand-cohort.js.map +1 -0
  89. package/dist/strand-database.d.ts +54 -6
  90. package/dist/strand-database.js +88 -100
  91. package/dist/strand-database.js.map +1 -1
  92. package/dist/strand-formation-manager.d.ts +79 -19
  93. package/dist/strand-formation-manager.js +196 -104
  94. package/dist/strand-formation-manager.js.map +1 -1
  95. package/dist/strand-formation-protocol.d.ts +162 -0
  96. package/dist/strand-formation-protocol.js +279 -0
  97. package/dist/strand-formation-protocol.js.map +1 -0
  98. package/dist/strand-instance-manager.d.ts +73 -4
  99. package/dist/strand-instance-manager.js +167 -41
  100. package/dist/strand-instance-manager.js.map +1 -1
  101. package/dist/strand-member-key.d.ts +29 -0
  102. package/dist/strand-member-key.js +38 -0
  103. package/dist/strand-member-key.js.map +1 -0
  104. package/dist/strand-member-registry.d.ts +98 -0
  105. package/dist/strand-member-registry.js +129 -0
  106. package/dist/strand-member-registry.js.map +1 -0
  107. package/dist/strand-membership-writer.d.ts +361 -0
  108. package/dist/strand-membership-writer.js +449 -0
  109. package/dist/strand-membership-writer.js.map +1 -0
  110. package/dist/strand-solicitation.d.ts +77 -2
  111. package/dist/strand-solicitation.js +25 -4
  112. package/dist/strand-solicitation.js.map +1 -1
  113. package/dist/strand-wake-payload.d.ts +30 -0
  114. package/dist/strand-wake-payload.js +20 -0
  115. package/dist/strand-wake-payload.js.map +1 -0
  116. package/dist/strand-wake-protocol.d.ts +114 -0
  117. package/dist/strand-wake-protocol.js +257 -0
  118. package/dist/strand-wake-protocol.js.map +1 -0
  119. package/dist/strand-watcher.d.ts +6 -2
  120. package/dist/strand-watcher.js +57 -17
  121. package/dist/strand-watcher.js.map +1 -1
  122. package/dist/types.d.ts +406 -23
  123. package/dist/types.js +22 -5
  124. package/dist/types.js.map +1 -1
  125. package/package.json +14 -8
  126. package/src/authority-key.ts +68 -0
  127. package/src/cadre-node.ts +2937 -948
  128. package/src/canonical-datetime.ts +39 -0
  129. package/src/canonical-json.ts +30 -0
  130. package/src/control-cohort.ts +103 -0
  131. package/src/control-database.ts +931 -383
  132. package/src/control-formation-recorder.ts +151 -0
  133. package/src/control-schema.ts +198 -0
  134. package/src/control-stream.ts +128 -0
  135. package/src/device-token.ts +89 -0
  136. package/src/diagnostics/connection-path.ts +277 -0
  137. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  138. package/src/hibernation-manager.ts +197 -28
  139. package/src/index.ts +273 -79
  140. package/src/key-store-file.ts +203 -0
  141. package/src/key-store.ts +99 -0
  142. package/src/peer-authorization.ts +53 -0
  143. package/src/peer-record.ts +146 -0
  144. package/src/push-credentials.ts +84 -0
  145. package/src/push-fanout.ts +272 -0
  146. package/src/push-notifier-apns.ts +243 -0
  147. package/src/push-notifier-fcm.ts +221 -0
  148. package/src/push-notifier-shared.ts +28 -0
  149. package/src/push-notifier.ts +93 -0
  150. package/src/schema-verification.ts +18 -4
  151. package/src/seed-bootstrap.ts +464 -188
  152. package/src/seed-trust-policy.ts +107 -0
  153. package/src/strand-addr-protocol.ts +410 -0
  154. package/src/strand-cohort.ts +77 -0
  155. package/src/strand-database.ts +113 -141
  156. package/src/strand-formation-manager.ts +233 -142
  157. package/src/strand-formation-protocol.ts +391 -0
  158. package/src/strand-instance-manager.ts +207 -56
  159. package/src/strand-member-key.ts +39 -0
  160. package/src/strand-member-registry.ts +177 -0
  161. package/src/strand-membership-writer.ts +640 -0
  162. package/src/strand-solicitation.ts +93 -4
  163. package/src/strand-wake-payload.ts +32 -0
  164. package/src/strand-wake-protocol.ts +331 -0
  165. package/src/strand-watcher.ts +61 -17
  166. package/src/types.ts +1012 -582
@@ -1,34 +1,39 @@
1
1
  import debug from 'debug';
2
- import { createBootstrapManager, DEFAULT_PROTOCOL_ID } from '@serfab/strand-proto';
2
+ import { FormationListener, dialFormation, isValidResponderCreatesResult } from './strand-formation-protocol.js';
3
3
  const log = debug('sereus:cadre:formation-manager');
4
4
  /**
5
- * StrandFormationManager bridges cadre-core's strand solicitation interfaces
6
- * with strand-proto's SessionManager for actual protocol handling over libp2p.
5
+ * StrandFormationManager drives the native cadre-core formation transport
6
+ * (`strand-formation-protocol.ts`) from cadre-core's strand-solicitation interfaces.
7
7
  *
8
- * It implements SessionHooks by delegating to the existing interfaces:
9
- * - DisclosureValidator -> validateIdentity
10
- * - FormationUsageRecorder -> validateToken
11
- * - StrandProvisioner -> provisionStrand
8
+ * Responder side: a {@link FormationListener} wires the inbound protocol to
9
+ * {@link FormationUsageRecorder} (token), {@link DisclosureValidator} (identity),
10
+ * and {@link StrandProvisioner} (provisioning), disclosing this party's real
11
+ * identity + cadre only after validation.
12
+ *
13
+ * Initiator side: {@link formStrand} dials the responder carrying the real
14
+ * disclosure/token/cadre, then validates the responder's result via the
15
+ * {@link FormationResponseValidator} (or a built-in structural check).
12
16
  */
13
17
  export class StrandFormationManager {
14
18
  constructor(options) {
15
19
  this.registeredNodes = new Set();
20
+ this.dialerSessions = 0;
16
21
  this.disclosureValidator = options.disclosureValidator;
17
22
  this.formationUsageRecorder = options.formationUsageRecorder;
18
23
  this.strandProvisioner = options.strandProvisioner;
24
+ this.formationResponseValidator = options.formationResponseValidator;
19
25
  this.partyId = options.partyId;
20
26
  this.cadrePeerAddrs = options.cadrePeerAddrs ?? [];
21
- // Create SessionHooks that delegate to our interfaces
22
- const hooks = this.createSessionHooks();
23
- // Create the underlying SessionManager
24
- const config = {
25
- sessionTimeoutMs: options.config?.sessionTimeoutMs ?? 30000,
26
- stepTimeoutMs: options.config?.stepTimeoutMs ?? 5000,
27
- maxConcurrentSessions: options.config?.maxConcurrentSessions ?? 100,
28
- enableDebugLogging: options.config?.enableDebugLogging ?? false,
29
- protocolId: options.config?.protocolId ?? DEFAULT_PROTOCOL_ID
30
- };
31
- this.sessionManager = createBootstrapManager(hooks, config);
27
+ this.config = options.config ?? {};
28
+ this.listener = new FormationListener({
29
+ validateToken: (token) => this.validateToken(token),
30
+ validateDisclosure: (token, disclosure) => this.validateDisclosure(token, disclosure),
31
+ provisionStrand: (token, initiatorPartyId, disclosure) => this.provisionAsResponder(token, initiatorPartyId, disclosure),
32
+ getResponderIdentity: () => ({ partyId: this.partyId, cadrePeerAddrs: this.cadrePeerAddrs }),
33
+ sessionTimeoutMs: this.config.sessionTimeoutMs,
34
+ stepTimeoutMs: this.config.stepTimeoutMs,
35
+ maxConcurrentSessions: this.config.maxConcurrentSessions
36
+ });
32
37
  log('StrandFormationManager created for party: %s', this.partyId);
33
38
  }
34
39
  /**
@@ -40,7 +45,7 @@ export class StrandFormationManager {
40
45
  log('Node already registered');
41
46
  return;
42
47
  }
43
- this.sessionManager.register(node, protocolId);
48
+ this.listener.register(node, protocolId ?? this.config.protocolId);
44
49
  this.registeredNodes.add(node);
45
50
  log('Registered as responder on node');
46
51
  }
@@ -51,112 +56,199 @@ export class StrandFormationManager {
51
56
  if (!this.registeredNodes.has(node)) {
52
57
  return;
53
58
  }
54
- this.sessionManager.unregister(node, protocolId);
59
+ this.listener.unregister(node, protocolId ?? this.config.protocolId);
55
60
  this.registeredNodes.delete(node);
56
61
  log('Unregistered from node');
57
62
  }
58
63
  /**
59
- * Form a strand with a responder via an open invitation.
64
+ * Form a strand with a responder via an open invitation (initiator side).
60
65
  *
61
- * This is the initiator-side operation. It:
62
- * 1. Converts the OpenInvitation to a BootstrapLink
63
- * 2. Calls SessionManager.initiateBootstrap()
64
- * 3. Returns the result as a FormStrandResult
66
+ * 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.
65
69
  */
66
70
  async formStrand(invitation, disclosure, node) {
67
71
  log('Forming strand with invitation token: %s', invitation.token);
68
- // Convert OpenInvitation to BootstrapLink
69
- const link = {
70
- responderPeerAddrs: invitation.bootstrap,
72
+ const contact = {
71
73
  token: invitation.token,
72
- tokenExpiryUtc: invitation.expiration.toISOString(),
73
- mode: 'responderCreates' // Responder provisions the strand
74
- };
75
- // The disclosure is passed via the identityBundle in the contact message
76
- // We store it temporarily so the hooks can access it
77
- // Note: strand-proto's DialerSession constructs the contact message internally
78
- // We need to extend this to pass the disclosure - for now we use partyId
79
- const result = await this.sessionManager.initiateBootstrap(link, node);
80
- log('Strand formed: %s', result.strand.strandId);
81
- return {
82
- memberKey: this.partyId, // The initiator's member key
83
- invitePrivateKey: '', // Would come from key generation
84
- strandId: result.strand.strandId
74
+ partyId: disclosure.partyId ?? this.partyId,
75
+ disclosure,
76
+ cadrePeerAddrs: this.cadrePeerAddrs
85
77
  };
78
+ this.dialerSessions++;
79
+ try {
80
+ const provision = await dialFormation(node, {
81
+ contact,
82
+ responderAddrs: invitation.bootstrap,
83
+ validateResponse: (response) => this.validateResponse(invitation, disclosure, response),
84
+ sessionTimeoutMs: this.config.sessionTimeoutMs,
85
+ stepTimeoutMs: this.config.stepTimeoutMs,
86
+ protocolId: this.config.protocolId
87
+ });
88
+ log('Strand formed: %s', provision.strand.strandId);
89
+ return {
90
+ memberKey: contact.partyId,
91
+ invitePrivateKey: '',
92
+ strandId: provision.strand.strandId,
93
+ // The host strand's membership key, delivered through the protocol (provision-then-record).
94
+ // Undefined for an open strand. Kept separate from invitePrivateKey (the initiator's
95
+ // generated signing key), which is set by the StrandSolicitationService layer.
96
+ memberPrivateKey: provision.memberPrivateKey
97
+ };
98
+ }
99
+ finally {
100
+ this.dialerSessions--;
101
+ }
86
102
  }
87
103
  /**
88
104
  * Get the number of active sessions
89
105
  */
90
106
  getActiveSessionCounts() {
91
- return this.sessionManager.getActiveSessionCounts();
107
+ return { listeners: this.listener.activeCount, dialers: this.dialerSessions };
108
+ }
109
+ // ── Responder-side hooks ─────────────────────────────────────────────────────
110
+ async validateToken(token) {
111
+ if (!this.formationUsageRecorder) {
112
+ // No recorder configured — accept all tokens.
113
+ return { valid: true };
114
+ }
115
+ const tokenCheck = await this.formationUsageRecorder.isTokenValid(token);
116
+ if (!tokenCheck.valid) {
117
+ log('Token invalid: %s', token);
118
+ return { valid: false };
119
+ }
120
+ if (await this.formationUsageRecorder.isTokenUsed(token)) {
121
+ log('Token already used: %s', token);
122
+ return { valid: false };
123
+ }
124
+ return { valid: true };
125
+ }
126
+ async validateDisclosure(token, disclosure) {
127
+ if (!this.disclosureValidator) {
128
+ // No validator configured — accept all disclosures.
129
+ return true;
130
+ }
131
+ return this.disclosureValidator.validateDisclosure(token, disclosure);
92
132
  }
93
133
  /**
94
- * Create SessionHooks that delegate to our interfaces
134
+ * Responder-side provisioning. Routes on how the invite resolves
135
+ * ({@link ResolvedHostStrand}), and can REJECT post-validation by returning a
136
+ * {@link ResponderProvisionOutcome} with `approved: false` — `runSession` turns that
137
+ * into a clean, non-disclosing reply instead of a dropped result frame.
138
+ *
139
+ * - **bound** (host strand present): provision-then-record — write the single
140
+ * `FormationUsage` consent row against the pre-existing strand (record-only) and
141
+ * return it + its membership key (a read-gating secret disclosed only here, behind the
142
+ * token + disclosure validation `runSession` already enforced).
143
+ * - **missing** (invite names a host strand absent on this responder, e.g. unconverged):
144
+ * reject cleanly + retryably, writing NO usage row — recording usage here would fail the
145
+ * deferred `StrandExists` CHECK at commit and drop the frame.
146
+ * - **unbound** (no binding): the responder-provisions fallback — see {@link provisionUnbound}.
147
+ *
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.
95
154
  */
96
- createSessionHooks() {
97
- return {
98
- validateToken: async (token, sessionId) => {
99
- log('validateToken: %s (session: %s)', token, sessionId);
100
- if (!this.formationUsageRecorder) {
101
- // No recorder configured - accept all tokens
102
- return { mode: 'responderCreates', valid: true };
103
- }
104
- const tokenCheck = await this.formationUsageRecorder.isTokenValid(token);
105
- if (!tokenCheck.valid) {
106
- log('Token invalid: %s', token);
107
- return { mode: 'responderCreates', valid: false };
108
- }
109
- const isUsed = await this.formationUsageRecorder.isTokenUsed(token);
110
- if (isUsed) {
111
- log('Token already used: %s', token);
112
- return { mode: 'responderCreates', valid: false };
113
- }
114
- return { mode: 'responderCreates', valid: true };
115
- },
116
- validateIdentity: async (identity, sessionId) => {
117
- log('validateIdentity (session: %s)', sessionId);
118
- if (!this.disclosureValidator) {
119
- // No validator configured - accept all identities
120
- return true;
121
- }
122
- // Extract disclosure from identity bundle
123
- const disclosure = identity;
124
- // We need the token here - it's passed via the contact message
125
- // For now, use a placeholder - this will be refined
126
- const token = identity?.token ?? '';
127
- return this.disclosureValidator.validateDisclosure(token, disclosure);
128
- },
129
- provisionStrand: async (creator, creatorPartyId, otherPartyId, sessionId) => {
130
- log('provisionStrand: creator=%s, other=%s (session: %s)', creatorPartyId, otherPartyId, sessionId);
131
- if (!this.strandProvisioner) {
132
- // No provisioner - return a placeholder
133
- const strandId = `strand-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
134
- return {
135
- strand: { strandId, createdBy: creator },
155
+ async provisionAsResponder(token, initiatorPartyId, _disclosure) {
156
+ const recorder = this.formationUsageRecorder;
157
+ const resolved = recorder?.resolveStrand
158
+ ? await recorder.resolveStrand(token)
159
+ : { kind: 'unbound' };
160
+ try {
161
+ switch (resolved.kind) {
162
+ case 'bound': {
163
+ // recorder is guaranteed non-null here: only resolveStrand can yield 'bound'.
164
+ await recorder.recordUsage(token, initiatorPartyId, resolved.strandId);
165
+ return this.approve({
166
+ strand: { strandId: resolved.strandId, createdBy: 'responder' },
167
+ memberPrivateKey: resolved.memberPrivateKey ?? undefined,
136
168
  dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
137
- };
169
+ });
138
170
  }
139
- // Determine initiator/responder keys based on creator
140
- const initiatorKey = creator === 'initiator' ? creatorPartyId : otherPartyId;
141
- const responderKey = creator === 'responder' ? creatorPartyId : otherPartyId;
142
- const result = await this.strandProvisioner.provisionStrand('', // sAppId - would come from invitation
143
- initiatorKey, responderKey);
144
- return {
145
- strand: { strandId: result.strandId, createdBy: creator },
146
- dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
147
- };
148
- },
149
- validateResponse: async (response, sessionId) => {
150
- log('validateResponse (session: %s)', sessionId);
151
- // Accept all responses for now
152
- return true;
153
- },
154
- validateDatabaseResult: async (result, sessionId) => {
155
- log('validateDatabaseResult (session: %s)', sessionId);
156
- // Accept all database results for now
157
- return true;
171
+ case 'missing': {
172
+ log('Host strand %s not yet available on this responder; rejecting token %s', resolved.strandId, token);
173
+ return { approved: false, reason: 'Host strand not yet available on this responder' };
174
+ }
175
+ case 'unbound':
176
+ return await this.provisionUnbound(token, initiatorPartyId);
158
177
  }
159
- };
178
+ }
179
+ catch (err) {
180
+ log('provisionAsResponder failed for token %s: %o', token, err);
181
+ return { approved: false, reason: 'Formation conflict, retry' };
182
+ }
183
+ }
184
+ /**
185
+ * Responder-provisions fallback (invite binds no host strand). Precedence:
186
+ *
187
+ * 1. `recorder.provisionAndRecord` present (a real DB recorder) → atomic create-strand +
188
+ * record-consent, so the unbound redemption is single-use just like the bound path.
189
+ * Returns the new strand (+ key, null for an open responder-provisioned strand).
190
+ * 2. else `strandProvisioner` present → the legacy/mock contract: provision a bare strand,
191
+ * NO inline usage write (those callers record usage explicitly via
192
+ * `recordFormationComplete`, or assert transport invariants only). Threads the invite's
193
+ * REAL `sAppId`.
194
+ * 3. else → a structural placeholder (no recorder + no provisioner ⇒ no single-use semantics
195
+ * exist to enforce).
196
+ *
197
+ * Plan tradeoff — option (a) "record usage on the fallback" over (b) "remove the fallback":
198
+ * (a) closes the single-use hole with a targeted atomic create+record and leaves the
199
+ * `StrandProvisioner` mock-transport tests untouched; (b) would delete the provisioner
200
+ * surface and churn ~6 unit + ~6 integration sites for ZERO production benefit (production
201
+ * — `cadre-web.ts` / `cadre-phone.ts` — always publishes strand-BOUND invites and treats
202
+ * the responder-provisions placeholder as failure). The broader "should this fallback exist
203
+ * at all?" question lives in backlog `formation-initiatorcreates-cover-or-remove`.
204
+ */
205
+ async provisionUnbound(token, initiatorPartyId) {
206
+ const recorder = this.formationUsageRecorder;
207
+ if (recorder?.provisionAndRecord) {
208
+ const sAppId = await this.resolveInviteSAppId(token);
209
+ const provisioned = await recorder.provisionAndRecord(token, initiatorPartyId, sAppId);
210
+ return this.approve({
211
+ strand: { strandId: provisioned.strandId, createdBy: 'responder' },
212
+ memberPrivateKey: provisioned.memberPrivateKey ?? undefined,
213
+ dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
214
+ });
215
+ }
216
+ if (this.strandProvisioner) {
217
+ const sAppId = await this.resolveInviteSAppId(token);
218
+ const result = await this.strandProvisioner.provisionStrand(sAppId, initiatorPartyId, this.partyId);
219
+ return this.approve({
220
+ strand: { strandId: result.strandId, createdBy: 'responder' },
221
+ dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
222
+ });
223
+ }
224
+ // No recorder + no provisioner — a structural placeholder the initiator can still validate.
225
+ const strandId = `strand-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
226
+ return this.approve({
227
+ strand: { strandId, createdBy: 'responder' },
228
+ dbConnectionInfo: { endpoint: 'local', credentialsRef: '' }
229
+ });
230
+ }
231
+ /** Wrap a provision result as an approving {@link ResponderProvisionOutcome}. */
232
+ approve(result) {
233
+ return { approved: true, result };
234
+ }
235
+ /**
236
+ * The invite's authoritative `sAppId` for the responder-provisions fallback, read back
237
+ * from the recorder's `isTokenValid` invitation (the real `FormationInvite.sAppId`).
238
+ * Empty string when no recorder is wired or the invitation omits it.
239
+ */
240
+ async resolveInviteSAppId(token) {
241
+ if (!this.formationUsageRecorder)
242
+ return '';
243
+ const check = await this.formationUsageRecorder.isTokenValid(token);
244
+ return check.invitation?.sAppId ?? '';
245
+ }
246
+ // ── Initiator-side result validation ─────────────────────────────────────────
247
+ async validateResponse(invitation, disclosure, response) {
248
+ if (this.formationResponseValidator) {
249
+ return this.formationResponseValidator.validateResponse({ invitation, disclosure, response });
250
+ }
251
+ return isValidResponderCreatesResult(response);
160
252
  }
161
253
  }
162
254
  /**
@@ -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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"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Native cadre-core strand-formation transport.
3
+ *
4
+ * Mirrors the non-deprecated `seed-bootstrap.ts` protocol service: a dedicated
5
+ * libp2p protocol id, length-prefixed JSON frames, and small single-purpose
6
+ * session helpers. It replaces the deprecated `@serfab/strand-proto` transport,
7
+ * carrying the caller's REAL invitation token + disclosure and BOTH parties'
8
+ * real cadre peer addresses end-to-end (no `{ partyId: sessionId }` / `cadre-*.local`
9
+ * placeholders), and validating the responder's result on the initiator side.
10
+ *
11
+ * Provisioning flow (`responderCreates`, 2 messages): the responder provisions (or, for
12
+ * provision-then-record, resolves + records consent against) the strand and returns the
13
+ * result on approval.
14
+ *
15
+ * Cadre-disclosure timing (see docs/strand-proto.md "Security & Privacy"): the
16
+ * responder reveals its own party id + cadre addresses — and, for a closed strand
17
+ * returned via provision-then-record, that strand's membership key — ONLY after the
18
+ * token and disclosure validate; a rejection discloses none of them.
19
+ */
20
+ import type { Libp2p } from '@libp2p/interface';
21
+ import type { StrandFormationDisclosure } from './types.js';
22
+ /** Protocol id for the native formation transport (parallel to `/sereus/seed/1.0.0`). */
23
+ export declare const FORMATION_PROTOCOL = "/sereus/formation/1.0.0";
24
+ export type FormationParty = 'initiator' | 'responder';
25
+ export interface FormationStrandInfo {
26
+ strandId: string;
27
+ createdBy: FormationParty;
28
+ }
29
+ export interface FormationDbConnectionInfo {
30
+ endpoint: string;
31
+ credentialsRef: string;
32
+ }
33
+ export interface FormationProvisionResult {
34
+ strand: FormationStrandInfo;
35
+ /**
36
+ * The strand's membership key (closed-strand read-gating secret), delivered to a
37
+ * validated invitee for provision-then-record formation. Disclosed only AFTER token
38
+ * + disclosure validation, exactly like the responder identity/cadre — a rejected or
39
+ * already-used token discloses neither identity nor key. Absent for open strands.
40
+ */
41
+ memberPrivateKey?: string;
42
+ dbConnectionInfo: FormationDbConnectionInfo;
43
+ }
44
+ /**
45
+ * Outcome of the responder's provisioning hook.
46
+ *
47
+ * Distinct from a bare {@link FormationProvisionResult} so the hook can REJECT a
48
+ * formation AFTER token + disclosure validation — e.g. a bound invite naming a host
49
+ * strand this responder has not yet converged on, or a concurrent-redemption collision.
50
+ * Without this channel such cases threw inside provisioning, the stream closed with no
51
+ * result frame, and the initiator saw a read-error/timeout instead of a clean
52
+ * `approved: false`. A rejection still discloses NO responder identity/cadre.
53
+ */
54
+ export type ResponderProvisionOutcome = {
55
+ approved: true;
56
+ result: FormationProvisionResult;
57
+ } | {
58
+ approved: false;
59
+ reason: string;
60
+ };
61
+ /** Initiator → Responder: carries the real token + disclosure + initiator cadre. */
62
+ export interface FormationContactMessage {
63
+ /** The real invitation token. */
64
+ token: string;
65
+ /** Initiator's member key (peer id). */
66
+ partyId: string;
67
+ /** The real disclosure, carried verbatim. */
68
+ disclosure: StrandFormationDisclosure;
69
+ /** Initiator's real multiaddrs. */
70
+ cadrePeerAddrs: string[];
71
+ }
72
+ /** Responder → Initiator: responder identity/cadre disclosed only after validation. */
73
+ export interface FormationResultMessage {
74
+ approved: boolean;
75
+ /** Present iff `approved === false`. */
76
+ reason?: string;
77
+ /** Responder's real party id (disclosed only after validation). */
78
+ partyId?: string;
79
+ /** Responder's real multiaddrs (omitted on rejection). */
80
+ cadrePeerAddrs?: string[];
81
+ /** The provisioned strand/db result (always present on approval). */
82
+ provisionResult?: FormationProvisionResult;
83
+ }
84
+ /**
85
+ * Structural check on a `responderCreates` result: the responder must have approved,
86
+ * disclosed a non-empty real identity + cadre (not the deprecated `cadre-*.local`
87
+ * placeholders), and returned a strand vouched for by the responder party with a real id.
88
+ *
89
+ * `createdBy: 'responder'` means "the responder party vouches for / returns this strand."
90
+ * Under provision-then-record that strand was minted authority-signed earlier (not created
91
+ * in-session), but it is still the responder party returning its own strand, so the marker
92
+ * — and this structural validator — stay unchanged.
93
+ *
94
+ * The behavioral floor for the initiator: a responder that returns an arbitrary/empty
95
+ * `strandId` or omits its disclosed identity is rejected, not silently accepted.
96
+ */
97
+ export declare function isValidResponderCreatesResult(response: FormationResultMessage): boolean;
98
+ export interface FormationListenerOptions {
99
+ /** Validate the invitation token; returns whether it is valid. */
100
+ validateToken(token: string): Promise<{
101
+ valid: boolean;
102
+ }>;
103
+ /** Validate the initiator's disclosure with the REAL token + disclosure. */
104
+ validateDisclosure(token: string, disclosure: StrandFormationDisclosure): Promise<boolean>;
105
+ /**
106
+ * 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.
109
+ *
110
+ * Returns a {@link ResponderProvisionOutcome}: the hook may REJECT post-validation
111
+ * (e.g. an unconverged host strand) and the listener turns that into a clean,
112
+ * non-disclosing `approved: false` reply rather than dropping the result frame.
113
+ */
114
+ provisionStrand(token: string, initiatorPartyId: string, disclosure: StrandFormationDisclosure): Promise<ResponderProvisionOutcome>;
115
+ /** Responder identity, disclosed only AFTER token + disclosure validation passes. */
116
+ getResponderIdentity(): {
117
+ partyId: string;
118
+ cadrePeerAddrs: string[];
119
+ };
120
+ sessionTimeoutMs?: number;
121
+ stepTimeoutMs?: number;
122
+ maxConcurrentSessions?: number;
123
+ }
124
+ /**
125
+ * Responder side of the native formation protocol. Registers a libp2p handler and
126
+ * drives each inbound stream through token + disclosure validation, provisioning,
127
+ * and result delivery — enforcing the cadre-disclosure timing rule (responder cadre
128
+ * is revealed only after validation; rejections disclose nothing).
129
+ */
130
+ export declare class FormationListener {
131
+ private readonly options;
132
+ private readonly sessionTimeoutMs;
133
+ private readonly stepTimeoutMs;
134
+ private readonly maxConcurrentSessions;
135
+ private readonly registered;
136
+ private activeSessions;
137
+ private sessionCounter;
138
+ constructor(options: FormationListenerOptions);
139
+ /** Number of in-flight inbound sessions. */
140
+ get activeCount(): number;
141
+ register(node: Libp2p, protocolId?: string): void;
142
+ unregister(node: Libp2p, protocolId?: string): void;
143
+ private handleStream;
144
+ private runSession;
145
+ }
146
+ export interface FormationDialOptions {
147
+ /** The contact message to send (real token, partyId, disclosure, initiator cadre). */
148
+ contact: FormationContactMessage;
149
+ /** Responder multiaddrs to dial. */
150
+ responderAddrs: string[];
151
+ /** Validate the responder's result; a false return aborts the formation. */
152
+ validateResponse(response: FormationResultMessage): Promise<boolean>;
153
+ sessionTimeoutMs?: number;
154
+ stepTimeoutMs?: number;
155
+ protocolId?: string;
156
+ }
157
+ /**
158
+ * Initiator side of the native formation protocol. Dials the responder, sends the
159
+ * contact (carrying the real disclosure/token/cadre), validates the responder's
160
+ * result, and returns the strand the responder provisioned.
161
+ */
162
+ export declare function dialFormation(node: Libp2p, options: FormationDialOptions): Promise<FormationProvisionResult>;