reploid 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +534 -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 +83 -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,132 @@
1
+ import { assert, digest, integer, text } from './contract.js';
2
+
3
+ const key = value => `${value.id}:${value.identity}`;
4
+ const fresh = (at, now, policy) => Number.isFinite(at) && at <= now && now - at <= policy.maxObservationAgeMs;
5
+ const positive = value => Number.isFinite(value) && value > 0;
6
+ const compare = (a, b) => a.costMs - b.costMs || a.computer.id.localeCompare(b.computer.id);
7
+
8
+ /** Deterministic policy over measured execution envelopes. Advertised GPU names never establish fit. */
9
+ export function planFleetPlacement({ computers, demand, models, links, policy, now }) {
10
+ assert(integer(now) && integer(policy?.maxObservationAgeMs) && policy.maxObservationAgeMs > 0
11
+ && integer(policy.maxMeasurementAgeMs) && policy.maxMeasurementAgeMs > 0
12
+ && integer(policy.minResidencyMs) && positive(policy.maxRouteCandidates)
13
+ && Number.isSafeInteger(policy.maxRouteCandidates) && positive(policy.queuedRequestsPerReplica), 'Explicit placement policy required');
14
+ assert(Array.isArray(computers) && Array.isArray(demand) && Array.isArray(models) && Array.isArray(links), 'Fleet observations required');
15
+ const machines = structuredClone(computers), catalog = structuredClone(models), decisions = [], routes = [], unavailable = [];
16
+ assert(new Set(machines.map(row => row.id)).size === machines.length, 'Duplicate computer');
17
+ const occupied = new Set();
18
+ for (const computer of machines) {
19
+ assert(text(computer.id) && text(computer.participantId) && Array.isArray(computer.measurements)
20
+ && Array.isArray(computer.preferences?.models) && integer(computer.activeRequests), 'Invalid computer observation');
21
+ const allowed = computer.preferences.enabled === true && (!computer.resident
22
+ || computer.preferences.models.some(model => model.identity === computer.resident.modelIdentity));
23
+ if (!allowed && computer.activeRequests === 0) {
24
+ occupied.add(computer.id);
25
+ decisions.push({ computerId: computer.id, action: 'stop', reason: 'owner-preferences', resident: computer.resident ?? null });
26
+ continue;
27
+ }
28
+ if (computer.activeRequests > 0 || computer.resident && now - computer.resident.preparedAt < policy.minResidencyMs) {
29
+ occupied.add(computer.id);
30
+ decisions.push({ computerId: computer.id, action: 'retain', reason: computer.activeRequests ? 'active-requests' : 'minimum-residency',
31
+ resident: computer.resident ?? null });
32
+ }
33
+ }
34
+ function candidates(model, workload, mode, planId = null, stageIndex = null) {
35
+ const results = [];
36
+ for (const computer of machines) {
37
+ const preferences = computer.preferences;
38
+ if (preferences.enabled !== true || !fresh(computer.observedAt, now, policy)
39
+ || !preferences.models.some(value => key(value) === key(model))) continue;
40
+ for (const measurement of computer.measurements) {
41
+ if (measurement.modelIdentity !== model.identity || measurement.mode !== mode
42
+ || (measurement.planId ?? null) !== planId || (measurement.stageIndex ?? null) !== stageIndex
43
+ || measurement.qualified !== true || !text(measurement.receipt)
44
+ || !digest(computer.softwareIdentity) || measurement.softwareIdentity !== computer.softwareIdentity
45
+ || !fresh(measurement.observedAt, now, { maxObservationAgeMs: policy.maxMeasurementAgeMs })
46
+ || !positive(measurement.prefillTokensPerSecond) || !positive(measurement.decodeTokensPerSecond)
47
+ || !integer(measurement.loadMs) || !integer(measurement.requiredGpuBytes) || !integer(measurement.requiredHostBytes)
48
+ || !integer(measurement.maxPromptTokens) || workload.promptTokens > measurement.maxPromptTokens
49
+ || !integer(measurement.maxOutputTokens) || workload.outputTokens > measurement.maxOutputTokens
50
+ || !integer(measurement.largestBindingBytes)) continue;
51
+ const sameResident = computer.resident?.modelIdentity === model.identity && computer.resident.mode === mode
52
+ && (computer.resident.planId ?? null) === planId && (computer.resident.stageIndex ?? null) === stageIndex;
53
+ // freeGpuBytes excludes this service's removable resident; explicitly reclaim only its accounted buffers.
54
+ const reclaim = sameResident || computer.activeRequests === 0 ? computer.resident?.gpuBytes ?? 0 : 0;
55
+ const gpuBudget = Math.min(preferences.maxGpuBytes, computer.freeGpuBytes + reclaim);
56
+ const hostBudget = Math.min(preferences.maxHostBytes, computer.freeHostBytes + (sameResident || computer.activeRequests === 0 ? computer.resident?.hostBytes ?? 0 : 0));
57
+ if (![gpuBudget, hostBudget, computer.maxStorageBufferBindingSize].every(integer)
58
+ || measurement.requiredGpuBytes > gpuBudget || measurement.requiredHostBytes > hostBudget
59
+ || measurement.largestBindingBytes > computer.maxStorageBufferBindingSize) continue;
60
+ results.push({ computer, measurement, sameResident, mode, planId, stageIndex });
61
+ }
62
+ }
63
+ return results;
64
+ }
65
+ function assign(candidate, model, reason) {
66
+ occupied.add(candidate.computer.id);
67
+ decisions.push({ computerId: candidate.computer.id, action: candidate.sameResident ? 'retain' : 'prepare', reason,
68
+ modelId: model.id, modelIdentity: model.identity, mode: candidate.mode, planId: candidate.planId,
69
+ stageIndex: candidate.stageIndex, evidence: candidate.measurement.receipt, estimatedMs: candidate.costMs });
70
+ }
71
+ const workloads = structuredClone(demand).sort((a, b) => b.queued - a.queued || a.since - b.since || a.id.localeCompare(b.id));
72
+ for (const workload of workloads) {
73
+ assert(text(workload.id) && digest(workload.identity) && integer(workload.queued) && integer(workload.promptTokens)
74
+ && integer(workload.outputTokens) && integer(workload.since), 'Invalid measured demand');
75
+ if (workload.queued === 0) continue;
76
+ const model = catalog.find(value => key(value) === key(workload));
77
+ if (!model) { unavailable.push({ modelId: workload.id, reason: 'unknown-exact-model' }); continue; }
78
+ const cost = candidate => ({ ...candidate, costMs: (candidate.sameResident ? 0 : candidate.measurement.loadMs)
79
+ + 1000 * (workload.promptTokens / candidate.measurement.prefillTokensPerSecond
80
+ + workload.outputTokens / candidate.measurement.decodeTokensPerSecond) });
81
+ const whole = candidates(model, workload, 'whole-model').map(cost).sort(compare);
82
+ // The product contract prefers feasible whole-model service, including a busy provider's next slot.
83
+ if (whole.length) {
84
+ let count = 0;
85
+ const wanted = Math.ceil(workload.queued / policy.queuedRequestsPerReplica);
86
+ for (const candidate of whole) {
87
+ if (occupied.has(candidate.computer.id)) {
88
+ if (candidate.sameResident && count < wanted) {
89
+ count++;
90
+ routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'whole-model', computers: [candidate.computer.id],
91
+ estimatedMs: candidate.costMs, busy: candidate.computer.activeRequests > 0 });
92
+ }
93
+ continue;
94
+ }
95
+ if (count >= wanted) break;
96
+ assign(candidate, model, count ? 'queued-demand-replica' : 'fastest-feasible-whole-model'); count++;
97
+ routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'whole-model', computers: [candidate.computer.id], estimatedMs: candidate.costMs });
98
+ }
99
+ if (!count) unavailable.push({ modelId: model.id, reason: 'eligible-computer-busy' });
100
+ continue;
101
+ }
102
+ let best = null, visited = 0;
103
+ for (const plan of model.plans || []) {
104
+ assert(digest(plan.id) && integer(plan.stageCount) && plan.stageCount >= 2 && integer(plan.activationBytesPerToken), 'Invalid partition plan envelope');
105
+ const stages = Array.from({ length: plan.stageCount }, (_, index) => candidates(model, workload, 'partition', plan.id, index)
106
+ .filter(candidate => !occupied.has(candidate.computer.id) || candidate.sameResident).map(cost).sort(compare));
107
+ const walk = (index, route, costMs) => {
108
+ if (visited++ >= policy.maxRouteCandidates || best && costMs >= best.costMs) return;
109
+ if (index === stages.length) { best = { route, costMs, planId: plan.id }; return; }
110
+ for (const candidate of stages[index]) {
111
+ if (route.some(value => value.computer.id === candidate.computer.id)) continue;
112
+ let transfer = 0;
113
+ if (index > 0) {
114
+ const link = links.find(value => value.from === route[index - 1].computer.id && value.to === candidate.computer.id
115
+ && fresh(value.observedAt, now, policy) && positive(value.bytesPerSecond) && Number.isFinite(value.latencyMs) && value.latencyMs >= 0);
116
+ if (!link) continue;
117
+ transfer = (workload.outputTokens + 1) * link.latencyMs
118
+ + 1000 * plan.activationBytesPerToken * (workload.promptTokens + workload.outputTokens) / link.bytesPerSecond;
119
+ }
120
+ walk(index + 1, [...route, candidate], costMs + candidate.costMs + transfer);
121
+ }
122
+ };
123
+ walk(0, [], 0);
124
+ }
125
+ if (!best) { unavailable.push({ modelId: model.id, reason: visited >= policy.maxRouteCandidates ? 'bounded-route-search-exhausted' : 'no-measured-complete-route' }); continue; }
126
+ for (const candidate of best.route) if (!occupied.has(candidate.computer.id)) assign(candidate, model, 'complete-partition-route');
127
+ routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'partition', planId: best.planId,
128
+ computers: best.route.map(value => value.computer.id), estimatedMs: best.costMs });
129
+ }
130
+ for (const computer of machines) if (!occupied.has(computer.id)) decisions.push({ computerId: computer.id, action: 'retain', reason: 'no-uncovered-demand', resident: computer.resident ?? null });
131
+ return { schema: 'reploid.fleet-placement/v1', at: now, decisions, routes, unavailable };
132
+ }
@@ -0,0 +1,346 @@
1
+ import { hashJson } from '../../packages/reploid/src/artifacts/signatures.js';
2
+ import { assert } from './contract.js';
3
+ import { createAttemptEvidenceSigner, openOutputAcknowledgments } from '../../packages/reploid/src/mesh/partitions/partition-attempt-evidence.js';
4
+ import { partitionAdapterIdentities } from '../../packages/reploid/src/mesh/partitions/partition-contract.js';
5
+
6
+ export function buildFleetRequestOutcome({
7
+ attemptId = null, threadId = null, reservationId = null, executionId = null,
8
+ mode, model, providerId = null, providerIdentity = null, inputDigest,
9
+ phase, dispatched, acknowledged, settlement = null, maximum = 0, recordedAt = Date.now()
10
+ }) {
11
+ const settled = typeof settlement?.settledAt === 'number';
12
+ const terminal = settlement?.status === 'cancelled' || settlement?.status === 'failed';
13
+ const finished = !dispatched
14
+ ? {
15
+ status: settlement?.status ?? 'cancelled_queued',
16
+ charge: 0,
17
+ outputTokens: 0,
18
+ settledAt: settlement?.settledAt ?? recordedAt,
19
+ evidenceHash: null
20
+ }
21
+ : settled
22
+ ? {
23
+ status: settlement.status ?? 'completed',
24
+ charge: settlement.charge,
25
+ outputTokens: settlement.outputTokens ?? null,
26
+ settledAt: settlement.settledAt,
27
+ evidenceHash: settlement.evidenceHash ?? null
28
+ }
29
+ : null;
30
+ const unknown = !dispatched ? null : settled
31
+ ? (mode === 'whole-model' && terminal ? { providerExecutionTokensBeforeStop: 'unmetered' } : null)
32
+ : {
33
+ reservationId,
34
+ executionId,
35
+ providerId,
36
+ providerIdentity,
37
+ providerExecution: 'unresolved',
38
+ heldCredits: maximum
39
+ };
40
+ return Object.freeze({
41
+ schema: 'reploid.request-outcome/v1',
42
+ attemptId,
43
+ threadId,
44
+ reservationId,
45
+ executionId,
46
+ mode,
47
+ model: Object.freeze({ id: model.id, identity: model.identity }),
48
+ providerId,
49
+ providerIdentity,
50
+ inputDigest,
51
+ phase,
52
+ dispatched,
53
+ acknowledged: Object.freeze({
54
+ deltaCount: acknowledged?.deltaCount ?? 0,
55
+ outputCharacters: acknowledged?.outputCharacters ?? 0,
56
+ acknowledgedOutputTokens: acknowledged?.acknowledgedOutputTokens ?? 0
57
+ }),
58
+ finished: finished ? Object.freeze(finished) : null,
59
+ unknown: unknown ? Object.freeze(unknown) : null,
60
+ recordedAt
61
+ });
62
+ }
63
+
64
+ /** Compose credit admission with the native requester's existing disclosure gate. */
65
+ export function createFleetRequester({
66
+ participant, identity, credits, maxOutputTokens,
67
+ onOutcome = () => {}, lookupOutcome = () => null, resolveProviderId = () => null
68
+ }) {
69
+ assert(participant?.generate && identity?.peerId && credits?.reserve && credits?.settle,
70
+ 'Requester, signing identity and service-credit owner required');
71
+ const readReservation = id => typeof credits.request === 'function' ? credits.request(id) : credits.call('request', { id });
72
+ const recoverReservation = id => typeof credits.recover === 'function' ? credits.recover(id) : credits.call('recover', { id });
73
+ return Object.freeze({
74
+ async generate(request, controls) {
75
+ request = structuredClone(request);
76
+ assert(typeof controls?.requestApproval === 'function' && controls.signal, 'Explicit disclosure and cancellation required');
77
+ const partition = Boolean(request.model.partition);
78
+ const mode = partition ? 'partition' : 'whole-model';
79
+ const outputLimit = Math.min(maxOutputTokens, request.generationRequest?.maxOutputTokens ?? maxOutputTokens,
80
+ request.model.generation?.maxTokens ?? maxOutputTokens);
81
+ assert(Number.isSafeInteger(outputLimit) && outputLimit > 0, 'Explicit bounded output required');
82
+ if (partition) request.generationRequest = { schema: 'reploid.partition-generation-request/v1',
83
+ usage: 'raw-model-tokens/v1', maxOutputTokens: outputLimit };
84
+ const inputDigest = await hashJson(request.messages);
85
+ let reservation = null, dispatched = false, abandoned = false, acknowledgments = null, routeIdentity = null, observeOutput = null;
86
+ let disclosedProviderId = null, disclosedProviderIdentity = null, deltaCount = 0, outputCharacters = 0;
87
+ const wrapped = { ...controls,
88
+ ...(controls.onDelta ? {
89
+ onDelta(value) {
90
+ const chunk = typeof value === 'string' ? value : typeof value?.text === 'string' ? value.text : '';
91
+ deltaCount += 1;
92
+ outputCharacters += chunk.length;
93
+ return controls.onDelta(value);
94
+ }
95
+ } : {}),
96
+ authorizationExpiresAt: () => Math.min(reservation?.deadline ?? Infinity, controls.authorizationExpiresAt?.() ?? Infinity),
97
+ requestApproval: async disclosure => {
98
+ assert(!reservation, 'One provider per request; replacements need explicit retry');
99
+ if (await controls.requestApproval(structuredClone(disclosure)) !== true) return false;
100
+ controls.signal.throwIfAborted();
101
+ const executionId = partition ? request.attemptId : disclosure.requestId;
102
+ const binding = { id: `service:${identity.peerId}:${executionId}`, executionId,
103
+ consumer: identity.peerId, mode, model: { id: request.model.id,
104
+ identity: request.model.identity, adapterIdentities: partitionAdapterIdentities(request.model) },
105
+ inputDigest, maxOutputTokens: outputLimit,
106
+ providers: [{ participantId: typeof disclosure.recipientIdentity === 'string' ? disclosure.recipientIdentity
107
+ : disclosure.recipientIdentity?.participantId ?? disclosure.recipientIdentity?.peerId, weight: 1 }] };
108
+ if (partition) {
109
+ routeIdentity = { schema: 'reploid.partition-attempt/v2', threadId: request.threadId, attemptId: executionId,
110
+ requesterId: identity.peerId, placementGeneration: request.placementGeneration ?? 0, route: disclosure.route };
111
+ const terms = await credits.call('terms', binding.model);
112
+ const plan = terms.model.plans?.find(row => row.id === disclosure.route.planId);
113
+ assert(plan?.creditWeights?.length === disclosure.route.stages.length, 'No accepted credit terms for this partition');
114
+ binding.route = routeIdentity;
115
+ binding.providers = disclosure.route.stages.map((stage, index) => ({ participantId: stage.participantId, weight: plan.creditWeights[index] }));
116
+ acknowledgments = await openOutputAcknowledgments({ signer: createAttemptEvidenceSigner({ identity }),
117
+ reservationId: binding.id, identity: routeIdentity });
118
+ wrapped.charge = acknowledgments.charge;
119
+ wrapped.onOutput = observeOutput = async record => {
120
+ const pair = await acknowledgments.observe(record);
121
+ if (pair) await credits.collect(binding.id, { identity: routeIdentity, receipts: [], acknowledged: pair });
122
+ return pair;
123
+ };
124
+ }
125
+ disclosedProviderId = typeof disclosure.providerId === 'string' ? disclosure.providerId : null;
126
+ disclosedProviderIdentity = binding.providers[0]?.participantId ?? null;
127
+ reservation = await credits.reserve(binding);
128
+ try {
129
+ if (partition) await credits.collect(reservation.id, { identity: routeIdentity, receipts: [], acknowledgment: acknowledgments.charge });
130
+ controls.signal.throwIfAborted();
131
+ // A lost response leaves admission uncertain; never automatically release that hold.
132
+ dispatched = true;
133
+ await credits.dispatch(reservation.id, identity.peerId);
134
+ controls.onReservation?.(reservation.id);
135
+ } catch (error) {
136
+ if (!dispatched) {
137
+ await credits.abandonBeforeDispatch(reservation.id, identity.peerId);
138
+ abandoned = true;
139
+ }
140
+ throw error;
141
+ }
142
+ return true;
143
+ }
144
+ };
145
+ let result, failure;
146
+ try { result = await participant.generate(request, wrapped); }
147
+ catch (error) { failure = error; }
148
+ if (!reservation || !dispatched) {
149
+ const recordedAt = Date.now();
150
+ const outcome = buildFleetRequestOutcome({
151
+ attemptId: request.attemptId ?? null,
152
+ threadId: request.threadId ?? null,
153
+ reservationId: reservation?.id ?? null,
154
+ executionId: reservation?.executionId ?? null,
155
+ mode,
156
+ model: request.model,
157
+ inputDigest,
158
+ phase: 'queued',
159
+ dispatched: false,
160
+ acknowledged: { deltaCount: 0, outputCharacters: 0, acknowledgedOutputTokens: 0 },
161
+ settlement: { status: abandoned ? 'abandoned' : 'cancelled_queued', charge: 0, outputTokens: 0, settledAt: recordedAt },
162
+ maximum: 0,
163
+ recordedAt
164
+ });
165
+ onOutcome(outcome);
166
+ if (failure) {
167
+ if (typeof failure === 'object') failure.disconnect = outcome;
168
+ throw failure;
169
+ }
170
+ throw Error('Output arrived without credit admission');
171
+ }
172
+ let settlement;
173
+ try {
174
+ if (partition) {
175
+ const closing = result?.execution?.settlement ?? failure?.partitionSettlement;
176
+ // A dropped output RPC can recover through the bounded closing response. These are
177
+ // actual final-stage signed outputs, not a token count inferred from the visible answer.
178
+ for (const record of closing?.unacknowledgedOutputs || []) {
179
+ if (record.step >= acknowledgments.state().count) await observeOutput(record);
180
+ }
181
+ const pair = acknowledgments.evidence().acknowledged;
182
+ const evidence = { identity: routeIdentity,
183
+ receipts: (closing?.cleanup || []).flatMap(row => row?.receipt ? [row.receipt] : []),
184
+ acknowledgment: pair?.acknowledgment ?? acknowledgments.charge,
185
+ ...(pair ? { acknowledgedReceipt: pair.receipt } : {}) };
186
+ await credits.collect(reservation.id, evidence);
187
+ if (result && evidence.receipts.length === routeIdentity.route.stages.length) {
188
+ settlement = await credits.settle(reservation.id, evidence);
189
+ } else settlement = await credits.fence(reservation.id, identity.peerId);
190
+ } else if (result?.execution?.receipt) settlement = await credits.settle(reservation.id, result.execution.receipt);
191
+ else if (failure?.generationTerminal) settlement = await credits.collect(reservation.id, { terminal: failure.generationTerminal });
192
+ else settlement = await credits.fence(reservation.id, identity.peerId);
193
+ } catch (error) {
194
+ settlement = { id: reservation.id, status: 'pending', error: error.message };
195
+ // Sealing admission is durable even if collection/settlement could not reach the service.
196
+ await credits.fence(reservation.id, identity.peerId).catch(() => {});
197
+ }
198
+ const completed = typeof settlement?.settledAt === 'number' && !['cancelled', 'failed'].includes(settlement?.status);
199
+ const acknowledgedOutputTokens = partition
200
+ ? (acknowledgments?.state().count ?? 0)
201
+ : completed ? (settlement.outputTokens ?? result?.usage?.outputTokens ?? 0) : 0;
202
+ const outcome = buildFleetRequestOutcome({
203
+ attemptId: request.attemptId ?? null,
204
+ threadId: request.threadId ?? null,
205
+ reservationId: reservation.id,
206
+ executionId: reservation.executionId,
207
+ mode,
208
+ model: request.model,
209
+ providerId: disclosedProviderId,
210
+ providerIdentity: disclosedProviderIdentity,
211
+ inputDigest,
212
+ phase: 'in-flight',
213
+ dispatched: true,
214
+ acknowledged: { deltaCount, outputCharacters, acknowledgedOutputTokens },
215
+ settlement,
216
+ maximum: reservation.maximum,
217
+ recordedAt: Date.now()
218
+ });
219
+ onOutcome(outcome);
220
+ if (failure) {
221
+ if (typeof failure === 'object') {
222
+ failure.serviceCredit = settlement;
223
+ failure.disconnect = outcome;
224
+ }
225
+ throw failure;
226
+ }
227
+ return { ...result, serviceCredit: settlement };
228
+ },
229
+ async recover({ reservationId, messages, providerId }, controls, recoverExecution = participant.recoverGeneration) {
230
+ assert(typeof reservationId === 'string' && reservationId.length > 0 && Array.isArray(messages) && messages.length > 0
231
+ && controls?.signal && typeof recoverExecution === 'function', 'Bound reservation, original messages and cancellation required');
232
+ controls.signal.throwIfAborted();
233
+ let row;
234
+ try { row = await readReservation(reservationId); }
235
+ catch (error) {
236
+ if (/Unknown credit reservation|Not a participant/i.test(error?.message || '')) {
237
+ throw Object.assign(Error('Unknown credit reservation'), { status: 404 });
238
+ }
239
+ throw error;
240
+ }
241
+ if (!row) throw Object.assign(Error('Unknown credit reservation'), { status: 404 });
242
+ if (row.binding?.consumer !== identity.peerId) {
243
+ throw Object.assign(Error('Reservation belongs to another requester'), { status: 403 });
244
+ }
245
+ const inputDigest = await hashJson(messages);
246
+ if (inputDigest !== row.binding.inputDigest) {
247
+ throw Object.assign(Error('Recovery input differs from the accepted request'), { status: 409 });
248
+ }
249
+ const prior = lookupOutcome(reservationId);
250
+ const emit = (dispatched, settlement, extraTokens = null, resolvedPeer = null) => {
251
+ const completed = typeof settlement?.settledAt === 'number' && !['cancelled', 'failed'].includes(settlement?.status);
252
+ const outcome = buildFleetRequestOutcome({
253
+ attemptId: prior?.attemptId ?? null,
254
+ threadId: prior?.threadId ?? null,
255
+ reservationId,
256
+ executionId: row.binding.executionId,
257
+ mode: row.binding.mode,
258
+ model: row.binding.model,
259
+ providerId: resolvedPeer ?? prior?.providerId ?? null,
260
+ providerIdentity: row.binding.providers[0]?.participantId ?? null,
261
+ inputDigest,
262
+ phase: dispatched ? 'in-flight' : 'queued',
263
+ dispatched,
264
+ acknowledged: {
265
+ deltaCount: prior?.acknowledged?.deltaCount ?? 0,
266
+ outputCharacters: prior?.acknowledged?.outputCharacters ?? 0,
267
+ acknowledgedOutputTokens: extraTokens ?? (completed ? (settlement.outputTokens ?? 0) : (prior?.acknowledged?.acknowledgedOutputTokens ?? 0))
268
+ },
269
+ settlement,
270
+ maximum: dispatched ? row.binding.maximum : 0,
271
+ recordedAt: Date.now()
272
+ });
273
+ onOutcome(outcome);
274
+ return outcome;
275
+ };
276
+ if (row.state === 'abandoned') {
277
+ const outcome = emit(false, { status: 'abandoned', charge: 0, outputTokens: 0, settledAt: row.binding.createdAt });
278
+ return { status: 'abandoned', outcome };
279
+ }
280
+ if (row.state === 'reserved') {
281
+ await credits.abandonBeforeDispatch(reservationId, identity.peerId);
282
+ const outcome = emit(false, { status: 'abandoned', charge: 0, outputTokens: 0, settledAt: Date.now() });
283
+ return { status: 'abandoned', outcome };
284
+ }
285
+ if (row.state === 'settled') {
286
+ const outcome = emit(true, row.outcome);
287
+ return { status: row.outcome.status ?? 'completed', serviceCredit: row.outcome, outcome };
288
+ }
289
+ if (row.binding.mode === 'partition') {
290
+ const settled = await recoverReservation(reservationId);
291
+ const outcome = emit(true, settled);
292
+ return { status: typeof settled?.settledAt === 'number' ? (settled.status ?? 'completed') : 'unresolved', serviceCredit: settled, outcome };
293
+ }
294
+ const fenced = await credits.fence(reservationId, identity.peerId);
295
+ if (typeof fenced?.settledAt === 'number') {
296
+ const outcome = emit(true, fenced);
297
+ return { status: fenced.status ?? 'completed', serviceCredit: fenced, outcome };
298
+ }
299
+ const targetProviderId = providerId ?? prior?.providerId ?? await resolveProviderId(reservationId, row.binding);
300
+ if (typeof targetProviderId !== 'string' || !targetProviderId) {
301
+ throw Object.assign(Error('Recovery peer required'), { status: 409 });
302
+ }
303
+ const request = {
304
+ threadId: prior?.threadId ?? `recover-${row.binding.executionId}`,
305
+ attemptId: prior?.attemptId ?? row.binding.executionId,
306
+ model: {
307
+ id: row.binding.model.id,
308
+ identity: row.binding.model.identity,
309
+ adapters: (row.binding.model.adapterIdentities || []).map(adapterId => ({
310
+ identity: adapterId, baseModelIdentity: row.binding.model.identity
311
+ }))
312
+ },
313
+ messages: structuredClone(messages)
314
+ };
315
+ const reference = {
316
+ requestId: row.binding.executionId,
317
+ providerId: targetProviderId,
318
+ providerIdentity: row.binding.providers[0].participantId
319
+ };
320
+ const recovered = await recoverExecution(request, reference, { signal: controls.signal });
321
+ controls.signal.throwIfAborted();
322
+ if (recovered?.status === 'unresolved') {
323
+ const outcome = emit(true, { id: reservationId, status: 'pending' }, null, targetProviderId);
324
+ return {
325
+ status: 'unresolved',
326
+ serviceCredit: { id: reservationId, status: 'pending', heldCredits: row.binding.maximum },
327
+ outcome
328
+ };
329
+ }
330
+ if (recovered?.status === 'completed') {
331
+ const settled = await credits.settle(reservationId, recovered.result.execution.receipt);
332
+ const outcome = emit(true, settled, settled.outputTokens, targetProviderId);
333
+ return {
334
+ status: 'completed',
335
+ ...recovered.result,
336
+ execution: { ...recovered.result.execution, serviceCredit: settled },
337
+ serviceCredit: settled,
338
+ outcome
339
+ };
340
+ }
341
+ const settled = await credits.collect(reservationId, { terminal: recovered.terminal });
342
+ const outcome = emit(true, settled, 0, targetProviderId);
343
+ return { status: recovered.terminal.status, serviceCredit: settled, outcome };
344
+ }
345
+ });
346
+ }