reploid 0.0.0-stage → 0.1.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 (233) hide show
  1. package/apps/poolday/config/chat-files.json +27 -0
  2. package/apps/poolday/config/partition-generation.js +21 -0
  3. package/apps/poolday/config/partition-policy.json +74 -0
  4. package/apps/poolday/config/partition-releases.json +3 -0
  5. package/apps/poolday/config/work-profile.json +122 -0
  6. package/apps/poolday/host/chat-view.js +47 -0
  7. package/apps/poolday/host/earning/client.js +199 -0
  8. package/apps/poolday/host/earning/contribution.js +168 -0
  9. package/apps/poolday/host/partition-release.js +33 -0
  10. package/apps/poolday/host/work-partition-artifact-source.js +37 -0
  11. package/apps/poolday/host/work-partition-capsule.js +39 -0
  12. package/apps/poolday/host/work-partition-plan.js +167 -0
  13. package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
  14. package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
  15. package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
  16. package/apps/poolday/pool/pack-operation.js +161 -0
  17. package/apps/poolday/pool/pack-release-policy.js +44 -0
  18. package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
  19. package/apps/poolday/providers/work-adapter.js +67 -0
  20. package/apps/poolday/providers/work-device.js +33 -0
  21. package/apps/poolday/providers/work-generation-completion.js +55 -0
  22. package/apps/poolday/providers/work-provider.js +209 -0
  23. package/apps/poolday/providers/work-resident-provider.js +113 -0
  24. package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
  25. package/docs/fleet-service.md +214 -0
  26. package/docs/native-participant.md +542 -0
  27. package/native-delivery.json +1174 -0
  28. package/package.json +29 -4
  29. package/packages/reploid/src/artifacts/binary.js +25 -0
  30. package/packages/reploid/src/artifacts/canonical-json.js +23 -0
  31. package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
  32. package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
  33. package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
  34. package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
  35. package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
  36. package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
  37. package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
  38. package/packages/reploid/src/artifacts/identity.js +91 -0
  39. package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
  40. package/packages/reploid/src/artifacts/receipt.js +131 -0
  41. package/packages/reploid/src/artifacts/signatures.js +94 -0
  42. package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
  43. package/packages/reploid/src/cancellation.js +44 -0
  44. package/packages/reploid/src/config/defaults.json +110 -0
  45. package/packages/reploid/src/config/index.js +209 -0
  46. package/packages/reploid/src/config/request-overrides.json +9 -0
  47. package/packages/reploid/src/config/transport-defaults.json +7 -0
  48. package/packages/reploid/src/hashing.js +30 -0
  49. package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
  50. package/packages/reploid/src/measurement/index.js +5 -0
  51. package/packages/reploid/src/measurement/readiness-records.js +310 -0
  52. package/packages/reploid/src/mesh/computer-identity.js +50 -0
  53. package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
  54. package/packages/reploid/src/mesh/contribution.js +141 -0
  55. package/packages/reploid/src/mesh/generation-recovery.js +58 -0
  56. package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
  57. package/packages/reploid/src/mesh/gpu-signature.js +136 -0
  58. package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
  59. package/packages/reploid/src/mesh/index.js +66 -0
  60. package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
  61. package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
  62. package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
  63. package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
  64. package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
  65. package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
  66. package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
  67. package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
  68. package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
  69. package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
  70. package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
  71. package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
  72. package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
  73. package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
  74. package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
  75. package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
  76. package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
  77. package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
  78. package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
  79. package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
  80. package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
  81. package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
  82. package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
  83. package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
  84. package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
  85. package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
  86. package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
  87. package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
  88. package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
  89. package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
  90. package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
  91. package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
  92. package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
  93. package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
  94. package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
  95. package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
  96. package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
  97. package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
  98. package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
  99. package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
  100. package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
  101. package/packages/reploid/src/mesh/peer-generation.js +896 -0
  102. package/packages/reploid/src/mesh/peer-identity.js +87 -0
  103. package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
  104. package/packages/reploid/src/mesh/preparation-progress.js +46 -0
  105. package/packages/reploid/src/mesh/provider-hardware.js +11 -0
  106. package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
  107. package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
  108. package/packages/reploid/src/mesh/web-access-network.js +112 -0
  109. package/packages/reploid/src/mesh/web-access-policy.json +17 -0
  110. package/packages/reploid/src/retrieval/citations.js +156 -0
  111. package/packages/reploid/src/retrieval/claim-support.js +113 -0
  112. package/packages/reploid/src/retrieval/costs.js +101 -0
  113. package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
  114. package/packages/reploid/src/retrieval/index.js +20 -0
  115. package/packages/reploid/src/retrieval/lexical-support.js +27 -0
  116. package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
  117. package/packages/reploid/src/retrieval/page-fetch.json +10 -0
  118. package/packages/reploid/src/retrieval/page-text.js +182 -0
  119. package/packages/reploid/src/retrieval/passage.js +37 -0
  120. package/packages/reploid/src/retrieval/policy.json +14 -0
  121. package/packages/reploid/src/retrieval/retrieval.js +23 -0
  122. package/packages/reploid/src/retrieval/review-queue.js +76 -0
  123. package/packages/reploid/src/retrieval/sources.js +263 -0
  124. package/packages/reploid/src/retrieval/support-review.js +155 -0
  125. package/packages/reploid/src/retrieval/text.js +21 -0
  126. package/packages/reploid/src/rules/contribution.rules.json +8 -0
  127. package/packages/reploid/src/rules/routing.rules.json +24 -0
  128. package/packages/reploid/src/transport/assignment.js +571 -0
  129. package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
  130. package/packages/reploid/src/transport/index.js +11 -0
  131. package/packages/reploid/src/transport/introduction-admission.js +24 -0
  132. package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
  133. package/packages/reploid/src/transport/introduction-policy.json +12 -0
  134. package/packages/reploid/src/transport/introduction-records.js +90 -0
  135. package/packages/reploid/src/transport/introductions.js +209 -0
  136. package/packages/reploid/src/transport/partition-data-channel.js +421 -0
  137. package/packages/reploid/src/transport/partition-errors.json +16 -0
  138. package/packages/reploid/src/transport/retry-policy.js +36 -0
  139. package/packages/reploid/src/transport/room.js +502 -0
  140. package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
  141. package/packages/reploid/src/transport/signaling.js +571 -0
  142. package/packages/reploid/src/transport/swarm.js +1247 -0
  143. package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
  144. package/server/browser-roots.js +42 -0
  145. package/server/browser-web-worker.js +94 -0
  146. package/server/chat-completions.js +45 -0
  147. package/server/fleet/browser-bridge.js +145 -0
  148. package/server/fleet/contract.js +42 -0
  149. package/server/fleet/credit-evidence.js +103 -0
  150. package/server/fleet/credit-recovery.js +175 -0
  151. package/server/fleet/credits.js +178 -0
  152. package/server/fleet/execution-admission.js +32 -0
  153. package/server/fleet/native-host.js +817 -0
  154. package/server/fleet/placement.js +132 -0
  155. package/server/fleet/requester.js +346 -0
  156. package/server/fleet/service.js +311 -0
  157. package/server/fleet/terminal-policy.js +83 -0
  158. package/server/fleet/text-api.js +94 -0
  159. package/server/hashing.js +35 -0
  160. package/server/http-request-owner.js +49 -0
  161. package/server/messages.js +57 -0
  162. package/server/native-canonical-epochs.js +90 -0
  163. package/server/native-change-log.js +40 -0
  164. package/server/native-earning.js +203 -0
  165. package/server/native-generation-journal.js +46 -0
  166. package/server/native-model-config.js +57 -0
  167. package/server/native-model-files.js +382 -0
  168. package/server/native-model-origin.js +54 -0
  169. package/server/native-model-origins.json +12 -0
  170. package/server/native-node.js +85 -0
  171. package/server/native-owner/controls.js +162 -0
  172. package/server/native-owner/monitor.js +251 -0
  173. package/server/native-owner/policy.js +273 -0
  174. package/server/native-owner/rate.js +92 -0
  175. package/server/native-participant.js +866 -0
  176. package/server/native-readiness-log.js +30 -0
  177. package/server/native-release-storage.js +64 -0
  178. package/server/native-shader-cache.js +369 -0
  179. package/server/native-supply-budget.js +66 -0
  180. package/server/native-whole-earning.js +67 -0
  181. package/server/native-whole-model-fill.js +182 -0
  182. package/server/native-whole-model.js +130 -0
  183. package/server/public-web-worker.js +66 -0
  184. package/server/responses.js +92 -0
  185. package/server/search/errors.js +47 -0
  186. package/server/search/page-fetch.js +185 -0
  187. package/server/text-api.js +83 -0
  188. package/server/text-recovery.js +45 -0
  189. package/server/text-request-options.js +23 -0
  190. package/server/text-requester.js +231 -0
  191. package/server/token-auth.js +9 -0
  192. package/server/whole-model-requester.js +99 -0
  193. package/shared/config/chat-models.json +4685 -0
  194. package/shared/config/doppler-execution-contracts.js +32 -0
  195. package/shared/config/doppler-execution-contracts.json +43 -0
  196. package/shared/config/doppler-generation-contract.js +223 -0
  197. package/shared/config/doppler-local-models.js +79 -0
  198. package/shared/config/doppler-package.json +7 -0
  199. package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
  200. package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
  201. package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
  202. package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
  203. package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
  204. package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
  205. package/shared/config/reploid-library.json +49 -0
  206. package/shared/config/swarm-bootstrap.json +21 -0
  207. package/shared/infrastructure/doppler-runtime-service.js +261 -0
  208. package/shared/pool/executable-pack.js +87 -0
  209. package/shared/pool/pack-operation-adapters.js +124 -0
  210. package/shared/pool/pack-operation-policy.js +75 -0
  211. package/shared/pool/pool-config.json +2280 -0
  212. package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
  213. package/shared/vendor/reploid/artifacts/binary.js +25 -0
  214. package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
  215. package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
  216. package/shared/vendor/reploid/artifacts/identity.js +91 -0
  217. package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
  218. package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
  219. package/shared/vendor/reploid/artifacts/signatures.js +94 -0
  220. package/shared/vendor/reploid/config/defaults.json +110 -0
  221. package/shared/vendor/reploid/config/index.js +209 -0
  222. package/shared/vendor/reploid/config/request-overrides.json +9 -0
  223. package/shared/vendor/reploid/hashing.js +30 -0
  224. package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
  225. package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
  226. package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
  227. package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
  228. package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
  229. package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
  230. package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
  231. package/shared/vendor/reploid/rules/routing.rules.json +24 -0
  232. package/shared/vendor/reploid/transport/partition-errors.json +16 -0
  233. package/README.md +0 -3
