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,896 @@
1
+ import { publicPreparationProgress } from './preparation-progress.js';
2
+ import { publicProviderHardware } from './provider-hardware.js';
3
+ import { identifyComputer } from './computer-identity.js';
4
+ import computerPolicy from './computer-identity.policy.json' with { type: 'json' };
5
+ import { createRemoteGenerationRequests } from './remote-generation-requests.js';
6
+ import { createMeshPeerIdentity } from './peer-identity.js';
7
+ import { requireResolvedConfig, snapshotJson } from '../config/index.js';
8
+ import { createPeerAdvertisement, createSwarmController } from './swarm-coordination.js';
9
+ import { rankProviderPeers, applyReceiptToContribution } from './contribution.js';
10
+ import { createReceiptDraft, countersignReceipt, signReceiptDraft, verifyReceipt } from '../artifacts/receipt.js';
11
+ import { measureWholeModelReceipt, matchesWholeModelReceipt } from '../artifacts/whole-model-receipt.js';
12
+ import { hashJson } from '../artifacts/signatures.js';
13
+ import { signGenerationTerminal, verifyGenerationTerminal } from '../artifacts/generation-terminal.js';
14
+ import { createGenerationRecovery, createMemoryGenerationJournal } from './generation-recovery.js';
15
+
16
+ const adapterSet = value => {
17
+ if (!Array.isArray(value) || value.length > 1 || value.some(id => !/^sha256:[a-f0-9]{64}$/.test(id))) {
18
+ throw new Error('Invalid adapter selection');
19
+ }
20
+ return [...value];
21
+ };
22
+ const supportsAdapters = (peer, selected) => selected.every(id => (peer.adapterIdentities || []).includes(id));
23
+ const modelBinding = (model, modelIdentity = null) => Object.freeze({ model, modelIdentity });
24
+ const matchesRequestedModel = (actual, requested) => (!requested.model || actual.model === requested.model)
25
+ && (!requested.modelIdentity || actual.modelIdentity === requested.modelIdentity);
26
+ const sameModelBinding = (actual, expected) => actual.model === expected.model
27
+ && actual.modelIdentity === expected.modelIdentity;
28
+
29
+ // Whole-request generation by authorized peers over the swarm protocol: advertisement,
30
+ // provider choice, streamed requests, receipts and recovery. Receipts are signed records,
31
+ // not independent evaluation, durable job replay, or hardware attestation.
32
+ export function createPeerGenerationMesh({ config, ports }) {
33
+ const policy = requireResolvedConfig(config);
34
+ for (const name of ['authorize', 'createTransport', 'generate']) {
35
+ if (typeof ports?.[name] !== 'function') throw new TypeError(`Mesh port ${name} is required`);
36
+ }
37
+ if (!ports.identity || !ports.events || !ports.eventBus || !ports.utils) throw new TypeError('Mesh identity, event and utility ports are required');
38
+ const { instanceId, utils, eventBus } = ports;
39
+ const modelConfig = snapshotJson(ports.modelConfig ?? null);
40
+ const bridgeEvents = ports.events;
41
+ const swarmEnabled = policy.mesh.enabled;
42
+ const REMOTE_GENERATION_TIMEOUT_MS = policy.mesh.generationTimeoutMs;
43
+ const swarmController = createSwarmController();
44
+ const receiptHistory = [], observedPeers = new Map();
45
+ const contextProviders = new Map();
46
+ const identificationAttempts = new Map(), identifying = new Set();
47
+ let observationSequence = 0;
48
+ const inbound = new Map(), inboundQueued = new Map(), completed = new Map();
49
+ const consumerOrder = [], consumerBuckets = new Map();
50
+ const inboundSettlements = new Set();
51
+ const receiptSettlements = new Set();
52
+ const executions = new Set();
53
+ const lifetime = new AbortController();
54
+ const subscriptions = [];
55
+ const providerWaiters = new Set();
56
+ const reservedProviders = new Set();
57
+ let closing = null;
58
+ let closed = false, identityBundle = null, swarmTransport = ports.transport || null;
59
+ let peerIdentity = null;
60
+ let recovery = null;
61
+ const generationJournal = ports.generationJournal || createMemoryGenerationJournal(policy.mesh.maxRetainedJobs);
62
+ let pendingFreshIdentity = ports.forceFreshIdentity === true;
63
+ const remoteRequests = createRemoteGenerationRequests({ timeoutMs: REMOTE_GENERATION_TIMEOUT_MS,
64
+ maxPending: policy.mesh.maxPendingJobs,
65
+ sendCancel: (peerId, requestId) => swarmTransport?.sendToPeer(peerId, 'reploid:generation-cancel', { requestId }) });
66
+ let swarmInitPromise = null, swarmInitialized = false, swarmHandlersRegistered = false;
67
+ const executionState = () => ports.getExecutionState?.() || { phase: modelConfig ? 'ready' : 'idle', modelIdentity: null };
68
+ const ready = () => !closed && !!modelConfig && executionState().phase === 'ready';
69
+ const currentModelBinding = () => modelBinding(modelConfig?.id || null, executionState().modelIdentity);
70
+ const bindingAvailable = (binding, adapters) => sameModelBinding(currentModelBinding(), binding)
71
+ && supportsAdapters(executionState(), adapters);
72
+ const executeBound = async (messages, onUpdate, controls, binding) => {
73
+ const adapters = adapterSet(controls.adapterIdentities);
74
+ const assertBinding = () => {
75
+ if (!bindingAvailable(binding, adapters)) {
76
+ throw new Error('Provider execution binding changed');
77
+ }
78
+ };
79
+ assertBinding();
80
+ const response = snapshotJson(await ports.generate(messages, chunk => {
81
+ assertBinding();
82
+ return onUpdate?.(chunk);
83
+ }, { ...controls, adapterIdentities: [...adapters] }));
84
+ assertBinding();
85
+ if (!response || !matchesRequestedModel(response, binding)) {
86
+ throw new Error('Provider execution identity mismatch');
87
+ }
88
+ if (JSON.stringify(response.adapterIdentities || []) !== JSON.stringify(adapters)) {
89
+ throw new Error('Provider adapter execution identity mismatch');
90
+ }
91
+ return response;
92
+ };
93
+ const generateBound = (messages, onUpdate, controls, binding) => {
94
+ const signal = AbortSignal.any([lifetime.signal, ...(controls.signal ? [controls.signal] : [])]);
95
+ signal.throwIfAborted();
96
+ const pending = executeBound(messages, chunk => {
97
+ if (!signal.aborted) return onUpdate?.(chunk);
98
+ }, { ...controls, signal }, binding).then(response => {
99
+ signal.throwIfAborted();
100
+ return response;
101
+ });
102
+ executions.add(pending);
103
+ const settled = () => executions.delete(pending);
104
+ pending.then(settled, settled);
105
+ return pending;
106
+ };
107
+ const chooseCompatibleProvider = (modelId, includeBusy = false, modelIdentity = null, adapters = [], preferred = null) => {
108
+ const eligible = rankProviderPeers(swarmController.listPeers())
109
+ .filter(peer => supportsAdapters(peer, adapters) && peer.readiness === 'ready' && (!modelIdentity || peer.modelIdentity === modelIdentity) && (!modelId || peer.model === modelId) && (includeBusy || (!reservedProviders.has(peer.peerId) && peer.availableSlots !== 0))
110
+ && (policy.models.contract === null
111
+ || JSON.stringify(snapshotJson(peer.modelContract ?? null)) === JSON.stringify(policy.models.contract)));
112
+ return eligible.find(peer => peer.peerId === preferred) || eligible[0] || null;
113
+ };
114
+
115
+ const getTransportState = () => ({
116
+ connectionState: swarmTransport?.getConnectionState?.() || 'disconnected',
117
+ transport: swarmTransport?.getTransportType?.() || null,
118
+ connections: swarmTransport?.getPeerStates?.() || []
119
+ });
120
+
121
+ const getSwarmSnapshot = () => ({
122
+ instanceId,
123
+ ...swarmController.getState({
124
+ swarmEnabled,
125
+ hasInference: ready()
126
+ }),
127
+ ...getTransportState(),
128
+ peerId: identityBundle?.peerId || null,
129
+ peers: snapshotJson(swarmController.listPeers().map(peer => {
130
+ const participantId = peerIdentity?.getVerified(peer.peerId) || null;
131
+ // Never promote identity fields supplied by an advertisement into verified labels.
132
+ return { ...(observedPeers.get(peer.peerId) || peer), participantId, identityVerified: !!participantId };
133
+ }))
134
+ });
135
+
136
+ const emitSwarmState = () => {
137
+ const snapshot = getSwarmSnapshot();
138
+ bridgeEvents.emit('swarm-state', snapshot);
139
+ if (snapshot.providerCount > 0) {
140
+ bridgeEvents.emit('provider-ready', snapshot);
141
+ }
142
+ return snapshot;
143
+ };
144
+
145
+ const syncIdentityDocument = async () => ports.identity.sync(identityBundle, { instanceId, swarmEnabled, hasInference: ready() });
146
+
147
+ const advertiseSelf = () => {
148
+ if (closed || !swarmEnabled || !swarmTransport || !identityBundle) return null;
149
+ const advertisement = createPeerAdvertisement({
150
+ peerId: identityBundle.peerId,
151
+ swarmEnabled,
152
+ hasInference: ready(),
153
+ capabilities: ['generation'],
154
+ contribution: identityBundle.contribution,
155
+ updatedAt: Date.now()
156
+ });
157
+ advertisement.model = modelConfig?.id || null;
158
+ advertisement.observationSession = instanceId;
159
+ advertisement.observationSequence = ++observationSequence;
160
+ advertisement.readiness = executionState().phase;
161
+ const activity = executionState().activity;
162
+ advertisement.activity = activity ? { phase: activity.phase,
163
+ completed: Number.isSafeInteger(activity.completed) ? activity.completed : 0,
164
+ failure: activity.error ? { phase: activity.phase, message: 'Model preparation or execution failed on this computer.' } : null } : null;
165
+ advertisement.preparation = advertisement.readiness === 'loading' ? publicPreparationProgress(executionState().progress) : null;
166
+ advertisement.modelIdentity = executionState().modelIdentity;
167
+ advertisement.hardware = publicProviderHardware(executionState().hardware);
168
+ advertisement.adapterIdentities = executionState().adapterIdentities || [];
169
+ advertisement.availableSlots = ready() ? Math.max(0, policy.mesh.maxInboundJobs - inbound.size) : 0;
170
+ advertisement.queuedJobs = inboundQueued.size;
171
+ advertisement.modelContract = policy.models.contract;
172
+ advertisement.identityProtocol = 1;
173
+ swarmTransport.broadcast('reploid:peer-advertisement', advertisement);
174
+ return advertisement;
175
+ };
176
+
177
+ const updateProviderContribution = async (receipt) => {
178
+ if (!identityBundle) return;
179
+ const priorHistory = [...receiptHistory];
180
+ receiptHistory.push(receipt);
181
+ identityBundle.contribution = applyReceiptToContribution(
182
+ identityBundle.contribution,
183
+ receipt,
184
+ priorHistory
185
+ );
186
+ await ports.identity.save(identityBundle);
187
+ await syncIdentityDocument();
188
+ advertiseSelf();
189
+ emitSwarmState();
190
+ };
191
+
192
+ const updateConsumerReceiptCount = async () => {
193
+ if (!identityBundle) return;
194
+ const summary = identityBundle.contribution || {};
195
+ identityBundle.contribution = {
196
+ ...summary,
197
+ receiptsConsumed: Math.max(0, Number(summary.receiptsConsumed || 0)) + 1,
198
+ updatedAt: Date.now()
199
+ };
200
+ await ports.identity.save(identityBundle);
201
+ await syncIdentityDocument();
202
+ };
203
+
204
+ const waitForProvider = (modelId, signal, timeoutMs = REMOTE_GENERATION_TIMEOUT_MS, modelIdentity = null, adapters = []) => {
205
+ if (chooseCompatibleProvider(modelId, false, modelIdentity, adapters)) return Promise.resolve(true);
206
+ return new Promise(resolve => {
207
+ let unsubscribe = () => {};
208
+ const finish = available => {
209
+ clearTimeout(timer); unsubscribe(); signal?.removeEventListener('abort', abort);
210
+ providerWaiters.delete(finish); resolve(available);
211
+ };
212
+ const abort = () => finish(false);
213
+ const timer = setTimeout(() => finish(false), timeoutMs);
214
+ providerWaiters.add(finish);
215
+ unsubscribe = bridgeEvents.on('provider-ready', () => { if (chooseCompatibleProvider(modelId, false, modelIdentity, adapters)) finish(true); });
216
+ signal?.addEventListener('abort', abort, { once: true });
217
+ if (signal?.aborted) finish(false);
218
+ });
219
+ };
220
+
221
+ const chooseProvider = async (modelId, signal, modelIdentity, adapters, preferred = null) => {
222
+ const deadline = Date.now() + REMOTE_GENERATION_TIMEOUT_MS;
223
+ while (!closed && Date.now() < deadline) {
224
+ signal?.throwIfAborted();
225
+ const provider = chooseCompatibleProvider(modelId, false, modelIdentity, adapters, preferred);
226
+ if (provider) { reservedProviders.add(provider.peerId); return snapshotJson(provider); }
227
+ if (!await waitForProvider(modelId, signal, deadline - Date.now(), modelIdentity, adapters)) break;
228
+ }
229
+ signal?.throwIfAborted();
230
+ return null;
231
+ };
232
+
233
+ const handlePeerAdvertisement = (remotePeerId, payload = {}) => {
234
+ try { payload = snapshotJson(payload); } catch { return; }
235
+ if (!payload || Array.isArray(payload) || typeof payload !== 'object') return;
236
+ if (!Number.isFinite(payload.updatedAt)) return;
237
+ delete payload.participantId;
238
+ delete payload.identityVerified;
239
+ try { adapterSet(payload.adapterIdentities || []); } catch { return; }
240
+ const next = swarmController.upsertPeer({
241
+ ...payload,
242
+ peerId: remotePeerId
243
+ });
244
+ if (next) {
245
+ next.activity = payload.activity && ['idle', 'loading', 'ready', 'executing', 'stopping', 'failed'].includes(payload.activity.phase)
246
+ ? { phase: payload.activity.phase, completed: Number.isSafeInteger(payload.activity.completed) ? payload.activity.completed : 0,
247
+ failure: payload.activity.failure ? { message: 'Model preparation or execution failed on this computer.' } : null } : null;
248
+ next.model = typeof payload.model === 'string' ? payload.model : null;
249
+ next.readiness = ['loading', 'ready', 'executing', 'idle', 'failed', 'stopping'].includes(payload.readiness) ? payload.readiness : 'unavailable';
250
+ next.preparation = next.readiness === 'loading' ? publicPreparationProgress(payload.preparation) : null;
251
+ next.modelIdentity = payload.modelIdentity || null;
252
+ next.hardware = publicProviderHardware(payload.hardware);
253
+ next.adapterIdentities = payload.adapterIdentities || [];
254
+ next.availableSlots = Number.isSafeInteger(payload.availableSlots) && payload.availableSlots > 0 ? payload.availableSlots : 0;
255
+ next.queuedJobs = Number.isSafeInteger(payload.queuedJobs) && payload.queuedJobs > 0 ? payload.queuedJobs : 0;
256
+ next.modelContract = payload.modelContract || null;
257
+ next.identityProtocol = payload.identityProtocol === 1 ? 1 : null;
258
+ }
259
+ if (next) {
260
+ const connection = swarmTransport?.getPeerStates?.().find(peer => peer.id === remotePeerId);
261
+ const previous = observedPeers.get(remotePeerId);
262
+ const sameAttempt = (previous?.attemptId ?? null) === (connection?.attemptId ?? null);
263
+ const ordered = !sameAttempt || !previous || (payload.observationSession === previous.observationSession
264
+ && Number.isSafeInteger(payload.observationSequence) && Number.isSafeInteger(previous.observationSequence)
265
+ ? payload.observationSequence > previous.observationSequence : payload.updatedAt >= previous.updatedAt);
266
+ if (ordered && (!connection || !['disconnected', 'failed'].includes(connection.phase))) {
267
+ observedPeers.set(remotePeerId, { ...snapshotJson(next), attemptId: connection?.attemptId || null,
268
+ observationSession: payload.observationSession ?? null, observationSequence: payload.observationSequence ?? null });
269
+ }
270
+ }
271
+ if (next?.role === 'provider') {
272
+ bridgeEvents.emit('provider-ready', next);
273
+ }
274
+ emitSwarmState();
275
+ const channel = swarmTransport?.getPeerBinding?.(remotePeerId);
276
+ const attemptId = swarmTransport?.getPeerStates?.().find(peer => peer.id === remotePeerId)?.attemptId ?? null;
277
+ const identificationKey = JSON.stringify([attemptId, channel]);
278
+ if (!closed && next?.identityProtocol === 1 && channel && peerIdentity
279
+ && !peerIdentity.getVerified(remotePeerId) && identifying.size < computerPolicy.maxBackgroundProofs
280
+ && identificationAttempts.get(remotePeerId) !== identificationKey) {
281
+ identificationAttempts.set(remotePeerId, identificationKey);
282
+ const pending = peerIdentity.observe(remotePeerId, lifetime.signal).catch(() => null);
283
+ identifying.add(pending);
284
+ pending.finally(() => identifying.delete(pending));
285
+ }
286
+ };
287
+
288
+ const handleGenerationUpdate = (remotePeerId, payload = {}) => {
289
+ const pending = remoteRequests.get(payload.requestId, remotePeerId);
290
+ if (!pending || pending.processing) return;
291
+ const chunk = String(payload.chunk || '');
292
+ if (!chunk) return;
293
+ try { pending.onUpdate?.(chunk); }
294
+ catch (error) { remoteRequests.settle(pending, { error, cancel: true }); }
295
+ };
296
+
297
+ const handleGenerationResult = async (remotePeerId, payload = {}) => {
298
+ const pending = remoteRequests.get(payload.requestId, remotePeerId);
299
+ if (!pending || pending.processing) return;
300
+ pending.processing = true;
301
+ const current = () => remoteRequests.get(pending.requestId, remotePeerId) === pending;
302
+ try {
303
+ payload = snapshotJson(payload);
304
+ const response = payload.response && typeof payload.response === 'object'
305
+ ? payload.response
306
+ : { content: String(payload.content || ''), raw: String(payload.raw || payload.content || ''),
307
+ model: payload.model || null, provider: payload.provider || null, timestamp: payload.timestamp || Date.now() };
308
+ // Only this verifier may attach execution evidence to the returned result.
309
+ delete response.executionReceipt;
310
+ if (!matchesRequestedModel(response, modelBinding(pending.modelId, pending.modelIdentity))) {
311
+ throw new Error('Remote response substituted the requested model');
312
+ }
313
+ if (JSON.stringify(response.adapterIdentities || []) !== JSON.stringify(pending.adapterIdentities)) {
314
+ throw new Error('Remote response substituted the requested adapter');
315
+ }
316
+ if (payload.receipt) {
317
+ const verified = await verifyReceipt(payload.receipt);
318
+ if (!current()) return;
319
+ if (!verified.valid || payload.receipt.jobHash !== `request:${pending.requestId}`
320
+ || payload.receipt.consumer !== identityBundle?.peerId || payload.receipt.model !== response.model) {
321
+ throw new Error('Remote receipt is invalid or does not bind this request');
322
+ }
323
+ if (Number(payload.receipt.version) === 2 && (!pending.providerIdentity || payload.receipt.provider !== pending.providerIdentity
324
+ || !await matchesWholeModelReceipt(payload.receipt, pending.messages, response))) {
325
+ throw new Error('Measured receipt differs from the admitted request or provider output');
326
+ }
327
+ const countersigned = await countersignReceipt(payload.receipt, identityBundle);
328
+ if (!current()) return;
329
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:receipt', { receipt: countersigned });
330
+ await updateConsumerReceiptCount();
331
+ response.executionReceipt = countersigned;
332
+ } else if (policy.models.contract !== null) {
333
+ throw new Error('Remote execution receipt is missing');
334
+ }
335
+ remoteRequests.settle(pending, { response });
336
+ } catch (error) { remoteRequests.settle(pending, { error }); }
337
+ };
338
+
339
+ const handleGenerationError = async (remotePeerId, payload = {}) => {
340
+ const pending = remoteRequests.get(payload.requestId, remotePeerId);
341
+ if (!pending || pending.processing) return;
342
+ const error = new Error(String(payload.error || 'Remote generation failed'));
343
+ if (payload?.code === 'CONTEXT_LENGTH_EXCEEDED') {
344
+ error.code = 'CONTEXT_LENGTH_EXCEEDED';
345
+ error.status = 400;
346
+ }
347
+ if (payload?.terminal && pending.providerIdentity && identityBundle) {
348
+ try {
349
+ const binding = {
350
+ requestId: pending.requestId,
351
+ provider: pending.providerIdentity,
352
+ consumer: identityBundle.peerId,
353
+ model: pending.modelId,
354
+ modelIdentity: pending.modelIdentity,
355
+ adapterIdentities: pending.adapterIdentities,
356
+ inputDigest: await hashJson(pending.messages),
357
+ };
358
+ if (await verifyGenerationTerminal(payload.terminal, binding)) {
359
+ error.generationTerminal = snapshotJson(payload.terminal);
360
+ }
361
+ } catch {
362
+ // Invalid terminal proof is ignored; normal request error settlement proceeds.
363
+ }
364
+ }
365
+ remoteRequests.settle(pending, { error });
366
+ };
367
+
368
+ const handleReceipt = async (_remotePeerId, payload = {}) => {
369
+ if (!payload?.receipt || !identityBundle) return;
370
+ const verification = await verifyReceipt(payload.receipt);
371
+ if (!verification.valid || !verification.consumerValid) return;
372
+ if (payload.receipt.provider !== identityBundle.peerId) return;
373
+ await ports.onReceipt?.(snapshotJson(payload.receipt));
374
+ await updateProviderContribution(payload.receipt);
375
+ };
376
+
377
+ const removeQueuedBucketEntry = entry => {
378
+ const bucket = consumerBuckets.get(entry.consumer);
379
+ if (!bucket) return;
380
+ const queue = bucket.lanes.get(entry.laneKey);
381
+ if (queue) {
382
+ const index = queue.indexOf(entry);
383
+ if (index >= 0) queue.splice(index, 1);
384
+ if (!queue.length) {
385
+ bucket.lanes.delete(entry.laneKey);
386
+ const laneIndex = bucket.laneOrder.indexOf(entry.laneKey);
387
+ if (laneIndex >= 0) bucket.laneOrder.splice(laneIndex, 1);
388
+ }
389
+ }
390
+ if (!bucket.laneOrder.length) {
391
+ consumerBuckets.delete(entry.consumer);
392
+ const consumerIndex = consumerOrder.indexOf(entry.consumer);
393
+ if (consumerIndex >= 0) consumerOrder.splice(consumerIndex, 1);
394
+ }
395
+ };
396
+
397
+ const cancelQueuedInbound = (key, reason, notifyPeer = true) => {
398
+ const entry = inboundQueued.get(key);
399
+ if (!entry) return false;
400
+ inboundQueued.delete(key);
401
+ clearTimeout(entry.timer);
402
+ removeQueuedBucketEntry(entry);
403
+ entry.controller.abort(reason);
404
+ if (notifyPeer && !closed) {
405
+ swarmTransport?.sendToPeer(entry.remotePeerId, 'reploid:generation-error', {
406
+ requestId: entry.requestId,
407
+ error: reason?.message || 'Queued request cancelled'
408
+ });
409
+ }
410
+ advertiseSelf();
411
+ entry.resolve(false);
412
+ return true;
413
+ };
414
+
415
+ const drainInboundQueue = () => {
416
+ while (!closed && ready() && inbound.size < policy.mesh.maxInboundJobs && consumerOrder.length > 0) {
417
+ const consumer = consumerOrder.shift();
418
+ const bucket = consumerBuckets.get(consumer);
419
+ if (!bucket || !bucket.laneOrder.length) {
420
+ consumerBuckets.delete(consumer);
421
+ continue;
422
+ }
423
+ const laneKey = bucket.laneOrder.shift();
424
+ const queue = bucket.lanes.get(laneKey);
425
+ if (!queue || !queue.length) {
426
+ bucket.lanes.delete(laneKey);
427
+ if (bucket.laneOrder.length) consumerOrder.push(consumer);
428
+ else consumerBuckets.delete(consumer);
429
+ continue;
430
+ }
431
+ const entry = queue.shift();
432
+ if (queue.length) bucket.laneOrder.push(laneKey);
433
+ else bucket.lanes.delete(laneKey);
434
+ if (bucket.laneOrder.length) consumerOrder.push(consumer);
435
+ else consumerBuckets.delete(consumer);
436
+ inboundQueued.delete(entry.key);
437
+ clearTimeout(entry.timer);
438
+ inbound.set(entry.key, entry.controller);
439
+ entry.resolve(true);
440
+ }
441
+ };
442
+
443
+ const enqueueInboundSlot = (key, requestId, remotePeerId, consumer, contextId) => new Promise(resolve => {
444
+ const laneKey = contextId || key;
445
+ const entry = {
446
+ key,
447
+ requestId,
448
+ remotePeerId,
449
+ consumer,
450
+ laneKey,
451
+ controller: new AbortController(),
452
+ resolve,
453
+ timer: setTimeout(() => {
454
+ cancelQueuedInbound(key, new Error('Remote job deadline exceeded'), true);
455
+ }, policy.mesh.generationTimeoutMs)
456
+ };
457
+ let bucket = consumerBuckets.get(consumer);
458
+ if (!bucket) {
459
+ bucket = { laneOrder: [], lanes: new Map() };
460
+ consumerBuckets.set(consumer, bucket);
461
+ consumerOrder.push(consumer);
462
+ }
463
+ let queue = bucket.lanes.get(laneKey);
464
+ if (!queue) {
465
+ queue = [];
466
+ bucket.lanes.set(laneKey, queue);
467
+ bucket.laneOrder.push(laneKey);
468
+ }
469
+ queue.push(entry);
470
+ inboundQueued.set(key, entry);
471
+ advertiseSelf();
472
+ });
473
+
474
+ const handleGenerationRequest = async (remotePeerId, payload = {}) => {
475
+ if (closed || !policy.mesh.executeJobs || !ready() || !swarmEnabled || !swarmTransport || !identityBundle) return;
476
+
477
+ try { payload = snapshotJson(payload); } catch { return; }
478
+ if (!payload || Array.isArray(payload) || typeof payload !== 'object') return;
479
+ const requestId = String(payload.requestId || '').trim();
480
+ const consumer = String(payload.consumer || remotePeerId).trim() || remotePeerId;
481
+ const targetProvider = payload.provider ? String(payload.provider).trim() : null;
482
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
483
+ const contextId = payload.contextId ?? null;
484
+ if (contextId !== null && (typeof contextId !== 'string' || !contextId || contextId.length > 256)) return;
485
+
486
+ if (!requestId || requestId.length > 512 || !messages.length) return;
487
+ let adapters;
488
+ try { adapters = adapterSet(payload.adapterIdentities || []); } catch { return; }
489
+ if (targetProvider && targetProvider !== identityBundle.peerId && targetProvider !== swarmTransport._getPeerId?.()) return;
490
+ const binding = currentModelBinding();
491
+ if (!matchesRequestedModel(binding, payload) || !supportsAdapters(executionState(), adapters)) {
492
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', { requestId, error: 'Requested model is unavailable' });
493
+ return;
494
+ }
495
+ if (policy.models.contract !== null && JSON.stringify(snapshotJson(payload.modelContract ?? null)) !== JSON.stringify(policy.models.contract)) return;
496
+ const key = `${remotePeerId}:${requestId}`;
497
+ if (inbound.has(key) || inboundQueued.has(key)) return;
498
+ // A replay cannot evade the durable fence by downgrading its protocol flag.
499
+ const earlierAdmission = !payload.recoverable && await generationJournal.get({ provider: identityBundle.peerId, consumer, requestId });
500
+ if (closed || !ready() || !bindingAvailable(binding, adapters)) return;
501
+ if (earlierAdmission) {
502
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', { requestId, error: 'Previously admitted execution requires recovery' });
503
+ return;
504
+ }
505
+ if (!payload.recoverable && completed.has(key)) {
506
+ const previous = completed.get(key);
507
+ swarmTransport.sendToPeer(remotePeerId, previous.type, previous.payload);
508
+ return;
509
+ }
510
+ if (inbound.has(key) || inboundQueued.has(key)) return;
511
+
512
+ let controller;
513
+ if (inbound.size < policy.mesh.maxInboundJobs && inboundQueued.size === 0) {
514
+ controller = new AbortController();
515
+ inbound.set(key, controller);
516
+ } else if (inboundQueued.size >= policy.mesh.maxPendingJobs) {
517
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', { requestId, error: 'Provider is busy' });
518
+ return;
519
+ } else {
520
+ const acquired = await enqueueInboundSlot(key, requestId, remotePeerId, consumer, contextId);
521
+ if (!acquired) return;
522
+ controller = inbound.get(key);
523
+ if (!controller) return;
524
+ }
525
+
526
+ let finishSettlement;
527
+ const settlement = new Promise(resolve => { finishSettlement = resolve; });
528
+ inboundSettlements.add(settlement);
529
+ const deadline = setTimeout(() => controller.abort(new Error('Remote job deadline exceeded')), policy.mesh.generationTimeoutMs);
530
+
531
+ let recoveryBinding = null, claimedRecovery = false, retainedResult = false;
532
+ try {
533
+ if (closed) return;
534
+ if (!ready() || !bindingAvailable(binding, adapters)) {
535
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', {
536
+ requestId, error: 'Provider execution binding changed during authorization'
537
+ });
538
+ return;
539
+ }
540
+ controller.signal.throwIfAborted();
541
+ // An asserted consumer string cannot select somebody else's decoder memory.
542
+ const verifiedConsumer = (contextId || payload.recoverable === true)
543
+ ? await peerIdentity.verify(remotePeerId, controller.signal) : null;
544
+ if (contextId && (!verifiedConsumer || verifiedConsumer !== consumer)) throw Error('Generation context consumer identity changed');
545
+ const contextOwner = contextId ? verifiedConsumer : null;
546
+ if (payload.recoverable === true) {
547
+ if (verifiedConsumer !== consumer) throw Error('Generation consumer identity changed');
548
+ recoveryBinding = { requestId, provider: identityBundle.peerId, consumer, model: binding.model,
549
+ modelIdentity: binding.modelIdentity, adapterIdentities: adapters, inputDigest: await hashJson(messages) };
550
+ const existing = await generationJournal.get(recoveryBinding);
551
+ if (existing) {
552
+ if (JSON.stringify(existing.binding) !== JSON.stringify(recoveryBinding)) throw Error('Generation replay binding changed');
553
+ if (existing.outcome) swarmTransport.sendToPeer(remotePeerId, existing.outcome.type, existing.outcome.payload);
554
+ else swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', { requestId, error: 'Execution unresolved; query recovery without repeating it' });
555
+ return;
556
+ }
557
+ }
558
+ controller.signal.throwIfAborted();
559
+ if (await ports.authorize({ action: 'mesh.execute', peerId: remotePeerId, request: snapshotJson(payload) }) !== true) return;
560
+ if (recoveryBinding) {
561
+ claimedRecovery = await generationJournal.claim(recoveryBinding);
562
+ if (!claimedRecovery) {
563
+ const previous = await generationJournal.get(recoveryBinding);
564
+ if (JSON.stringify(previous?.binding) !== JSON.stringify(recoveryBinding)) throw Error('Generation replay binding changed');
565
+ if (previous?.outcome) swarmTransport.sendToPeer(remotePeerId, previous.outcome.type, previous.outcome.payload);
566
+ else swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-error', { requestId, error: 'Execution unresolved; query recovery without repeating it' });
567
+ return;
568
+ }
569
+ }
570
+ if (closed) return;
571
+ if (!ready() || !bindingAvailable(binding, adapters)) {
572
+ throw new Error('Provider execution binding changed during authorization');
573
+ }
574
+ controller.signal.throwIfAborted();
575
+ advertiseSelf();
576
+ const response = await generateBound(messages, (chunk) => {
577
+ if (closed || controller.signal.aborted) return;
578
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-update', {
579
+ requestId,
580
+ chunk
581
+ });
582
+ }, { signal: controller.signal, adapterIdentities: adapters, requestId, consumer, contextOwner, contextId }, binding);
583
+ controller.signal.throwIfAborted();
584
+
585
+ const receipt = await signReceiptDraft(
586
+ await createReceiptDraft({
587
+ provider: identityBundle.peerId,
588
+ consumer,
589
+ jobHash: `request:${requestId}`,
590
+ model: response.model,
591
+ inputTokens: 0,
592
+ outputTokens: 0,
593
+ status: 'unmetered',
594
+ ...await measureWholeModelReceipt(messages, response)
595
+ }),
596
+ identityBundle
597
+ );
598
+ controller.signal.throwIfAborted();
599
+ if (closed) return;
600
+
601
+ const result = { requestId, response, receipt };
602
+ if (recoveryBinding) {
603
+ await generationJournal.finish(recoveryBinding, { type: 'reploid:generation-result', payload: result });
604
+ retainedResult = true;
605
+ }
606
+ completed.set(key, { type: 'reploid:generation-result', payload: result });
607
+ swarmTransport.sendToPeer(remotePeerId, 'reploid:generation-result', result);
608
+ } catch (error) {
609
+ const payload = {
610
+ requestId,
611
+ error: error?.message || String(error),
612
+ ...(error?.code === 'CONTEXT_LENGTH_EXCEEDED' ? { code: 'CONTEXT_LENGTH_EXCEEDED' } : {})
613
+ };
614
+ if (claimedRecovery && !retainedResult && typeof ports.settleGeneration === 'function') {
615
+ try {
616
+ // A rejected promise alone cannot prove GPU cleanup. The owning host
617
+ // must confirm reset/disposal before this terminal record is signed.
618
+ if (await ports.settleGeneration() === true) {
619
+ payload.terminal = await signGenerationTerminal(recoveryBinding,
620
+ controller.signal.reason?.code === 'REQUESTER_CANCELLED' ? 'cancelled' : 'failed', identityBundle);
621
+ await generationJournal.finish(recoveryBinding, { type: 'reploid:generation-error', payload });
622
+ await ports.onGenerationTerminal?.(snapshotJson(payload.terminal));
623
+ }
624
+ } catch { /* No terminal proof: retain the admission fence and uncertainty. */ }
625
+ }
626
+ if (!closed) {
627
+ completed.set(key, { type: 'reploid:generation-error', payload });
628
+ swarmTransport?.sendToPeer(remotePeerId, 'reploid:generation-error', payload);
629
+ }
630
+ } finally {
631
+ while (completed.size > policy.mesh.maxRetainedJobs) completed.delete(completed.keys().next().value);
632
+ clearTimeout(deadline);
633
+ inbound.delete(key);
634
+ drainInboundQueue();
635
+ inboundSettlements.delete(settlement);
636
+ finishSettlement();
637
+ advertiseSelf();
638
+ }
639
+ };
640
+
641
+ const initialize = async () => {
642
+ if (closed) throw new Error('Mesh is closed');
643
+ if (swarmEnabled && await ports.authorize({ action: 'mesh.connect', roomId: policy.mesh.roomId }) !== true) {
644
+ throw new Error('Host denied mesh connection');
645
+ }
646
+ if (identityBundle && (!swarmEnabled || swarmInitialized)) {
647
+ return getSwarmSnapshot();
648
+ }
649
+ if (swarmInitPromise) return swarmInitPromise;
650
+
651
+ swarmInitPromise = (async () => {
652
+ identityBundle = await ports.identity.ensure({
653
+ instanceId,
654
+ swarmEnabled,
655
+ hasInference: ready(),
656
+ forceNew: pendingFreshIdentity
657
+ });
658
+ pendingFreshIdentity = false;
659
+
660
+ if (!swarmEnabled) {
661
+ return getSwarmSnapshot();
662
+ }
663
+
664
+ if (!swarmTransport) {
665
+ swarmTransport = ports.createTransport();
666
+ }
667
+
668
+ if (!swarmHandlersRegistered) {
669
+ peerIdentity = createMeshPeerIdentity({ transport: swarmTransport, getIdentity: () => identityBundle,
670
+ roomId: policy.mesh.roomId, timeoutMs: REMOTE_GENERATION_TIMEOUT_MS, maxPending: policy.mesh.maxPendingJobs,
671
+ onVerified: () => { if (!closed) emitSwarmState(); } });
672
+ recovery = createGenerationRecovery({ transport: swarmTransport, getIdentity: () => identityBundle,
673
+ verifyPeer: (peer, signal) => peerIdentity.verify(peer, signal), journal: generationJournal,
674
+ timeoutMs: REMOTE_GENERATION_TIMEOUT_MS, maxPending: policy.mesh.maxPendingJobs });
675
+ swarmTransport.onMessage('reploid:peer-advertisement', handlePeerAdvertisement);
676
+ swarmTransport.onMessage('reploid:generation-request', handleGenerationRequest);
677
+ swarmTransport.onMessage('reploid:generation-update', handleGenerationUpdate);
678
+ swarmTransport.onMessage('reploid:generation-result', handleGenerationResult);
679
+ swarmTransport.onMessage('reploid:generation-error', handleGenerationError);
680
+ swarmTransport.onMessage('reploid:generation-cancel', (peerId, payload = {}) => {
681
+ const key = `${peerId}:${String(payload.requestId || '')}`;
682
+ const reason = Object.assign(new Error('Requester stopped this job'), { code: 'REQUESTER_CANCELLED' });
683
+ if (!cancelQueuedInbound(key, reason, true)) inbound.get(key)?.abort(reason);
684
+ });
685
+ swarmTransport.onMessage('reploid:receipt', (peer, payload) => {
686
+ if (closed) return;
687
+ const pending = handleReceipt(peer, payload);
688
+ receiptSettlements.add(pending);
689
+ pending.then(() => receiptSettlements.delete(pending), () => receiptSettlements.delete(pending));
690
+ return pending;
691
+ });
692
+
693
+ subscriptions.push(eventBus.on('swarm:peer-connected', () => {
694
+ advertiseSelf();
695
+ emitSwarmState();
696
+ }, 'self-bridge'));
697
+ subscriptions.push(eventBus.on('swarm:peer-joined', () => {
698
+ advertiseSelf();
699
+ emitSwarmState();
700
+ }, 'self-bridge'));
701
+ const retirePeer = ({ peerId }) => {
702
+ peerIdentity?.retirePeer(peerId);
703
+ swarmController.removePeer(peerId);
704
+ observedPeers.delete(peerId);
705
+ identificationAttempts.delete(peerId);
706
+ remoteRequests.retirePeer(peerId);
707
+ for (const key of [...inboundQueued.keys()]) if (key.startsWith(`${peerId}:`)) cancelQueuedInbound(key, new Error('Requester disconnected'), false);
708
+ for (const [key, controller] of inbound) if (key.startsWith(`${peerId}:`)) controller.abort(new Error('Requester disconnected'));
709
+ emitSwarmState();
710
+ };
711
+ subscriptions.push(eventBus.on('swarm:peer-left', retirePeer, 'self-bridge'));
712
+ subscriptions.push(eventBus.on('swarm:peer-disconnected', retirePeer, 'self-bridge'));
713
+ subscriptions.push(eventBus.on('swarm:state-change', () => {
714
+ emitSwarmState();
715
+ }, 'self-bridge'));
716
+ subscriptions.push(eventBus.on('swarm:peer-progress', emitSwarmState, 'self-bridge'));
717
+ swarmHandlersRegistered = true;
718
+ }
719
+
720
+ swarmInitialized = await swarmTransport.init();
721
+ if (swarmInitialized) {
722
+ advertiseSelf();
723
+ }
724
+
725
+ return emitSwarmState();
726
+ })().finally(() => {
727
+ swarmInitPromise = null;
728
+ });
729
+
730
+ return swarmInitPromise;
731
+ };
732
+
733
+ const rotateIdentity = async (input = {}) => {
734
+ if (!identityBundle) {
735
+ await initialize();
736
+ }
737
+
738
+ identityBundle = await ports.identity.rotate({
739
+ ...input,
740
+ instanceId,
741
+ retireLegacy: input.retireLegacy !== false
742
+ });
743
+ await syncIdentityDocument();
744
+ if (swarmEnabled && swarmInitialized) {
745
+ advertiseSelf();
746
+ }
747
+ return emitSwarmState();
748
+ };
749
+
750
+
751
+ const generate = async (messages, onUpdate, { signal, modelId, modelIdentity = null, adapterIdentities = [], requestContext = null } = {}) => {
752
+ if (closed) throw new Error('Mesh is closed');
753
+ signal?.throwIfAborted();
754
+ messages = snapshotJson(messages);
755
+ requestContext = snapshotJson(requestContext);
756
+ const contextId = requestContext?.contextId ?? null;
757
+ if (contextId !== null && (typeof contextId !== 'string' || !contextId || contextId.length > 256)) throw Error('Invalid generation context ID');
758
+ adapterIdentities = adapterSet(adapterIdentities);
759
+ if (modelConfig) {
760
+ const binding = currentModelBinding();
761
+ if (!ready() || !matchesRequestedModel(binding, modelBinding(modelId, modelIdentity))) {
762
+ throw new Error('Requested model is unavailable');
763
+ }
764
+ if (!supportsAdapters(executionState(), adapterIdentities)) throw new Error('Requested adapter is unavailable');
765
+ return generateBound(messages, onUpdate, { signal, adapterIdentities, contextOwner: 'local', contextId }, binding);
766
+ }
767
+
768
+ if (!swarmEnabled) {
769
+ throw new Error('No model selected');
770
+ }
771
+
772
+ await initialize();
773
+ const affinityKey = contextId ? JSON.stringify([contextId, modelId, modelIdentity, adapterIdentities]) : null;
774
+ const provider = await chooseProvider(modelId, signal, modelIdentity, adapterIdentities, contextProviders.get(affinityKey));
775
+ if (!provider?.peerId || !swarmTransport || !identityBundle) {
776
+ // An adapter request never falls back to the base model or another adapter.
777
+ throw new Error(adapterIdentities.length ? 'No contributor serves the requested adapter. The base model was not used.'
778
+ : 'No remote host slot available');
779
+ }
780
+
781
+ // Bind automatic selection to the selected advertisement, not an open-ended request.
782
+ modelId ||= provider.model;
783
+ modelIdentity ||= provider.modelIdentity || null;
784
+ try {
785
+ if (remoteRequests.size >= policy.mesh.maxPendingJobs) throw new Error('Pending remote job limit exceeded');
786
+ const connectionBinding = JSON.stringify(swarmTransport.getPeerBinding?.(provider.peerId) || null);
787
+ const recipientIdentity = provider.identityProtocol === 1 ? await peerIdentity.verify(provider.peerId, signal) : null;
788
+ const requestId = utils.generateId('swarmreq');
789
+ const requestPayload = {
790
+ requestId, consumer: identityBundle.peerId, provider: provider.peerId,
791
+ ...(contextId && provider.identityProtocol === 1 ? { contextId } : {}),
792
+ model: modelId || provider.model || null, modelIdentity, adapterIdentities,
793
+ modelContract: policy.models.contract, messages, ...(requestContext?.recoverable === true ? { recoverable: true } : {})
794
+ };
795
+ if (Number.isSafeInteger(policy.webrtc?.maxMessageBytes) && policy.webrtc.maxMessageBytes > 0
796
+ && new TextEncoder().encode(JSON.stringify(requestPayload)).byteLength > policy.webrtc.maxMessageBytes) {
797
+ throw new Error(`Conversation exceeds transport payload limit (${policy.webrtc.maxMessageBytes} bytes)`);
798
+ }
799
+ if (await ports.authorize({ action: 'mesh.dispatch', peerId: provider.peerId, modelId, modelIdentity, messages: snapshotJson(messages),
800
+ requestId, recipientIdentity, adapterIdentities: [...adapterIdentities], requestContext: snapshotJson(requestContext) }) !== true) {
801
+ throw new Error('Host denied remote prompt disclosure');
802
+ }
803
+ signal?.throwIfAborted();
804
+ if (closed) throw new Error('Mesh is closed');
805
+ const currentProvider = swarmController.listPeers().find(peer => peer.peerId === provider.peerId);
806
+ if (!currentProvider || currentProvider.readiness !== 'ready' || currentProvider.availableSlots === 0
807
+ || JSON.stringify(swarmTransport.getPeerBinding?.(provider.peerId) || null) !== connectionBinding
808
+ || currentProvider.model !== provider.model || currentProvider.modelIdentity !== provider.modelIdentity
809
+ || !supportsAdapters(currentProvider, adapterIdentities)) {
810
+ throw new Error('Selected contributor is no longer ready; retry requires new placement');
811
+ }
812
+ const result = await remoteRequests.start({ requestId, modelId, modelIdentity, adapterIdentities,
813
+ providerPeerId: provider.peerId, providerIdentity: recipientIdentity, messages, onUpdate, signal }, () =>
814
+ swarmTransport.sendToPeer(provider.peerId, 'reploid:generation-request', requestPayload));
815
+ if (affinityKey) {
816
+ contextProviders.delete(affinityKey);
817
+ contextProviders.set(affinityKey, provider.peerId);
818
+ while (contextProviders.size > policy.mesh.maxRetainedJobs) contextProviders.delete(contextProviders.keys().next().value);
819
+ }
820
+ return result;
821
+ } finally {
822
+ reservedProviders.delete(provider.peerId);
823
+ emitSwarmState();
824
+ }
825
+ };
826
+
827
+ const recoverGeneration = async (reference, messages, signal) => {
828
+ await initialize(); signal?.throwIfAborted();
829
+ reference = snapshotJson(reference); messages = snapshotJson(messages);
830
+ if (!reference?.requestId || !reference.providerIdentity || !reference.providerId) throw Error('Bound recovery reference required');
831
+ const binding = { requestId: reference.requestId, provider: reference.providerIdentity, consumer: identityBundle.peerId,
832
+ model: reference.modelId, modelIdentity: reference.modelIdentity, adapterIdentities: adapterSet(reference.adapterIdentities),
833
+ inputDigest: await hashJson(messages) };
834
+ const outcome = await recovery.read(reference, signal);
835
+ signal?.throwIfAborted();
836
+ if (closed) throw Error('Mesh closed during recovery');
837
+ if (!outcome) return { status: 'unresolved' };
838
+ const payload = outcome.payload;
839
+ if (payload?.requestId !== reference.requestId) throw Error('Recovery request identity changed');
840
+ if (outcome.type === 'reploid:generation-error') {
841
+ if (!await verifyGenerationTerminal(payload.terminal, binding)) throw Error('Unverified recovery cleanup');
842
+ return { status: payload.terminal.status, terminal: payload.terminal };
843
+ }
844
+ const response = payload.response, receipt = payload.receipt;
845
+ const verified = receipt && await verifyReceipt(receipt);
846
+ if (outcome.type !== 'reploid:generation-result' || receipt?.version !== 2 || !verified?.providerValid
847
+ || !matchesRequestedModel(response, modelBinding(binding.model, binding.modelIdentity))
848
+ || receipt.provider !== binding.provider || receipt.consumer !== binding.consumer || receipt.model !== binding.model
849
+ || receipt.jobHash !== `request:${binding.requestId}` || receipt.modelIdentity !== binding.modelIdentity
850
+ || JSON.stringify(receipt.adapterIdentities) !== JSON.stringify(binding.adapterIdentities)
851
+ || !await matchesWholeModelReceipt(receipt, messages, response)) throw Error('Unverified recovered completion');
852
+ response.executionReceipt = await countersignReceipt(receipt, identityBundle);
853
+ signal?.throwIfAborted();
854
+ if (closed) throw Error('Mesh closed during recovery');
855
+ swarmTransport.sendToPeer(reference.providerId, 'reploid:receipt', { receipt: response.executionReceipt });
856
+ return { status: 'completed', response };
857
+ };
858
+
859
+ const close = () => {
860
+ if (closing) return closing;
861
+ // Withdraw admission immediately; cancellation is a request, not settlement.
862
+ closed = true;
863
+ closing = Promise.resolve().then(async () => {
864
+ lifetime.abort(new Error('Mesh closed'));
865
+ peerIdentity?.close();
866
+ const recoveryClose = recovery?.close();
867
+ for (const finish of providerWaiters) finish(false);
868
+ remoteRequests.close();
869
+ for (const key of [...inboundQueued.keys()]) cancelQueuedInbound(key, new Error('Mesh closed'), false);
870
+ for (const controller of inbound.values()) controller.abort(new Error('Mesh closed'));
871
+ for (const unsubscribe of subscriptions.splice(0)) unsubscribe?.();
872
+ swarmTransport?.disconnect();
873
+ // Keep every close caller waiting while an admitted executor drains, even
874
+ // when its cancellation port cannot interrupt the current GPU operation.
875
+ await Promise.all([recoveryClose, ...identifying, ...inboundSettlements, ...receiptSettlements, ...[...executions].map(pending => pending.catch(() => {})),
876
+ ...(swarmInitPromise ? [swarmInitPromise.catch(() => {})] : [])]);
877
+ completed.clear();
878
+ identificationAttempts.clear();
879
+ swarmTransport?.disconnect();
880
+ });
881
+ return closing;
882
+ };
883
+ return Object.freeze({ connect: initialize, initialize, generate, recoverGeneration, rotateIdentity, getSwarmSnapshot,
884
+ identifyComputer: (peerId, options = {}) => {
885
+ if (closed || !peerIdentity) throw new Error('Peer identity verification is unavailable');
886
+ return identifyComputer(peerId, { ...options,
887
+ hardware: swarmController.listPeers().find(peer => peer.peerId === peerId)?.hardware,
888
+ verifyPeer: (id, signal) => peerIdentity.verify(id, signal) });
889
+ },
890
+ verifyPeerIdentity: (peerId, signal) => {
891
+ if (closed || !peerIdentity) throw new Error('Peer identity verification is unavailable');
892
+ return peerIdentity.verify(peerId, signal);
893
+ },
894
+ refreshAdvertisement: () => { advertiseSelf(); return emitSwarmState(); },
895
+ hasAvailableProvider: modelId => !closed && !!chooseCompatibleProvider(modelId, true), close, on: bridgeEvents.on });
896
+ }