@serfab/cadre-core 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +11 -10
  2. package/dist/cadre-node.d.ts +818 -19
  3. package/dist/cadre-node.js +1952 -99
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/canonical-datetime.d.ts +33 -0
  6. package/dist/canonical-datetime.js +38 -0
  7. package/dist/canonical-datetime.js.map +1 -0
  8. package/dist/canonical-json.d.ts +12 -0
  9. package/dist/canonical-json.js +33 -0
  10. package/dist/canonical-json.js.map +1 -0
  11. package/dist/control-cohort.d.ts +63 -0
  12. package/dist/control-cohort.js +80 -0
  13. package/dist/control-cohort.js.map +1 -0
  14. package/dist/control-database.d.ts +279 -8
  15. package/dist/control-database.js +563 -134
  16. package/dist/control-database.js.map +1 -1
  17. package/dist/control-formation-recorder.d.ts +89 -0
  18. package/dist/control-formation-recorder.js +139 -0
  19. package/dist/control-formation-recorder.js.map +1 -0
  20. package/dist/control-schema.d.ts +12 -0
  21. package/dist/control-schema.js +223 -0
  22. package/dist/control-schema.js.map +1 -0
  23. package/dist/control-stream.d.ts +54 -0
  24. package/dist/control-stream.js +104 -0
  25. package/dist/control-stream.js.map +1 -0
  26. package/dist/device-token.d.ts +50 -0
  27. package/dist/device-token.js +66 -0
  28. package/dist/device-token.js.map +1 -0
  29. package/dist/diagnostics/connection-path.d.ts +118 -0
  30. package/dist/diagnostics/connection-path.js +204 -0
  31. package/dist/diagnostics/connection-path.js.map +1 -0
  32. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  33. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  34. package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
  35. package/dist/ed25519-key.d.ts +42 -0
  36. package/dist/ed25519-key.js +51 -0
  37. package/dist/ed25519-key.js.map +1 -0
  38. package/dist/hibernation-manager.d.ts +79 -0
  39. package/dist/hibernation-manager.js +173 -23
  40. package/dist/hibernation-manager.js.map +1 -1
  41. package/dist/index.d.ts +25 -4
  42. package/dist/index.js +59 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/key-store-file.d.ts +52 -0
  45. package/dist/key-store-file.js +194 -0
  46. package/dist/key-store-file.js.map +1 -0
  47. package/dist/key-store.d.ts +75 -0
  48. package/dist/key-store.js +66 -0
  49. package/dist/key-store.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +48 -0
  51. package/dist/peer-authorization.js +66 -0
  52. package/dist/peer-authorization.js.map +1 -0
  53. package/dist/peer-record.d.ts +89 -0
  54. package/dist/peer-record.js +117 -0
  55. package/dist/peer-record.js.map +1 -0
  56. package/dist/push-credentials.d.ts +34 -0
  57. package/dist/push-credentials.js +81 -0
  58. package/dist/push-credentials.js.map +1 -0
  59. package/dist/push-fanout.d.ts +137 -0
  60. package/dist/push-fanout.js +197 -0
  61. package/dist/push-fanout.js.map +1 -0
  62. package/dist/push-node.d.ts +38 -0
  63. package/dist/push-node.js +48 -0
  64. package/dist/push-node.js.map +1 -0
  65. package/dist/push-notifier-apns.d.ts +61 -0
  66. package/dist/push-notifier-apns.js +199 -0
  67. package/dist/push-notifier-apns.js.map +1 -0
  68. package/dist/push-notifier-fcm.d.ts +44 -0
  69. package/dist/push-notifier-fcm.js +182 -0
  70. package/dist/push-notifier-fcm.js.map +1 -0
  71. package/dist/push-notifier-shared.d.ts +18 -0
  72. package/dist/push-notifier-shared.js +25 -0
  73. package/dist/push-notifier-shared.js.map +1 -0
  74. package/dist/push-notifier.d.ts +52 -0
  75. package/dist/push-notifier.js +22 -0
  76. package/dist/push-notifier.js.map +1 -0
  77. package/dist/schema-verification.d.ts +11 -2
  78. package/dist/schema-verification.js +15 -4
  79. package/dist/schema-verification.js.map +1 -1
  80. package/dist/seed-bootstrap.d.ts +228 -21
  81. package/dist/seed-bootstrap.js +489 -168
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +63 -0
  84. package/dist/seed-trust-policy.js +56 -0
  85. package/dist/seed-trust-policy.js.map +1 -0
  86. package/dist/strand-addr-protocol.d.ts +132 -0
  87. package/dist/strand-addr-protocol.js +313 -0
  88. package/dist/strand-addr-protocol.js.map +1 -0
  89. package/dist/strand-cohort.d.ts +54 -0
  90. package/dist/strand-cohort.js +38 -0
  91. package/dist/strand-cohort.js.map +1 -0
  92. package/dist/strand-database.d.ts +54 -6
  93. package/dist/strand-database.js +88 -100
  94. package/dist/strand-database.js.map +1 -1
  95. package/dist/strand-formation-manager.d.ts +79 -19
  96. package/dist/strand-formation-manager.js +196 -104
  97. package/dist/strand-formation-manager.js.map +1 -1
  98. package/dist/strand-formation-protocol.d.ts +162 -0
  99. package/dist/strand-formation-protocol.js +279 -0
  100. package/dist/strand-formation-protocol.js.map +1 -0
  101. package/dist/strand-instance-manager.d.ts +73 -4
  102. package/dist/strand-instance-manager.js +167 -41
  103. package/dist/strand-instance-manager.js.map +1 -1
  104. package/dist/strand-member-key.d.ts +29 -0
  105. package/dist/strand-member-key.js +38 -0
  106. package/dist/strand-member-key.js.map +1 -0
  107. package/dist/strand-member-registry.d.ts +98 -0
  108. package/dist/strand-member-registry.js +129 -0
  109. package/dist/strand-member-registry.js.map +1 -0
  110. package/dist/strand-membership-writer.d.ts +361 -0
  111. package/dist/strand-membership-writer.js +449 -0
  112. package/dist/strand-membership-writer.js.map +1 -0
  113. package/dist/strand-solicitation.d.ts +77 -2
  114. package/dist/strand-solicitation.js +25 -4
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-wake-payload.d.ts +30 -0
  117. package/dist/strand-wake-payload.js +20 -0
  118. package/dist/strand-wake-payload.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +114 -0
  120. package/dist/strand-wake-protocol.js +257 -0
  121. package/dist/strand-wake-protocol.js.map +1 -0
  122. package/dist/strand-watcher.d.ts +6 -2
  123. package/dist/strand-watcher.js +57 -17
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/types.d.ts +436 -33
  126. package/dist/types.js +22 -5
  127. package/dist/types.js.map +1 -1
  128. package/package.json +18 -8
  129. package/src/cadre-node.ts +2981 -948
  130. package/src/canonical-datetime.ts +39 -0
  131. package/src/canonical-json.ts +30 -0
  132. package/src/control-cohort.ts +103 -0
  133. package/src/control-database.ts +948 -383
  134. package/src/control-formation-recorder.ts +151 -0
  135. package/src/control-schema.ts +222 -0
  136. package/src/control-stream.ts +128 -0
  137. package/src/device-token.ts +89 -0
  138. package/src/diagnostics/connection-path.ts +277 -0
  139. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  140. package/src/ed25519-key.ts +69 -0
  141. package/src/hibernation-manager.ts +197 -28
  142. package/src/index.ts +275 -79
  143. package/src/key-store-file.ts +203 -0
  144. package/src/key-store.ts +99 -0
  145. package/src/peer-authorization.ts +81 -0
  146. package/src/peer-record.ts +146 -0
  147. package/src/push-credentials.ts +84 -0
  148. package/src/push-fanout.ts +272 -0
  149. package/src/push-node.ts +64 -0
  150. package/src/push-notifier-apns.ts +247 -0
  151. package/src/push-notifier-fcm.ts +220 -0
  152. package/src/push-notifier-shared.ts +28 -0
  153. package/src/push-notifier.ts +52 -0
  154. package/src/schema-verification.ts +18 -4
  155. package/src/seed-bootstrap.ts +563 -223
  156. package/src/seed-trust-policy.ts +107 -0
  157. package/src/strand-addr-protocol.ts +410 -0
  158. package/src/strand-cohort.ts +77 -0
  159. package/src/strand-database.ts +113 -141
  160. package/src/strand-formation-manager.ts +233 -142
  161. package/src/strand-formation-protocol.ts +391 -0
  162. package/src/strand-instance-manager.ts +207 -56
  163. package/src/strand-member-key.ts +39 -0
  164. package/src/strand-member-registry.ts +177 -0
  165. package/src/strand-membership-writer.ts +640 -0
  166. package/src/strand-solicitation.ts +93 -4
  167. package/src/strand-wake-payload.ts +32 -0
  168. package/src/strand-wake-protocol.ts +331 -0
  169. package/src/strand-watcher.ts +61 -17
  170. package/src/types.ts +1032 -582