@@ -0,0 +1,277 @@
1
+ import { createPeerIdFromPublicJwk, importSigningKey, importVerificationKey, getIdentitySignAlgorithm,
2
+ toBase64Url, fromBase64Url } from '../../artifacts/identity.js';
3
+ import { assertPartition as assert, canonicalPartitionJson, validatePartitionIdentity,
4
+ samePartitionIdentity, partitionActions, partitionIdentityKeys, canonicalRouteAttemptBinding, sameRouteAttemptIdentity } from './partition-contract.js';
5
+
6
+ const SCHEMA = 'reploid.partition-grant/v2';
7
+ const CANONICAL_SCHEMA = 'reploid.partition-grant/v3';
8
+ const ORDERED_SCHEMA = 'reploid.partition-grant/v4';
9
+ const bytes = value => new TextEncoder().encode(canonicalPartitionJson(value));
10
+ const bounds = ['maxTokens', 'maxPromptTokens', 'maxActivationBytes', 'maxOutputCharacters'];
11
+
12
+ /** Explicit disclosure closure for the ordinary stage-zero coordinator path. */
13
+ export function orderedPartitionDisclosure(identity) {
14
+ const binding = canonicalRouteAttemptBinding(identity), stages = binding.route.stages;
15
+ const coordinator = stages[0].participantId, unique = values => [...new Set(values)];
16
+ return { kind: 'coordinator-mediated-activations-and-tokens',
17
+ activationPaths: stages.slice(1).map((stage, index) => unique([stages[index].participantId, coordinator, stage.participantId])),
18
+ tokenRecipients: stages.map(stage => stage.participantId),
19
+ outputRecipients: unique([stages.at(-1).participantId, coordinator, binding.requesterId]) };
20
+ }
21
+
22
+ /** The host calls issue only after disclosure approval. B separately consents by preparing its resident. */
23
+ export function createPartitionGrantAuthority({ identity, meshId, maxGrants, maxTtlMs, maxClockSkewMs = 0, now = Date.now, canonicalRuntime }) {
24
+ assert(identity?.privateJwk && identity?.publicJwk && typeof meshId === 'string' && meshId
25
+ && Number.isSafeInteger(maxGrants) && maxGrants > 0 && Number.isSafeInteger(maxTtlMs) && maxTtlMs > 0
26
+ && Number.isSafeInteger(maxClockSkewMs) && maxClockSkewMs >= 0,
27
+ 'Explicit partition signing identity, mesh and grant budgets required');
28
+ const signer = structuredClone(identity);
29
+ const issued = new Map();
30
+ let closed = false;
31
+ // Signatures already verified, keyed by signature and bound to the exact canonical grant record.
32
+ // Later checks of the same grant skip only the key derivation, import and signature verification;
33
+ // binding, scope, expiry and revocation are evaluated on every call. Bounded like issuance.
34
+ const verified = new Map();
35
+ const OK = Object.freeze({ ok: true, reason: null });
36
+ const refused = reason => ({ ok: false, reason });
37
+ const DECLINED = Object.freeze(refused('declined'));
38
+ async function signatureValid(record, signerId, signed) {
39
+ const json = canonicalPartitionJson(record);
40
+ const known = verified.get(record.signature);
41
+ if (known?.json === json && known.signerId === signerId) return true;
42
+ if (await createPeerIdFromPublicJwk(record.publicJwk) !== signerId) return false;
43
+ const key = await importVerificationKey(record.publicJwk);
44
+ if (!await crypto.subtle.verify(getIdentitySignAlgorithm(record.publicJwk), key, fromBase64Url(record.signature), signed)) return false;
45
+ const at = now();
46
+ for (const [signature, entry] of verified) if (entry.expiresAt <= at) verified.delete(signature);
47
+ while (verified.size >= maxGrants) verified.delete(verified.keys().next().value);
48
+ verified.set(record.signature, { json, signerId, expiresAt: record.claim.expiresAt });
49
+ return true;
50
+ }
51
+ // Expiry and revocation are re-evaluated after the asynchronous signature check.
52
+ const current = (claim, settlement) => closed ? DECLINED : settlement ? OK
53
+ : issued.get(claim.id)?.revoked === true ? refused('revoked') : claim.expiresAt <= now() ? refused('expired') : OK;
54
+ const canonicalCapability = () => assert(['metadataDigest', 'canonicalGenerationIdentity', 'validateResidentAttemptIdentity', 'residentAttemptDigest']
55
+ .every(method => typeof canonicalRuntime?.[method] === 'function'), 'Canonical Doppler grant capability required');
56
+ const exact = (value, keys) => assert(value && typeof value === 'object' && !Array.isArray(value)
57
+ && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key)), 'Invalid canonical grant fields');
58
+ const canonicalDigest = value => canonicalRuntime.metadataDigest(CANONICAL_SCHEMA, value);
59
+ const canonicalBytes = value => new TextEncoder().encode(canonicalDigest(value));
60
+ const canonicalScope = ({ identity: binding, limits, disclosure, generationId }) => {
61
+ canonicalCapability(); validatePartitionIdentity(binding);
62
+ exact(binding, [...partitionIdentityKeys, 'requesterId', 'placementGeneration']);
63
+ assert(typeof binding.requesterId === 'string' && bounds.every(key => Number.isSafeInteger(limits?.[key]) && limits[key] > 0)
64
+ && disclosure === 'partition-activations-and-tokens' && /^sha256:[a-f0-9]{64}$/.test(generationId),
65
+ 'Explicit canonical route, disclosure and allocation required');
66
+ return canonicalRuntime.metadataDigest('reploid.partition-authorization/v1', { meshId, identity: binding,
67
+ limits: Object.fromEntries(bounds.map(key => [key, limits[key]])), disclosure, generationId });
68
+ };
69
+ const canonicalClaim = claim => {
70
+ canonicalCapability();
71
+ canonicalDigest(claim);
72
+ exact(claim, ['schema', 'id', 'meshId', 'identity', 'canonicalAttempt', 'generation', 'vocabSize',
73
+ 'disclosure', 'issuedAt', 'expiresAt', 'limits']);
74
+ exact(claim.limits, bounds); exact(claim.generation, ['policy', 'executionIdentity', 'generationId']);
75
+ assert(claim.schema === CANONICAL_SCHEMA && claim.meshId === meshId, 'Canonical grant scope mismatch');
76
+ const generation = canonicalRuntime.canonicalGenerationIdentity(claim.generation.policy,
77
+ claim.generation.executionIdentity, claim.vocabSize);
78
+ assert(canonicalDigest(generation) === canonicalDigest(claim.generation), 'Canonical generation identity mismatch');
79
+ const attempt = canonicalRuntime.validateResidentAttemptIdentity(claim.canonicalAttempt);
80
+ assert(attempt.attemptId === claim.identity.attemptId && attempt.requesterId === claim.identity.requesterId
81
+ && attempt.generationId === generation.generationId && attempt.routeKind === 'two-stage-layer'
82
+ && attempt.stages[0].participantId === claim.identity.participantA
83
+ && attempt.stages[1].participantId === claim.identity.participantB
84
+ && attempt.stages.every(stage => stage.executionId === generation.executionIdentity), 'Canonical attempt route binding mismatch');
85
+ assert(attempt.authorizationScope === canonicalScope({ ...claim, generationId: generation.generationId }),
86
+ 'Canonical authorization scope mismatch');
87
+ assert(generation.policy.maxGeneratedTokens <= claim.limits.maxTokens
88
+ && generation.policy.maxRawOutputUtf16 <= claim.limits.maxOutputCharacters
89
+ && generation.policy.maxVisibleOutputUtf16 <= claim.limits.maxOutputCharacters,
90
+ 'Canonical generation exceeds grant allocation');
91
+ assert(typeof claim.id === 'string' && claim.id.length > 0 && claim.id.length <= 128
92
+ && Number.isSafeInteger(claim.issuedAt) && claim.issuedAt >= 0 && Number.isSafeInteger(claim.expiresAt)
93
+ && claim.issuedAt <= now() + maxClockSkewMs && claim.expiresAt > claim.issuedAt
94
+ && claim.expiresAt - claim.issuedAt <= maxTtlMs, 'Invalid canonical grant lifetime');
95
+ return { generation, attempt };
96
+ };
97
+ async function checkCanonical(grant, request, settlement) {
98
+ try {
99
+ if (closed) return DECLINED;
100
+ canonicalCapability(); canonicalDigest({ grant, request });
101
+ const record = structuredClone(grant), input = structuredClone(request), claim = record.claim;
102
+ exact(record, ['claim', 'publicJwk', 'signature']);
103
+ const { generation, attempt } = canonicalClaim(claim);
104
+ const requested = canonicalRuntime.validateResidentAttemptIdentity(input.canonicalAttempt);
105
+ if (!samePartitionIdentity(claim.identity, input.identity)
106
+ || canonicalRuntime.residentAttemptDigest(attempt) !== canonicalRuntime.residentAttemptDigest(requested)) return DECLINED;
107
+ if (!settlement) {
108
+ if (input.generationId !== generation.generationId || !partitionActions.includes(input.action)
109
+ || !['admit', 'step'].includes(input.phase) || !Number.isSafeInteger(input.step) || input.step < 0
110
+ || !Number.isSafeInteger(input.inputTokenCount) || input.inputTokenCount < 1
111
+ || input.inputTokenCount > (input.step === 0 ? Math.min(claim.limits.maxPromptTokens,
112
+ generation.policy.materializedInputCapacity, generation.policy.totalAcceptedTokenLimit) : 1)
113
+ || input.phase === 'admit' && (input.step !== 0 || input.activationBytes != null && input.activationBytes !== 0)
114
+ || input.phase === 'step' && input.step >= generation.policy.maxGeneratedTokens
115
+ || input.activationBytes != null && (!Number.isSafeInteger(input.activationBytes)
116
+ || input.activationBytes < 0 || input.activationBytes > claim.limits.maxActivationBytes)) return DECLINED;
117
+ }
118
+ if (!await signatureValid(record, claim.identity.participantA, canonicalBytes(claim))) return DECLINED;
119
+ return current(claim, settlement);
120
+ } catch { return DECLINED; }
121
+ }
122
+ const orderedClaim = claim => {
123
+ exact(claim, ['schema', 'id', 'meshId', 'identity', 'coordinatorId', 'disclosure', 'generationDigest', 'issuedAt', 'expiresAt', 'limits']);
124
+ const binding = canonicalRouteAttemptBinding(claim.identity);
125
+ exact(claim.limits, bounds);
126
+ assert(claim.schema === ORDERED_SCHEMA && claim.meshId === meshId
127
+ && claim.coordinatorId === binding.route.stages[0].participantId
128
+ && canonicalPartitionJson(claim.disclosure) === canonicalPartitionJson(orderedPartitionDisclosure(binding)),
129
+ 'Explicit ordered route disclosure mismatch');
130
+ assert(typeof claim.id === 'string' && claim.id.length > 0 && claim.id.length <= 128
131
+ && /^sha256:[a-f0-9]{64}$/.test(claim.generationDigest)
132
+ && bounds.every(key => Number.isSafeInteger(claim.limits[key]) && claim.limits[key] > 0)
133
+ && Number.isSafeInteger(claim.issuedAt) && claim.issuedAt >= 0 && Number.isSafeInteger(claim.expiresAt)
134
+ && claim.issuedAt <= now() + maxClockSkewMs && claim.expiresAt > claim.issuedAt
135
+ && claim.expiresAt - claim.issuedAt <= maxTtlMs, 'Invalid ordered grant allocation or lifetime');
136
+ return binding;
137
+ };
138
+ async function checkOrdered(grant, request, settlement) {
139
+ try {
140
+ if (closed) return DECLINED;
141
+ const record = structuredClone(grant), claim = record.claim;
142
+ // Host authorization calls also carry borrowed AbortSignals/ports. Snapshot
143
+ // only the signed protocol inputs, before any asynchronous verification.
144
+ const input = structuredClone(Object.fromEntries(['identity', 'coordinatorId', 'action', 'partitionIndex',
145
+ 'fromStage', 'toStage', 'recipients', 'step', 'inputTokenCount', 'activationBytes', 'generationDigest']
146
+ .filter(key => Object.hasOwn(request, key)).map(key => [key, request[key]])));
147
+ exact(record, ['claim', 'publicJwk', 'signature']);
148
+ const binding = orderedClaim(claim), stages = binding.route.stages;
149
+ if (!sameRouteAttemptIdentity(binding, input.identity) || input.coordinatorId !== claim.coordinatorId) return DECLINED;
150
+ const validStage = Number.isSafeInteger(input.partitionIndex) && input.partitionIndex >= 0 && input.partitionIndex < stages.length;
151
+ if (settlement) {
152
+ if (input.action !== 'mesh.execute_partition_stage' || !validStage) return DECLINED;
153
+ } else {
154
+ if (input.generationDigest !== claim.generationDigest || !Number.isSafeInteger(input.step)
155
+ || input.step < 0 || input.step >= claim.limits.maxTokens || !Number.isSafeInteger(input.inputTokenCount)
156
+ || input.inputTokenCount < 1 || input.inputTokenCount > (input.step === 0 ? claim.limits.maxPromptTokens : 1)
157
+ || input.activationBytes != null && (!Number.isSafeInteger(input.activationBytes)
158
+ || input.activationBytes < 0 || input.activationBytes > claim.limits.maxActivationBytes)) return DECLINED;
159
+ let recipients;
160
+ if (input.action === 'mesh.execute_partition_stage') { if (!validStage) return DECLINED; }
161
+ else if (input.action === 'mesh.transfer_token_context') {
162
+ if (!validStage) return DECLINED;
163
+ recipients = [...new Set([claim.coordinatorId, stages[input.partitionIndex].participantId])];
164
+ } else if (input.action === 'mesh.transfer_intermediate_activation') {
165
+ if (!Number.isSafeInteger(input.fromStage) || input.fromStage < 0 || input.toStage !== input.fromStage + 1
166
+ || input.toStage >= stages.length) return DECLINED;
167
+ recipients = claim.disclosure.activationPaths[input.fromStage];
168
+ } else if (input.action === 'mesh.transfer_partition_output') {
169
+ if (input.partitionIndex !== stages.length - 1) return DECLINED;
170
+ recipients = claim.disclosure.outputRecipients;
171
+ } else return DECLINED;
172
+ if (recipients && canonicalPartitionJson(input.recipients) !== canonicalPartitionJson(recipients)) return DECLINED;
173
+ }
174
+ if (!await signatureValid(record, claim.coordinatorId, bytes(claim))) return DECLINED;
175
+ return current(claim, settlement);
176
+ } catch { return DECLINED; }
177
+ }
178
+ /** `{ ok, reason }`: reason is 'expired', 'revoked' or 'declined' (any other mismatch) when refused. */
179
+ async function check(grant, request, { settlement = false } = {}) {
180
+ if (grant?.claim?.schema === ORDERED_SCHEMA) return checkOrdered(grant, request, settlement);
181
+ if (grant?.claim?.schema === CANONICAL_SCHEMA) return checkCanonical(grant, request, settlement);
182
+ try {
183
+ if (closed) return DECLINED;
184
+ validatePartitionIdentity(request.identity);
185
+ const record = structuredClone(grant), claim = record.claim;
186
+ if (claim?.schema !== SCHEMA || claim.meshId !== meshId || !samePartitionIdentity(claim.identity, request.identity)
187
+ || !['partition-activations', 'partition-activations-and-tokens'].includes(claim.disclosure)
188
+ || !/^sha256:[a-f0-9]{64}$/.test(claim.generationDigest)
189
+ || !Number.isSafeInteger(claim.issuedAt) || !Number.isSafeInteger(claim.expiresAt)
190
+ || claim.issuedAt > now() + maxClockSkewMs || claim.expiresAt <= claim.issuedAt || claim.expiresAt - claim.issuedAt > maxTtlMs
191
+ || typeof claim.id !== 'string' || claim.id.length > 128
192
+ || bounds.some(key => !Number.isSafeInteger(claim.limits?.[key]) || claim.limits[key] <= 0)) return DECLINED;
193
+ if (!settlement && (!partitionActions.includes(request.action) || !Number.isSafeInteger(request.step)
194
+ || request.generationDigest !== claim.generationDigest
195
+ || request.action === 'mesh.transfer_token_context' && claim.disclosure !== 'partition-activations-and-tokens'
196
+ || request.step < 0 || request.step >= claim.limits.maxTokens
197
+ || !Number.isSafeInteger(request.inputTokenCount) || request.inputTokenCount < 1
198
+ || request.inputTokenCount > (request.step === 0 ? claim.limits.maxPromptTokens : 1)
199
+ || request.activationBytes != null && (!Number.isSafeInteger(request.activationBytes)
200
+ || request.activationBytes < 0 || request.activationBytes > claim.limits.maxActivationBytes))) return DECLINED;
201
+ if (!await signatureValid(record, request.identity.participantA, bytes(claim))) return DECLINED;
202
+ return current(claim, settlement);
203
+ } catch { return DECLINED; }
204
+ }
205
+ const verify = async (grant, request, options) => (await check(grant, request, options)).ok;
206
+ return Object.freeze({
207
+ participantId: signer.peerId, meshId, verify, check,
208
+ /** The declared clock allowance between participants that check this authority's grants. */
209
+ maxClockSkewMs,
210
+ canonicalScope,
211
+ async issueOrdered(identity, limits, { approved, ttlMs, disclosure, generationDigest, expiresAt }) {
212
+ assert(!closed && approved === true, 'Explicit ordered partition disclosure approval required');
213
+ assert(Number.isSafeInteger(ttlMs) && ttlMs > 0 && ttlMs <= maxTtlMs, 'Invalid ordered grant lifetime');
214
+ const binding = canonicalRouteAttemptBinding(identity), issuedAt = now();
215
+ assert(expiresAt === undefined || Number.isSafeInteger(expiresAt) && expiresAt > issuedAt, 'Ordered disclosure deadline expired or invalid');
216
+ const claim = structuredClone({ schema: ORDERED_SCHEMA, id: crypto.randomUUID(), meshId, identity: binding,
217
+ coordinatorId: binding.route.stages[0].participantId, disclosure, generationDigest, issuedAt,
218
+ expiresAt: Math.min(issuedAt + ttlMs, expiresAt ?? Infinity), limits: Object.fromEntries(bounds.map(key => [key, limits[key]])) });
219
+ orderedClaim(claim);
220
+ assert(claim.coordinatorId === signer.peerId && await createPeerIdFromPublicJwk(signer.publicJwk) === signer.peerId,
221
+ 'Only the authenticated stage-zero coordinator grants ordered execution');
222
+ assert(!closed && issued.size < maxGrants, 'Partition grant budget exhausted');
223
+ const entry = { revoked: false }; issued.set(claim.id, entry);
224
+ try {
225
+ const key = await importSigningKey(signer);
226
+ const signature = await crypto.subtle.sign(getIdentitySignAlgorithm(signer), key, bytes(claim));
227
+ assert(!closed && !entry.revoked, 'Partition authority closed');
228
+ return { claim, publicJwk: structuredClone(signer.publicJwk), signature: toBase64Url(signature) };
229
+ } catch (error) { entry.revoked = true; throw error; }
230
+ },
231
+ async issueCanonical(binding, limits, { approved, ttlMs, disclosure, generation, canonicalAttempt, vocabSize }) {
232
+ canonicalCapability();
233
+ assert(!closed && approved === true, 'Explicit canonical partition disclosure approval required');
234
+ assert(Number.isSafeInteger(ttlMs) && ttlMs > 0 && ttlMs <= maxTtlMs, 'Invalid canonical grant lifetime');
235
+ const issuedAt = now();
236
+ const claim = structuredClone({ schema: CANONICAL_SCHEMA, id: crypto.randomUUID(), meshId, identity: binding,
237
+ canonicalAttempt, generation, vocabSize, disclosure, issuedAt, expiresAt: issuedAt + ttlMs,
238
+ limits: Object.fromEntries(bounds.map(key => [key, limits[key]])) });
239
+ canonicalClaim(claim);
240
+ assert(claim.identity.participantA === signer.peerId && await createPeerIdFromPublicJwk(signer.publicJwk) === signer.peerId,
241
+ 'Only the authenticated input owner grants canonical execution');
242
+ assert(!closed && issued.size < maxGrants, 'Partition grant budget exhausted');
243
+ const entry = { revoked: false }; issued.set(claim.id, entry);
244
+ try {
245
+ const key = await importSigningKey(signer);
246
+ const signature = await crypto.subtle.sign(getIdentitySignAlgorithm(signer), key, canonicalBytes(claim));
247
+ assert(!closed && !entry.revoked, 'Partition authority closed');
248
+ return { claim, publicJwk: structuredClone(signer.publicJwk), signature: toBase64Url(signature) };
249
+ } catch (error) { entry.revoked = true; throw error; }
250
+ },
251
+ async issue(binding, limits, { approved, ttlMs, disclosure, generationDigest }) {
252
+ binding = structuredClone(binding); limits = structuredClone(limits);
253
+ assert(!closed && approved === true, 'Explicit partition disclosure approval required');
254
+ validatePartitionIdentity(binding);
255
+ assert(binding.participantA === signer.peerId && await createPeerIdFromPublicJwk(signer.publicJwk) === signer.peerId,
256
+ 'Only the authenticated input owner can grant partition execution');
257
+ assert(Number.isSafeInteger(ttlMs) && ttlMs > 0 && ttlMs <= maxTtlMs, 'Invalid partition grant lifetime');
258
+ assert(['partition-activations', 'partition-activations-and-tokens'].includes(disclosure)
259
+ && /^sha256:[a-f0-9]{64}$/.test(generationDigest), 'Explicit partition disclosure and generation identity required');
260
+ assert(bounds.every(key => Number.isSafeInteger(limits?.[key]) && limits[key] > 0), 'Explicit partition grant allocations required');
261
+ assert(issued.size < maxGrants, 'Partition grant budget exhausted');
262
+ const issuedAt = now();
263
+ const claim = { schema: SCHEMA, id: crypto.randomUUID(), meshId, identity: binding,
264
+ disclosure, generationDigest, issuedAt, expiresAt: issuedAt + ttlMs,
265
+ limits: Object.fromEntries(bounds.map(key => [key, limits[key]])) };
266
+ const entry = { revoked: false }; issued.set(claim.id, entry);
267
+ try {
268
+ const key = await importSigningKey(signer);
269
+ const signature = await crypto.subtle.sign(getIdentitySignAlgorithm(signer), key, bytes(claim));
270
+ assert(!closed && !entry.revoked, 'Partition authority closed');
271
+ return { claim, publicJwk: structuredClone(signer.publicJwk), signature: toBase64Url(signature) };
272
+ } catch (error) { entry.revoked = true; throw error; }
273
+ },
274
+ revoke(grant) { const entry = issued.get(grant?.claim?.id); if (entry) entry.revoked = true; },
275
+ close() { closed = true; verified.clear(); for (const entry of issued.values()) entry.revoked = true; }
276
+ });
277
+ }
@@ -0,0 +1,103 @@
1
+ import { createP2PTransport } from '../../transport/assignment.js';
2
+ import { createMeshPeerIdentity } from '../peer-identity.js';
3
+ import { assertPartition as assert } from './partition-contract.js';
4
+
5
+ /** Manual signaling adapter with certificate-bound identities. Reuses Poolday's
6
+ * transport and identity challenge; a signaling code grants no compute permission.
7
+ */
8
+ export function createPartitionLink({ identity, roomId, kind, initiator, config, rtcConfig,
9
+ timeoutMs, createEndpoint, onChange = () => {} }) {
10
+ assert(['entry', 'stage'].includes(kind) && Number.isSafeInteger(timeoutMs) && timeoutMs > 0,
11
+ 'Explicit partition link kind and timeout required');
12
+ let remoteId = null, receiver, endpoint = null, channel = null, disposed = false, codeSignals = [], connecting = null;
13
+ const handlers = new Map(), lifetime = new AbortController();
14
+ const signal = (type, payload) => { codeSignals.push({ type, payload }); };
15
+ const fingerprint = description => description?.sdp?.split(/\r?\n/)
16
+ .find(line => line.startsWith('a=fingerprint:sha-256 '))?.slice(14);
17
+ const transport = createP2PTransport({ config, initiator, rtcConfig,
18
+ signaling: { subscribe: fn => { receiver = fn; return () => { receiver = null; }; },
19
+ sendOffer: payload => signal('offer', payload), sendAnswer: payload => signal('answer', payload),
20
+ sendIceCandidate: payload => signal('ice-candidate', payload), sendClose() {} },
21
+ onMessage: message => handlers.get(message.type)?.(remoteId, message.payload),
22
+ onAuxiliaryChannel: accepted => {
23
+ if (initiator || channel || accepted.label !== 'reploid-partition-' + kind) return false;
24
+ channel = accepted; attach(channel).catch(failed); return true;
25
+ },
26
+ onStateChange: state => {
27
+ if (state === 'connected' && initiator) {
28
+ channel = transport.getPeerConnection().createDataChannel('reploid-partition-' + kind, { ordered: true });
29
+ attach(channel).catch(failed);
30
+ }
31
+ if (['failed', 'closed'].includes(state)) {
32
+ lifetime.abort(new Error('Partition link lost'));
33
+ Promise.resolve(endpoint?.close()).catch(failed);
34
+ }
35
+ onChange();
36
+ },
37
+ });
38
+ const peerIdentity = createMeshPeerIdentity({ transport: {
39
+ _getPeerId: () => identity.peerId,
40
+ getPeerBinding: () => {
41
+ const pc = transport.getPeerConnection();
42
+ return transport.getState() === 'connected' ? { local: fingerprint(pc.localDescription), remote: fingerprint(pc.remoteDescription) } : null;
43
+ },
44
+ onMessage: (type, handler) => handlers.set(type, handler),
45
+ sendToPeer: (_peer, type, payload) => { transport.send({ type, payload }); return true; },
46
+ }, getIdentity: () => identity, roomId, timeoutMs, maxPending: 2 });
47
+ let error = null;
48
+ function failed(cause) { error = String(cause.message); lifetime.abort(cause); channel?.close(); onChange(); }
49
+ async function attach(accepted) {
50
+ const deadline = AbortSignal.any([lifetime.signal, AbortSignal.timeout(timeoutMs)]);
51
+ const wait = (event, predicate) => new Promise((resolve, reject) => {
52
+ const done = (error) => { accepted.removeEventListener(event, received); deadline.removeEventListener('abort', aborted); error ? reject(error) : resolve(); };
53
+ const received = input => { if (!predicate || predicate(input)) done(); };
54
+ const aborted = () => done(deadline.reason);
55
+ accepted.addEventListener(event, received); deadline.addEventListener('abort', aborted, { once: true });
56
+ if (deadline.aborted) aborted(); else if (event === 'open' && accepted.readyState === 'open') done();
57
+ });
58
+ const ready = initiator ? wait('message', e => e.data === 'partition-entry-ready') : wait('open');
59
+ ready.catch(() => {});
60
+ await wait('open');
61
+ const verified = await peerIdentity.verify(remoteId, deadline);
62
+ assert(verified === remoteId, 'Partition link signing identity differs from signaling code');
63
+ if (initiator) await ready;
64
+ deadline.throwIfAborted();
65
+ endpoint = await createEndpoint({ channel: accepted, localParticipantId: identity.peerId, remoteParticipantId: verified, kind, initiator });
66
+ if (!initiator) accepted.send('partition-entry-ready');
67
+ onChange();
68
+ }
69
+ return Object.freeze({
70
+ get endpoint() { return endpoint; },
71
+ get remoteId() { return remoteId; },
72
+ getState: () => ({ state: transport.getState(), kind, remoteId, ready: !!endpoint && !lifetime.signal.aborted, error }),
73
+ async connect() {
74
+ if (connecting) return connecting;
75
+ connecting = transport.connect(); connecting.catch(failed);
76
+ assert(transport.getPeerConnection(), 'Transport did not create its connection');
77
+ },
78
+ async exportCode() {
79
+ const deadline = performance.now() + timeoutMs;
80
+ while (!codeSignals.some(s => s.type === (initiator ? 'offer' : 'answer'))
81
+ || transport.getPeerConnection().iceGatheringState !== 'complete') {
82
+ lifetime.signal.throwIfAborted();
83
+ if (performance.now() >= deadline) throw new Error('Partition signaling timed out');
84
+ await new Promise(resolve => setTimeout(resolve, 50));
85
+ }
86
+ return btoa(JSON.stringify({ schema: 'reploid.partition-link/v1', peerId: identity.peerId, roomId, kind, signals: codeSignals }));
87
+ },
88
+ acceptCode(code) {
89
+ assert(typeof code === 'string' && code.length <= 90000, 'Invalid partition connection code');
90
+ const packet = JSON.parse(atob(code.trim()));
91
+ assert(packet.schema === 'reploid.partition-link/v1' && packet.roomId === roomId && packet.kind === kind
92
+ && /^peer:[a-f0-9]{24}$/.test(packet.peerId) && packet.peerId !== identity.peerId
93
+ && (!remoteId || remoteId === packet.peerId) && Array.isArray(packet.signals) && packet.signals.length <= 70,
94
+ 'Partition signaling identity mismatch');
95
+ remoteId = packet.peerId;
96
+ for (const item of packet.signals) { assert(['offer', 'answer', 'ice-candidate'].includes(item.type), 'Invalid signal'); receiver(item); }
97
+ },
98
+ async close() {
99
+ if (disposed) return; disposed = true; lifetime.abort(); peerIdentity.close();
100
+ await endpoint?.close(); await transport.close();
101
+ },
102
+ });
103
+ }
@@ -0,0 +1,109 @@
1
+ import { createRoutePartitionNetwork } from './partition-route-network.js';
2
+ import { assertPartition as assert } from './partition-contract.js';
3
+
4
+ const LABEL = 'reploid-partitions';
5
+ const READY = 'reploid.partition-channel-ready/v1';
6
+
7
+ /** Uses the existing mesh certificate/signature verifier; never trusts an advertised peer ID. */
8
+ export function createPartitionNetwork({ transport, verifyPeer, createEndpoint, maxPeers, timeoutMs, onPeer = () => {}, label = LABEL, ordered = null }) {
9
+ if (ordered !== null) return createRoutePartitionNetwork({ transport, verifyPeer, createEndpoint, maxPeers, timeoutMs, onPeer, label, ordered });
10
+ assert((/^reploid-partitions(?:-[a-z-]+)?$/.test(label) || label === 'reploid-training') && label.length <= 80, 'Invalid partition channel label');
11
+ assert(typeof transport?.onDataChannel === 'function' && typeof transport.getPeerBinding === 'function'
12
+ && typeof verifyPeer === 'function' && typeof createEndpoint === 'function'
13
+ && Number.isSafeInteger(maxPeers) && maxPeers > 0 && Number.isSafeInteger(timeoutMs) && timeoutMs > 0
14
+ && timeoutMs <= 2147483647, 'Partition network ports and budgets required');
15
+ const entries = new Map(), retiring = new Set();
16
+ let closed = false, closing = null, settlementFailure = null;
17
+ const settleEntry = entry => {
18
+ if (!entry.settlement) {
19
+ entry.settlement = Promise.resolve().then(() => entry.endpoint?.close());
20
+ retiring.add(entry.settlement);
21
+ entry.settlement.then(() => retiring.delete(entry.settlement), error => {
22
+ retiring.delete(entry.settlement); settlementFailure ||= error;
23
+ });
24
+ }
25
+ return entry.settlement;
26
+ };
27
+ const wait = (entry, handshake) => new Promise((resolve, reject) => {
28
+ const finish = error => {
29
+ clearTimeout(timer); entry.channel.removeEventListener('open', opened);
30
+ entry.channel.removeEventListener('message', message); entry.channel.removeEventListener('close', lost);
31
+ entry.controller.signal.removeEventListener('abort', lost);
32
+ error ? reject(error) : resolve();
33
+ };
34
+ const opened = () => { if (!handshake) finish(); };
35
+ const message = event => finish(event.data === READY ? null : new Error('Invalid partition channel handshake'));
36
+ const lost = () => finish(new Error('Partition channel closed during authentication'));
37
+ const timer = setTimeout(() => finish(new Error('Partition channel authentication timed out')), timeoutMs);
38
+ entry.channel.addEventListener('open', opened);
39
+ if (handshake) entry.channel.addEventListener('message', message, { once: true });
40
+ entry.channel.addEventListener('close', lost, { once: true });
41
+ entry.controller.signal.addEventListener('abort', lost, { once: true });
42
+ if (entry.controller.signal.aborted || entry.channel.readyState === 'closed') lost();
43
+ else if (!handshake && entry.channel.readyState === 'open') finish();
44
+ });
45
+ const attach = (peerId, channel, incoming) => {
46
+ assert(!closed && !entries.has(peerId) && entries.size < maxPeers, 'Partition peer limit or duplicate connection');
47
+ const entry = { channel, controller: new AbortController(), endpoint: null, promise: null };
48
+ entries.set(peerId, entry);
49
+ channel.addEventListener('close', () => {
50
+ entry.controller.abort();
51
+ if (entries.get(peerId) === entry) entries.delete(peerId);
52
+ settleEntry(entry);
53
+ }, { once: true });
54
+ // Install the handshake listener before any asynchronous certificate verification.
55
+ const ready = wait(entry, !incoming);
56
+ ready.catch(() => {});
57
+ entry.promise = (async () => {
58
+ const timer = setTimeout(() => entry.controller.abort(), timeoutMs);
59
+ try {
60
+ const before = transport.getPeerBinding(peerId);
61
+ assert(before, 'Authenticated WebRTC certificate binding required');
62
+ const verifiedId = await verifyPeer(peerId, entry.controller.signal);
63
+ entry.controller.signal.throwIfAborted();
64
+ const after = transport.getPeerBinding(peerId);
65
+ assert(/^peer:[a-f0-9]{24}$/.test(verifiedId) && before.local === after?.local && before.remote === after?.remote,
66
+ 'Partition peer identity or certificate changed');
67
+ await ready;
68
+ entry.controller.signal.throwIfAborted();
69
+ entry.endpoint = createEndpoint({ channel, remoteParticipantId: verifiedId });
70
+ if (incoming) channel.send(READY);
71
+ onPeer(peerId, entry.endpoint);
72
+ return entry.endpoint;
73
+ } catch (error) {
74
+ entry.controller.abort(); channel.close();
75
+ if (entries.get(peerId) === entry) entries.delete(peerId);
76
+ await settleEntry(entry).catch(() => {});
77
+ throw error;
78
+ } finally { clearTimeout(timer); }
79
+ })();
80
+ return entry.promise;
81
+ };
82
+ const unsubscribe = transport.onDataChannel(label, (peerId, channel) => {
83
+ try { attach(peerId, channel, true).catch(() => {}); } catch { channel.close(); }
84
+ });
85
+ return Object.freeze({
86
+ connect(peerId) {
87
+ assert(!closed, 'Partition network closed');
88
+ if (entries.has(peerId)) return entries.get(peerId).promise;
89
+ assert(entries.size < maxPeers, 'Partition peer limit');
90
+ const channel = transport.openDataChannel(peerId, label);
91
+ try { return attach(peerId, channel, false); } catch (error) { channel.close(); throw error; }
92
+ },
93
+ close() {
94
+ if (closing) return closing;
95
+ closed = true; unsubscribe();
96
+ const pending = [...entries.values()];
97
+ closing = Promise.resolve().then(async () => {
98
+ for (const entry of pending) { entry.controller.abort(); entry.channel.close(); }
99
+ await Promise.allSettled(pending.map(entry => entry.promise));
100
+ const results = await Promise.allSettled([...retiring, ...pending.map(settleEntry)]);
101
+ entries.clear();
102
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason);
103
+ if (settlementFailure && !failures.includes(settlementFailure)) failures.push(settlementFailure);
104
+ if (failures.length) throw new AggregateError(failures, 'Partition network settlement failed');
105
+ });
106
+ return closing;
107
+ }
108
+ });
109
+ }