@@ -1,122 +1,107 @@
1
1
  import debug from 'debug';
2
- import { Database, registerPlugin } from '@quereus/quereus';
3
- import cryptoPlugin from '@optimystic/quereus-plugin-crypto/plugin';
4
- import optimysticPlugin from '@optimystic/quereus-plugin-optimystic/plugin';
2
+ import { Database } from '@quereus/quereus';
3
+ import { connectToStrand } from '@serfab/quereus-plugin-sereus';
4
+ import { bootstrapFounderMembership } from './strand-membership-writer.js';
5
+ import { strandMemberKeyPair } from './strand-member-key.js';
5
6
  const log = debug('sereus:cadre:strand-db');
6
7
  const timing = debug('sereus:cadre:timing');
7
8
  /**
8
- * StrandDatabase manages the sApp schema for a strand using Quereus with Optimystic backend.
9
- * Each strand instance has its own isolated database with the sApp's schema applied.
9
+ * StrandDatabase manages the sApp schema for a strand using Quereus with the
10
+ * Optimystic backend. Each strand instance has its own isolated database with
11
+ * the sApp's schema applied.
12
+ *
13
+ * This class owns the `Database` lifecycle (creation, `getDatabase()`, `close()`)
14
+ * but delegates the actual SQL-surface composition — plugin registration, node
15
+ * wiring, catalog hydration, schema apply — to `connectToStrand` from
16
+ * `@serfab/quereus-plugin-sereus`, the single shared composition. The libp2p
17
+ * node is injected here, so `connectToStrand` never *creates* the node;
18
+ * `StrandInstanceManager` owns the node lifecycle. (The strand connection's
19
+ * `shutdown` still stops the injected node via the collection factory, so the
20
+ * manager's own `node.stop()` is an idempotent second stop — see `close()`.)
10
21
  */
11
22
  export class StrandDatabase {
12
23
  constructor(config) {
13
24
  this.db = null;
14
- this.collectionFactory = null;
25
+ this.shutdownStrand = null;
15
26
  this.initialized = false;
16
27
  this.config = config;
17
28
  }
18
29
  /**
19
- * Initialize the database - register plugins and execute sApp schema
30
+ * Initialize the database — create the `Database` and delegate the strand
31
+ * SQL-surface composition (plugins, node wiring, hydrate, schema apply) to
32
+ * `connectToStrand` with the injected libp2p node.
20
33
  */
21
34
  async initialize() {
22
35
  if (this.initialized) {
23
36
  log('StrandDatabase for strand %s already initialized', this.config.strandId);
24
37
  return;
25
38
  }
26
- log('Initializing StrandDatabase for strand: %s (sApp: %s v%s)', this.config.strandId, this.config.sAppConfig.id, this.config.sAppConfig.version);
27
- // Create database instance
28
- this.db = new Database();
29
39
  const sid = this.config.strandId;
30
- // Register crypto plugin (provides digest, sign, verify functions)
31
- let t0 = performance.now();
32
- await registerPlugin(this.db, cryptoPlugin);
33
- timing('[strandDb:%s] cryptoPlugin: %dms', sid, Math.round(performance.now() - t0));
34
- log('Registered crypto plugin');
35
- // Register optimystic plugin. In `bootstrap` mode we route through the
36
- // local transactor so a solo node can initialize (schema apply etc.)
37
- // without network round trips. In `networked` mode the network transactor
38
- // is used. Mode is fixed for the lifetime of this Database instance; the
39
- // caller must restart the strand to transition modes.
40
- t0 = performance.now();
41
- const networkName = `strand-${sid}`;
42
40
  const mode = this.config.mode ?? 'networked';
43
- const defaultTransactor = mode === 'bootstrap' ? 'local' : 'network';
44
- // In bootstrap mode the local transactor IS the data path. Hand the plugin
45
- // the strand's raw storage (same instance the libp2p node uses) so writes
46
- // persist to the host backend instead of in-memory storage.
47
- const rawStorage = this.config.rawStorage;
48
- const pluginConfig = {
49
- default_transactor: defaultTransactor,
50
- default_key_network: 'libp2p',
51
- default_network_name: networkName,
52
- enable_cache: true,
53
- };
54
- if (mode === 'bootstrap' && rawStorage) {
55
- pluginConfig.rawStorageFactory = () => rawStorage;
56
- }
57
- // The plugin's published signature is `Record<string, SqlValue>` but it
58
- // also reads `rawStorageFactory` (a function reference) from the same map.
59
- // Cast through unknown rather than widen the public type.
60
- const pluginResult = optimysticPlugin(this.db, pluginConfig);
61
- log('Optimystic plugin registered for strand %s (mode=%s, transactor=%s, persistentStorage=%s)', sid, mode, defaultTransactor, mode === 'bootstrap' && !!rawStorage);
62
- // Register vtables and functions manually since we need access to collectionFactory
63
- for (const vtable of pluginResult.vtables) {
64
- this.db.registerModule(vtable.name, vtable.module, vtable.auxData);
65
- }
66
- for (const func of pluginResult.functions) {
67
- this.db.registerFunction(func.schema);
68
- }
69
- timing('[strandDb:%s] optimysticPlugin: %dms', sid, Math.round(performance.now() - t0));
70
- this.collectionFactory = pluginResult.collectionFactory;
71
- // Inject the libp2p node into the collection factory
72
- t0 = performance.now();
73
- this.collectionFactory.registerLibp2pNode(networkName, this.config.libp2pNode, this.config.coordinatedRepo);
74
- timing('[strandDb:%s] registerLibp2pNode: %dms', sid, Math.round(performance.now() - t0));
75
- log('Registered libp2p node with collection factory');
76
- // Set optimystic as the default virtual table module so that
77
- // `declare schema` tables (which omit USING) are backed by optimystic
78
- // instead of the built-in memory module.
79
- t0 = performance.now();
80
- this.db.setDefaultVtabName('optimystic');
81
- this.db.setDefaultVtabArgs({
82
- networkName,
83
- transactor: defaultTransactor,
84
- keyNetwork: 'libp2p',
41
+ log('Initializing StrandDatabase for strand: %s (sApp: %s v%s, mode=%s)', sid, this.config.sAppConfig.id, this.config.sAppConfig.version, mode);
42
+ this.db = new Database();
43
+ // Delegate to the shared composition. The node is injected, so:
44
+ // - `connectToStrand` never creates a node (its `createdNode` stays null);
45
+ // `StrandInstanceManager` owns the node lifecycle. Its `shutdown` does
46
+ // still stop the injected node via the collection factory, so the
47
+ // manager's later `node.stop()` is an idempotent second stop;
48
+ // - `storage` (the strand's raw storage, same instance the node uses) is
49
+ // handed to the plugin's local transactor in bootstrap mode so DML lands
50
+ // on the host backend rather than in-memory.
51
+ const t0 = performance.now();
52
+ const result = await connectToStrand(this.db, {
53
+ strandId: sid,
54
+ schema: this.config.sAppConfig.schema,
55
+ mode,
56
+ storage: this.config.rawStorage,
57
+ libp2pNode: this.config.libp2pNode,
58
+ coordinatedRepo: this.config.coordinatedRepo,
59
+ enableCache: true,
85
60
  });
86
- timing('[strandDb:%s] setDefaultVtab: %dms', sid, Math.round(performance.now() - t0));
87
- log('Set default vtab to optimystic (networkName=%s, transactor=%s)', networkName, defaultTransactor);
88
- // Hydrate Quereus's catalog from any persisted optimystic vtab schemas
89
- // before running `apply schema App;`. Without this, a warm restart diffs
90
- // the wrapped DDL against an empty catalog and re-emits CREATE TABLE /
91
- // CREATE INDEX for every persisted object — which round-trips through
92
- // optimystic storage and is the cause of the ~160s warm-start regression
93
- // tracked in strand-database-must-hydrate-catalog-before-apply-schema.
94
- // No-op on first launch (empty storage).
95
- t0 = performance.now();
96
- const hydrated = await pluginResult.hydrate(this.db);
97
- timing('[strandDb:%s] hydrate: %dms (tables=%d, indexes=%d)', sid, Math.round(performance.now() - t0), hydrated.tables, hydrated.indexes);
98
- log('Hydrated catalog for strand %s (tables=%d, indexes=%d)', sid, hydrated.tables, hydrated.indexes);
99
- // Execute the sApp schema DDL
100
- t0 = performance.now();
101
- await this.executeSchema();
102
- timing('[strandDb:%s] executeSchema: %dms', sid, Math.round(performance.now() - t0));
61
+ this.shutdownStrand = result.shutdown;
62
+ timing('[strandDb:%s] connectToStrand: %dms (hydrated tables=%d, indexes=%d)', sid, Math.round(performance.now() - t0), result.hydrated?.tables ?? 0, result.hydrated?.indexes ?? 0);
63
+ // Founder-only: write the one-time membership bootstrap now that the schema is
64
+ // applied (connectToStrand has returned). A throw here propagates out of
65
+ // initialize() so buildStrandRuntime's rollback tears the half-built strand
66
+ // down rather than leaking a node. Joiners skip this — their rows arrive via sync.
67
+ if (this.config.founder === true) {
68
+ await this.bootstrapFounder();
69
+ }
103
70
  this.initialized = true;
104
71
  log('StrandDatabase for strand %s initialized successfully', sid);
105
72
  }
106
- async executeSchema() {
107
- const rawSchema = this.config.sAppConfig.schema;
108
- log('Applying sApp schema for strand %s', this.config.strandId);
109
- // Wrap the raw schema DDL in a declarative schema block and apply it.
110
- // This ensures proper schema management with diff/apply semantics.
111
- // The schema is applied to a named schema 'App' to keep it isolated.
112
- const wrappedSchema = `
113
- declare schema App {
114
- ${rawSchema}
115
- }
116
- apply schema App;
117
- `;
118
- await this.db.exec(wrappedSchema);
119
- log('sApp schema applied to strand %s', this.config.strandId);
73
+ /**
74
+ * Run the founder membership bootstrap against the freshly-composed strand DB.
75
+ *
76
+ * Derives the founding keypair from the closed-strand `memberPrivateKey` (the
77
+ * `Member.Key`/`Manager.MemberKey` are its public key); a closed strand with no
78
+ * `memberPrivateKey` throws, because it could never seat a founding manager.
79
+ * Open strands derive no keypair (Header only). Idempotent — see
80
+ * {@link bootstrapFounderMembership}.
81
+ */
82
+ async bootstrapFounder() {
83
+ const { strandId, strandType, memberPrivateKey, sAppConfig } = this.config;
84
+ const founderKeyPair = strandType === 'c'
85
+ ? this.deriveFounderKeyPair(strandId, memberPrivateKey)
86
+ : undefined;
87
+ await bootstrapFounderMembership(this.db, {
88
+ strandId,
89
+ type: strandType,
90
+ sApp: sAppConfig,
91
+ founderKeyPair,
92
+ });
93
+ }
94
+ /**
95
+ * Derive the founding keypair for a closed strand from its `memberPrivateKey`,
96
+ * failing loudly when the key is absent (a closed strand with no founding
97
+ * Manager can never admit anyone).
98
+ */
99
+ deriveFounderKeyPair(strandId, memberPrivateKey) {
100
+ if (!memberPrivateKey) {
101
+ throw new Error(`Cannot found closed strand ${strandId}: the strand row has no MemberPrivateKey. ` +
102
+ 'A closed strand needs a founding Member/Manager derived from that key.');
103
+ }
104
+ return strandMemberKeyPair(memberPrivateKey);
120
105
  }
121
106
  /**
122
107
  * Get the underlying database for queries
@@ -126,15 +111,18 @@ export class StrandDatabase {
126
111
  return this.db;
127
112
  }
128
113
  /**
129
- * Close the database and cleanup resources
114
+ * Close the database and cleanup resources. Runs the strand-connection
115
+ * shutdown (collection-factory teardown, which also stops the injected node),
116
+ * then closes the `Database`. `StrandInstanceManager.releaseRuntime` issues a
117
+ * further idempotent `node.stop()` after this returns.
130
118
  */
131
119
  async close() {
132
- if (this.collectionFactory) {
133
- await this.collectionFactory.shutdown();
134
- this.collectionFactory = null;
120
+ if (this.shutdownStrand) {
121
+ await this.shutdownStrand();
122
+ this.shutdownStrand = null;
135
123
  }
136
124
  if (this.db) {
137
- this.db.close();
125
+ void this.db.close();
138
126
  this.db = null;
139
127
  }
140
128
  this.initialized = false;
@@ -1 +1 @@
1
- {"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-database.js","sourceRoot":"","sources":["../src/strand-database.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import type { Libp2p } from '@libp2p/interface';
2
2
  import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
3
- import type { DisclosureValidator, FormationUsageRecorder, StrandProvisioner } from './strand-solicitation.js';
3
+ import type { DisclosureValidator, FormationUsageRecorder, StrandProvisioner, FormationResponseValidator } from './strand-solicitation.js';
4
4
  /**
5
5
  * Configuration for StrandFormationManager
6
6
  */
@@ -20,12 +20,14 @@ export interface StrandFormationManagerConfig {
20
20
  * Options for creating a StrandFormationManager
21
21
  */
22
22
  export interface StrandFormationManagerOptions {
23
- /** Validates disclosures from initiators */
23
+ /** Validates disclosures from initiators (responder side) */
24
24
  disclosureValidator?: DisclosureValidator;
25
- /** Records and validates token usage */
25
+ /** Records and validates token usage (responder side) */
26
26
  formationUsageRecorder?: FormationUsageRecorder;
27
- /** Provisions strands after validation */
27
+ /** Provisions strands after validation (responder side) */
28
28
  strandProvisioner?: StrandProvisioner;
29
+ /** Validates the responder's result (initiator side); defaults to a structural check */
30
+ formationResponseValidator?: FormationResponseValidator;
29
31
  /** This party's ID for identification */
30
32
  partyId: string;
31
33
  /** This party's cadre peer addresses */
@@ -34,22 +36,29 @@ export interface StrandFormationManagerOptions {
34
36
  config?: StrandFormationManagerConfig;
35
37
  }
36
38
  /**
37
- * StrandFormationManager bridges cadre-core's strand solicitation interfaces
38
- * with strand-proto's SessionManager for actual protocol handling over libp2p.
39
+ * StrandFormationManager drives the native cadre-core formation transport
40
+ * (`strand-formation-protocol.ts`) from cadre-core's strand-solicitation interfaces.
39
41
  *
40
- * It implements SessionHooks by delegating to the existing interfaces:
41
- * - DisclosureValidator -> validateIdentity
42
- * - FormationUsageRecorder -> validateToken
43
- * - StrandProvisioner -> provisionStrand
42
+ * Responder side: a {@link FormationListener} wires the inbound protocol to
43
+ * {@link FormationUsageRecorder} (token), {@link DisclosureValidator} (identity),
44
+ * and {@link StrandProvisioner} (provisioning), disclosing this party's real
45
+ * identity + cadre only after validation.
46
+ *
47
+ * Initiator side: {@link formStrand} dials the responder carrying the real
48
+ * disclosure/token/cadre, then validates the responder's result via the
49
+ * {@link FormationResponseValidator} (or a built-in structural check).
44
50
  */
45
51
  export declare class StrandFormationManager {
46
- private readonly sessionManager;
47
52
  private readonly disclosureValidator?;
48
53
  private readonly formationUsageRecorder?;
49
54
  private readonly strandProvisioner?;
55
+ private readonly formationResponseValidator?;
50
56
  private readonly partyId;
51
57
  private readonly cadrePeerAddrs;
52
- private registeredNodes;
58
+ private readonly config;
59
+ private readonly listener;
60
+ private readonly registeredNodes;
61
+ private dialerSessions;
53
62
  constructor(options: StrandFormationManagerOptions);
54
63
  /**
55
64
  * Register this manager as a protocol handler on a libp2p node.
@@ -61,12 +70,11 @@ export declare class StrandFormationManager {
61
70
  */
62
71
  unregisterResponder(node: Libp2p, protocolId?: string): void;
63
72
  /**
64
- * Form a strand with a responder via an open invitation.
73
+ * Form a strand with a responder via an open invitation (initiator side).
65
74
  *
66
- * This is the initiator-side operation. It:
67
- * 1. Converts the OpenInvitation to a BootstrapLink
68
- * 2. Calls SessionManager.initiateBootstrap()
69
- * 3. Returns the result as a FormStrandResult
75
+ * 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.
70
78
  */
71
79
  formStrand(invitation: OpenInvitation, disclosure: StrandFormationDisclosure, node: Libp2p): Promise<FormStrandResult>;
72
80
  /**
@@ -76,10 +84,62 @@ export declare class StrandFormationManager {
76
84
  listeners: number;
77
85
  dialers: number;
78
86
  };
87
+ private validateToken;
88
+ private validateDisclosure;
89
+ /**
90
+ * Responder-side provisioning. Routes on how the invite resolves
91
+ * ({@link ResolvedHostStrand}), and can REJECT post-validation by returning a
92
+ * {@link ResponderProvisionOutcome} with `approved: false` — `runSession` turns that
93
+ * into a clean, non-disclosing reply instead of a dropped result frame.
94
+ *
95
+ * - **bound** (host strand present): provision-then-record — write the single
96
+ * `FormationUsage` consent row against the pre-existing strand (record-only) and
97
+ * return it + its membership key (a read-gating secret disclosed only here, behind the
98
+ * token + disclosure validation `runSession` already enforced).
99
+ * - **missing** (invite names a host strand absent on this responder, e.g. unconverged):
100
+ * reject cleanly + retryably, writing NO usage row — recording usage here would fail the
101
+ * deferred `StrandExists` CHECK at commit and drop the frame.
102
+ * - **unbound** (no binding): the responder-provisions fallback — see {@link provisionUnbound}.
103
+ *
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
108
+ * internal-error→protocol-rejection conversion (AGENTS.md: don't eat exceptions silently),
109
+ * not control-flow-by-exception.
110
+ */
111
+ private provisionAsResponder;
112
+ /**
113
+ * Responder-provisions fallback (invite binds no host strand). Precedence:
114
+ *
115
+ * 1. `recorder.provisionAndRecord` present (a real DB recorder) → atomic create-strand +
116
+ * record-consent, so the unbound redemption is single-use just like the bound path.
117
+ * Returns the new strand (+ key, null for an open responder-provisioned strand).
118
+ * 2. else `strandProvisioner` present → the legacy/mock contract: provision a bare strand,
119
+ * NO inline usage write (those callers record usage explicitly via
120
+ * `recordFormationComplete`, or assert transport invariants only). Threads the invite's
121
+ * REAL `sAppId`.
122
+ * 3. else → a structural placeholder (no recorder + no provisioner ⇒ no single-use semantics
123
+ * exist to enforce).
124
+ *
125
+ * Plan tradeoff — option (a) "record usage on the fallback" over (b) "remove the fallback":
126
+ * (a) closes the single-use hole with a targeted atomic create+record and leaves the
127
+ * `StrandProvisioner` mock-transport tests untouched; (b) would delete the provisioner
128
+ * surface and churn ~6 unit + ~6 integration sites for ZERO production benefit (production
129
+ * — `cadre-web.ts` / `cadre-phone.ts` — always publishes strand-BOUND invites and treats
130
+ * the responder-provisions placeholder as failure). The broader "should this fallback exist
131
+ * at all?" question lives in backlog `formation-initiatorcreates-cover-or-remove`.
132
+ */
133
+ private provisionUnbound;
134
+ /** Wrap a provision result as an approving {@link ResponderProvisionOutcome}. */
135
+ private approve;
79
136
  /**
80
- * Create SessionHooks that delegate to our interfaces
137
+ * The invite's authoritative `sAppId` for the responder-provisions fallback, read back
138
+ * from the recorder's `isTokenValid` invitation (the real `FormationInvite.sAppId`).
139
+ * Empty string when no recorder is wired or the invitation omits it.
81
140
  */
82
- private createSessionHooks;
141
+ private resolveInviteSAppId;
142
+ private validateResponse;
83
143
  }
84
144
  /**
85
145
  * Create a StrandFormationManager with the given options