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,192 @@
1
+ import failureMessages from '../../transport/partition-errors.json' with { type: 'json' };
2
+ import { canonicalize } from '../../artifacts/canonical-json.js';
3
+ import { sha256Hex } from '../../artifacts/signatures.js';
4
+
5
+ /** Shared orchestration bindings. Tensor semantics remain supplied by Doppler. */
6
+ export const partitionIdentityKeys = Object.freeze(['modelId', 'modelIdentity', 'planId', 'threadId', 'attemptId', 'participantA', 'participantB']);
7
+ export const partitionActions = Object.freeze(['mesh.execute_partition_a', 'mesh.execute_partition_b',
8
+ 'mesh.transfer_intermediate_activation', 'mesh.transfer_token_context', 'mesh.transfer_partition_output']);
9
+ export const assertPartition = (value, message) => { if (!value) throw new Error(message); };
10
+ /** A declared failure whose code and message cross every channel hop together; an uncoded error
11
+ * becomes EXECUTION_FAILED at the next hop. `detail` adds non-message fields, such as a bound. */
12
+ export function partitionFailure(code, detail = {}) {
13
+ assertPartition(Object.hasOwn(failureMessages, code), 'Undeclared partition failure code');
14
+ return Object.assign(new Error(failureMessages[code]), detail, { code });
15
+ }
16
+ /** Throw CAPACITY_UNAVAILABLE unless `value` holds: an exhausted or closed admission slot, never a defect. */
17
+ export const assertCapacity = value => { if (!value) throw partitionFailure('CAPACITY_UNAVAILABLE'); };
18
+ export const samePartitionIdentity = (actual, expected) => actual && [...partitionIdentityKeys, 'requesterId', 'placementGeneration'].every(key => actual[key] === expected[key]);
19
+ /** Ask the injected provider for tensor layout; retain the accepted v1 adapter. */
20
+ export function partitionActivationContract(runtime, plan) {
21
+ if (runtime.getPartitionActivationContract) {
22
+ assertPartition(typeof runtime.validateActivationFrameForPlan === 'function', 'Doppler activation validator required');
23
+ return Object.freeze({ ...runtime.getPartitionActivationContract(plan) });
24
+ }
25
+ assertPartition(plan?.schema === runtime.LAYER_PARTITION_SCHEMA
26
+ && plan.activationPrecision === undefined && plan.activationComponents === undefined
27
+ && plan.activationElementStrideBytes === undefined, 'Unsupported Doppler partition activation contract');
28
+ return Object.freeze({ schema: runtime.ACTIVATION_TENSOR_SCHEMA, dtype: plan.activationDtype,
29
+ hiddenSize: plan.hiddenSize, precision: plan.activationDtype, components: 1,
30
+ elementStrideBytes: plan.activationDtype === 'f16' ? 2 : 4 });
31
+ }
32
+ export function matchingPartitionActivation(value, contract, frame = false) {
33
+ if (frame && contract.components === 1) {
34
+ return value?.dtype === contract.dtype && ['precision', 'components', 'elementStrideBytes']
35
+ .every(key => value[key] === undefined);
36
+ }
37
+ return value?.dtype === contract.dtype && ['precision', 'components', 'elementStrideBytes']
38
+ .every(key => value[key] === contract[key] || contract.components === 1 && value[key] === undefined);
39
+ }
40
+ export function validatePartitionIdentity(identity) {
41
+ assertPartition(identity && typeof identity === 'object' && !Array.isArray(identity) && partitionIdentityKeys.every(key => typeof identity[key] === 'string'
42
+ && identity[key].length > 0 && identity[key].length <= 256)
43
+ && /^sha256:[a-f0-9]{64}$/.test(identity.modelIdentity)
44
+ && identity.participantA !== identity.participantB, 'Invalid partition identity');
45
+ if (identity.requesterId !== undefined || identity.placementGeneration !== undefined) {
46
+ assertPartition(typeof identity.requesterId === 'string' && identity.requesterId.length > 0 && identity.requesterId.length <= 256
47
+ && Number.isSafeInteger(identity.placementGeneration) && identity.placementGeneration >= 0, 'Invalid requester placement binding');
48
+ }
49
+ }
50
+ /** Snapshot the complete validated identity; optional placement fields travel as a pair. */
51
+ export function canonicalPartitionBinding(identity) {
52
+ validatePartitionIdentity(identity);
53
+ const keys = identity.requesterId === undefined ? partitionIdentityKeys
54
+ : [...partitionIdentityKeys, 'requesterId', 'placementGeneration'];
55
+ return Object.freeze(Object.fromEntries(keys.map(key => [key, identity[key]])));
56
+ }
57
+ const assertFiniteJson = value => {
58
+ if (Array.isArray(value)) return value.forEach(item => assertFiniteJson(item));
59
+ if (value && typeof value === 'object') return Object.keys(value).forEach(key => assertFiniteJson(value[key]));
60
+ assertPartition(value === null || ['string', 'boolean'].includes(typeof value)
61
+ || typeof value === 'number' && Number.isFinite(value), 'Partition metadata must be finite JSON');
62
+ };
63
+ /** The library's canonical JSON (artifacts/canonical-json.js) of finite JSON metadata. */
64
+ export function canonicalPartitionJson(value) {
65
+ assertFiniteJson(value);
66
+ return canonicalize(value);
67
+ }
68
+ export async function partitionFingerprint(metadata, bytes = new Uint8Array()) {
69
+ const header = new TextEncoder().encode(canonicalPartitionJson(metadata));
70
+ const input = new Uint8Array(4 + header.length + bytes.length);
71
+ new DataView(input.buffer).setUint32(0, header.length);
72
+ input.set(header, 4); input.set(bytes, 4 + header.length);
73
+ return sha256Hex(input);
74
+ }
75
+
76
+ export const PARTITION_ROUTE_SCHEMA = 'reploid.partition-route/v1';
77
+ export const ROUTE_ATTEMPT_SCHEMA = 'reploid.partition-attempt/v2';
78
+ const identifier = value => typeof value === 'string' && value.length > 0 && value.length <= 256;
79
+ const exactFields = (value, keys) => value && typeof value === 'object' && !Array.isArray(value)
80
+ && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
81
+ const freezeRouteJson = value => {
82
+ if (value && typeof value === 'object') { Object.values(value).forEach(freezeRouteJson); Object.freeze(value); }
83
+ return value;
84
+ };
85
+ /** Structural checks also apply when comparing a received identity without a runtime. */
86
+ export function canonicalPartitionRoute(route) {
87
+ assertPartition(exactFields(route, ['schema', 'modelId', 'modelIdentity', 'planId', 'stages', 'activation'])
88
+ && route.schema === PARTITION_ROUTE_SCHEMA && identifier(route.modelId) && /^sha256:[a-f0-9]{64}$/.test(route.planId)
89
+ && /^sha256:[a-f0-9]{64}$/.test(route.modelIdentity)
90
+ && Array.isArray(route.stages) && route.stages.length >= 2, 'Invalid partition route');
91
+ const participants = new Set(), transports = new Set();
92
+ route.stages.forEach((stage, index) => {
93
+ assertPartition(exactFields(stage, ['index', 'participantId', 'transportId', 'residentId', 'executionId'])
94
+ && stage.index === index && ['participantId', 'transportId', 'residentId', 'executionId'].every(key => identifier(stage[key]))
95
+ && !participants.has(stage.participantId) && !transports.has(stage.transportId), 'Invalid or duplicate route stage');
96
+ participants.add(stage.participantId); transports.add(stage.transportId);
97
+ });
98
+ const layout = route.activation;
99
+ assertPartition(exactFields(layout, ['schema', 'dtype', 'hiddenSize', 'precision', 'components', 'elementStrideBytes'])
100
+ && layout.schema === (layout.precision === 'two-f32' ? 'doppler.activation-tensor/v2' : 'doppler.activation-tensor/v1')
101
+ && Number.isSafeInteger(layout.hiddenSize) && layout.hiddenSize > 0
102
+ && (layout.precision === 'two-f32' ? layout.dtype === 'f32' && layout.components === 2 && layout.elementStrideBytes === 8
103
+ : ['f32', 'f16'].includes(layout.dtype) && layout.precision === layout.dtype && layout.components === 1
104
+ && layout.elementStrideBytes === (layout.dtype === 'f16' ? 2 : 4)), 'Invalid route activation layout');
105
+ return freezeRouteJson(structuredClone(route));
106
+ }
107
+ /** Provider hashing validates its plan geometry; host admission separately binds the selected manifest. */
108
+ export async function validatePartitionRoute(runtime, plan, route) {
109
+ const snapshot = canonicalPartitionRoute(route), planSnapshot = structuredClone(plan);
110
+ assertPartition(typeof runtime?.hashLayerPartitionPlan === 'function'
111
+ && typeof runtime.getPartitionActivationContract === 'function', 'Ordered Doppler contracts required');
112
+ assertPartition(planSnapshot?.schema === 'doppler.layer-partition-contract/v3'
113
+ && planSnapshot.modelId === snapshot.modelId && planSnapshot.stageCount === snapshot.stages.length
114
+ && Array.isArray(planSnapshot.partitions) && planSnapshot.partitions.length === snapshot.stages.length
115
+ && planSnapshot.partitions.every((part, index) => part.index === index), 'Route plan stage mismatch');
116
+ const layout = partitionActivationContract(runtime, planSnapshot);
117
+ assertPartition(canonicalPartitionJson(layout) === canonicalPartitionJson(snapshot.activation), 'Route activation mismatch');
118
+ assertPartition(await runtime.hashLayerPartitionPlan(planSnapshot) === snapshot.planId, 'Route plan identity mismatch');
119
+ return snapshot;
120
+ }
121
+ export function canonicalRouteAttemptBinding(identity) {
122
+ assertPartition(exactFields(identity, ['schema', 'threadId', 'attemptId', 'requesterId', 'placementGeneration', 'route'])
123
+ && identity.schema === ROUTE_ATTEMPT_SCHEMA && ['threadId', 'attemptId', 'requesterId'].every(key => identifier(identity[key]))
124
+ && Number.isSafeInteger(identity.placementGeneration) && identity.placementGeneration >= 0, 'Invalid route attempt identity');
125
+ return Object.freeze({ schema: identity.schema, threadId: identity.threadId, attemptId: identity.attemptId,
126
+ requesterId: identity.requesterId, placementGeneration: identity.placementGeneration, route: canonicalPartitionRoute(identity.route) });
127
+ }
128
+ export function sameRouteAttemptIdentity(actual, expected) {
129
+ try { return canonicalPartitionJson(canonicalRouteAttemptBinding(actual)) === canonicalPartitionJson(canonicalRouteAttemptBinding(expected)); }
130
+ catch { return false; }
131
+ }
132
+
133
+ /** A ready stage is inventory, not a complete chat path. This checks advertised
134
+ * binding only; entry refresh, grants and reservations remain authoritative. */
135
+ export function isOrderedPartitionEntryModel(model, peer) {
136
+ try {
137
+ const selection = model?.partition;
138
+ if (peer.description?.schema !== 'reploid.partition-capability/v2' || model.index !== 0
139
+ || selection?.schema !== 'reploid.partition-selection/v2') return false;
140
+ const route = canonicalPartitionRoute(selection.route), first = route.stages[0];
141
+ return model.id === route.modelId && model.identity === route.modelIdentity
142
+ && model.planId === route.planId && selection.planId === route.planId
143
+ && selection.coordinatorId === first.participantId && first.participantId === peer.participantId
144
+ && first.transportId === peer.transportId && first.residentId === model.residentId
145
+ && first.executionId === model.executionId
146
+ && canonicalPartitionJson(route.activation) === canonicalPartitionJson(model.activation);
147
+ } catch { return false; }
148
+ }
149
+
150
+ /** One observed inventory version, bounded by host policy. These hints never authorize execution. */
151
+ export function validateOrderedPartitionCapability(description, participantId, maxOffersPerPeer) {
152
+ assertPartition(Number.isSafeInteger(maxOffersPerPeer) && maxOffersPerPeer > 0, 'Explicit stage offer limit required');
153
+ assertPartition(description?.schema === 'reploid.partition-capability/v2' && description.participantId === participantId
154
+ && ['idle', 'waiting', 'loading', 'ready', 'stopping', 'failed'].includes(description.phase)
155
+ && Array.isArray(description.models) && description.models.length <= maxOffersPerPeer,
156
+ 'Ordered partition capability identity or inventory mismatch');
157
+ const capacity = description.capacity;
158
+ assertPartition(capacity && typeof capacity.closed === 'boolean' && Number.isSafeInteger(capacity.active) && capacity.active >= 0
159
+ && Number.isSafeInteger(capacity.availableSlots) && capacity.availableSlots >= 0, 'Ordered partition capacity required');
160
+ for (const model of description.models) {
161
+ assertPartition(model && typeof model.id === 'string' && model.id.length > 0 && model.id.length <= 256
162
+ && /^sha256:[a-f0-9]{64}$/.test(model.identity) && Number.isSafeInteger(model.index) && model.index >= 0
163
+ && ['planId', 'residentId', 'executionId'].every(key => typeof model[key] === 'string' && model[key].length > 0 && model[key].length <= 256)
164
+ && ['ready', 'unavailable'].includes(model.availability), 'Invalid ordered partition offer');
165
+ }
166
+ }
167
+
168
+ /** Exact adapter selection travels in the plan digest, separate from the base manifest. */
169
+ export function partitionAdapterIdentities(model) {
170
+ const adapters = model?.adapters ?? [];
171
+ assertPartition(Array.isArray(adapters) && adapters.length <= 1 && adapters.every(adapter =>
172
+ /^sha256:[a-f0-9]{64}$/.test(adapter.identity) && adapter.baseModelIdentity === model.identity),
173
+ 'Partition adapter must target the exact base artifact');
174
+ return adapters.map(adapter => adapter.identity);
175
+ }
176
+ export function samePartitionAdapters(left, right) {
177
+ return canonicalPartitionJson(partitionAdapterIdentities(left)) === canonicalPartitionJson(partitionAdapterIdentities(right));
178
+ }
179
+ export function selectPartitionContribution(model, adapters = model.adapters ?? []) {
180
+ const selection = { ...model, adapters };
181
+ partitionAdapterIdentities(selection);
182
+ selection.adapters = adapters.map(adapter => {
183
+ const pinned = model.availableAdapters?.find(entry => entry.identity === adapter.identity
184
+ && entry.baseModelIdentity === model.identity);
185
+ assertPartition(pinned, 'Partition adapter is absent from the authorized catalog');
186
+ return structuredClone(pinned);
187
+ });
188
+ if (selection.partitionPlan && adapters.length) selection.partitionPlan = {
189
+ ...selection.partitionPlan, adapterIdentities: partitionAdapterIdentities(selection)
190
+ };
191
+ return structuredClone(selection);
192
+ }
@@ -0,0 +1,96 @@
1
+ import failureMessages from '../../transport/partition-errors.json' with { type: 'json' };
2
+ import { assertPartition as assert } from './partition-contract.js';
3
+
4
+ const positive = value => Number.isSafeInteger(value) && value > 0;
5
+ const unavailable = () => Object.assign(new Error(failureMessages.CAPACITY_UNAVAILABLE), { code: 'CAPACITY_UNAVAILABLE' });
6
+
7
+ /** One admission budget per executing device (Launch 3, opt-in). It charges declared application
8
+ * allocations, not measured physical memory: each resident's weights once, plus `bytes` and one
9
+ * slot per admitted attempt, across every resident and route on the device. Callers release an
10
+ * attempt only after its cleanup settles, and a resident only after its provider closed.
11
+ *
12
+ * With `queue`, an attempt that does not fit waits instead of failing. Waiting is bounded in
13
+ * length, per requester and in time; each bound fails with CAPACITY_UNAVAILABLE. Grants go to the
14
+ * route owner (coordinator) with the fewest attempts admitted on this device, then the one served
15
+ * least recently, then first come, first served, with no overtaking by smaller requests. */
16
+ export function createDeviceAdmission({ maxBytes, maxConcurrentAttempts, queue = null }) {
17
+ assert(positive(maxBytes) && positive(maxConcurrentAttempts), 'Explicit device admission budget required');
18
+ assert(queue === null || ['maxQueued', 'maxQueuedPerRequester', 'maxWaitMs'].every(key => positive(queue[key])),
19
+ 'Explicit bounded admission queue required');
20
+ const residents = new Map(), attempts = new Map(), waiting = [], served = new Map();
21
+ let sequence = 0, grants = 0;
22
+ const attemptRows = () => [...attempts.values()].flatMap(rows => [...rows.values()]);
23
+ const residentBytes = () => [...residents.values()].reduce((total, bytes) => total + bytes, 0);
24
+ const attemptBytes = () => attemptRows().reduce((total, row) => total + row.bytes, 0);
25
+ const free = () => maxBytes - residentBytes() - attemptBytes();
26
+ const fits = bytes => attemptRows().length < maxConcurrentAttempts && bytes <= free();
27
+ const admittedFor = routeKey => attemptRows().filter(row => row.routeKey === routeKey).length;
28
+ function charge(entry) {
29
+ attempts.get(entry.owner).set(entry.attemptId, { bytes: entry.bytes, routeKey: entry.routeKey });
30
+ served.set(entry.routeKey, grants++);
31
+ // Forget idle route owners so this record stays bounded; a returning owner counts as least recent.
32
+ if (served.size > maxConcurrentAttempts + (queue?.maxQueued ?? 0)) for (const key of served.keys()) {
33
+ if (!admittedFor(key) && !waiting.some(row => row.routeKey === key)) served.delete(key);
34
+ }
35
+ }
36
+ function settle(entry, error) {
37
+ const at = waiting.indexOf(entry);
38
+ if (at < 0) return;
39
+ waiting.splice(at, 1);
40
+ clearTimeout(entry.timer); entry.signal?.removeEventListener('abort', entry.onAbort);
41
+ if (error) entry.reject(error); else { charge(entry); entry.resolve(); }
42
+ }
43
+ function pump() {
44
+ while (waiting.length) {
45
+ const next = waiting.reduce((best, entry) => {
46
+ const order = admittedFor(entry.routeKey) - admittedFor(best.routeKey)
47
+ || (served.get(entry.routeKey) ?? -1) - (served.get(best.routeKey) ?? -1) || entry.sequence - best.sequence;
48
+ return order < 0 ? entry : best;
49
+ });
50
+ if (!fits(next.bytes)) return;
51
+ settle(next, null);
52
+ }
53
+ }
54
+ return Object.freeze({
55
+ /** Charge resident weights once; a resident that does not fit is never offered. */
56
+ admitResident(owner, bytes) {
57
+ assert(owner && typeof owner === 'object' && positive(bytes), 'Device resident charge requires an owner and weight bytes');
58
+ if (residents.has(owner)) return;
59
+ if (bytes > free()) throw Object.assign(new Error('Device allocation budget cannot hold this resident'), { code: 'RESOURCE_EXHAUSTED' });
60
+ residents.set(owner, bytes); attempts.set(owner, new Map());
61
+ },
62
+ /** Admit one attempt on one resident; repeated admission of the same attempt charges once. */
63
+ async admit({ owner, attemptId, bytes, requesterId = null, routeKey = null, signal }) {
64
+ assert(residents.has(owner) && typeof attemptId === 'string' && attemptId.length > 0 && positive(bytes),
65
+ 'Device attempt admission requires an admitted resident, attempt and allocation');
66
+ if (attempts.get(owner).has(attemptId)) return;
67
+ const queued = waiting.find(entry => entry.owner === owner && entry.attemptId === attemptId);
68
+ if (queued) return queued.promise;
69
+ signal?.throwIfAborted();
70
+ if (!waiting.length && fits(bytes)) { charge({ owner, attemptId, bytes, routeKey }); return; }
71
+ if (!queue || waiting.length >= queue.maxQueued
72
+ || waiting.filter(entry => entry.requesterId === requesterId).length >= queue.maxQueuedPerRequester) throw unavailable();
73
+ const { promise, resolve, reject } = Promise.withResolvers();
74
+ const entry = { owner, attemptId, bytes, requesterId, routeKey, sequence: sequence++, promise, resolve, reject, signal };
75
+ entry.timer = setTimeout(() => settle(entry, unavailable()), queue.maxWaitMs);
76
+ entry.onAbort = () => settle(entry, signal.reason);
77
+ signal?.addEventListener('abort', entry.onAbort, { once: true });
78
+ waiting.push(entry); pump();
79
+ return promise;
80
+ },
81
+ release(owner, attemptId) { attempts.get(owner)?.delete(attemptId); pump(); },
82
+ releaseResident(owner) {
83
+ for (const entry of waiting.filter(row => row.owner === owner)) settle(entry, unavailable());
84
+ residents.delete(owner); attempts.delete(owner); pump();
85
+ },
86
+ /** Attempts of `bytes` each that could still be admitted without waiting. */
87
+ available(bytes) {
88
+ assert(positive(bytes), 'Device availability requires an attempt allocation');
89
+ if (waiting.length) return 0;
90
+ return Math.max(0, Math.min(maxConcurrentAttempts - attemptRows().length, Math.floor(free() / bytes)));
91
+ },
92
+ getState: () => ({ maxBytes, maxConcurrentAttempts, residents: residents.size, residentBytes: residentBytes(),
93
+ attemptBytes: attemptBytes(), active: attemptRows().length, availableBytes: free(),
94
+ ...(queue ? { queued: waiting.length, maxQueued: queue.maxQueued } : {}) })
95
+ });
96
+ }
@@ -0,0 +1,96 @@
1
+ import { createPartitionDataChannel } from '../../transport/partition-data-channel.js';
2
+ import { assertPartition as assert, validateOrderedPartitionCapability, isOrderedPartitionEntryModel } from './partition-contract.js';
3
+
4
+ /** Owns bounded capability observation, never placement, loading or permission. */
5
+ export function createPartitionDiscovery({ identity, catalog, policy, peers, createNetwork,
6
+ describe, onChange = () => {}, now = () => Date.now() }) {
7
+ const connections = new Map(), lifetime = new AbortController();
8
+ let refreshing = null, closed = false, closing = null, networkSettled = false;
9
+ const snapshot = () => [...connections.entries()].map(([transportId, peer]) => {
10
+ const expiresAt = peer.observedAt + policy.inputLimits.descriptorTtlMs;
11
+ return { transportId, participantId: peer.participantId, observedAt: peer.observedAt,
12
+ expiresAt, available: !closed && !!peer.description && now() < expiresAt,
13
+ reason: closed ? 'closed' : !peer.description ? peer.reason || 'unobserved'
14
+ : now() >= expiresAt ? 'expired' : 'observed', description: structuredClone(peer.description) };
15
+ });
16
+ const notify = () => onChange(snapshot());
17
+ const network = createNetwork({ label: 'reploid-partitions-control',
18
+ maxPeers: policy.maxPeers, timeoutMs: policy.connectTimeoutMs,
19
+ createEndpoint: ({ channel, remoteParticipantId }) => {
20
+ const endpoint = createPartitionDataChannel({ channel, localParticipantId: identity.peerId,
21
+ remoteParticipantId, limits: policy.controlChannel,
22
+ authorize: async ({ metadata, byteLength }) => metadata.operation === 'describe' && byteLength === 0,
23
+ serve: async () => structuredClone(describe()) });
24
+ const peer = { endpoint, participantId: remoteParticipantId, description: null, observedAt: 0, revision: 0,
25
+ close: () => endpoint.close() };
26
+ channel.addEventListener('close', () => {
27
+ for (const [id, value] of connections) if (value === peer) connections.delete(id);
28
+ notify();
29
+ }, { once: true });
30
+ return peer;
31
+ },
32
+ onPeer(transportId, peer) { if (!closed) connections.set(transportId, peer); }
33
+ });
34
+ return Object.freeze({
35
+ getSnapshot: snapshot,
36
+ getModels: () => snapshot().filter(peer => peer.available).flatMap(peer => peer.description.models
37
+ .filter(model => peer.description.schema !== 'reploid.partition-capability/v2'
38
+ || isOrderedPartitionEntryModel(model, peer))),
39
+ invalidateModel(transportId, model) {
40
+ const peer = connections.get(transportId);
41
+ if (!peer || closed) return;
42
+ peer.revision++;
43
+ if (peer.description) peer.description.models = peer.description.models.filter(candidate =>
44
+ candidate.id !== model.id || candidate.identity !== model.identity);
45
+ notify();
46
+ },
47
+ refresh() {
48
+ if (closed) return Promise.resolve(snapshot());
49
+ if (refreshing) return refreshing;
50
+ refreshing = (async () => {
51
+ for (const peer of peers().slice(0, policy.maxPeers)) {
52
+ if (closed) break;
53
+ if (!connections.has(peer.id) && peer.localTransportId < peer.id) {
54
+ try { await network.connect(peer.id); } catch { /* The next bounded refresh may retry. */ }
55
+ }
56
+ }
57
+ await Promise.allSettled([...connections.values()].map(async peer => {
58
+ const revision = peer.revision;
59
+ try {
60
+ const description = await peer.endpoint.request({ operation: 'describe' }, new Uint8Array(), { signal: lifetime.signal });
61
+ if (description?.schema === 'reploid.partition-capability/v2') {
62
+ validateOrderedPartitionCapability(description, peer.participantId, policy.maxStageOffersPerPeer);
63
+ } else assert(description.participantId === peer.participantId && Array.isArray(description.models)
64
+ && (description.eligibleIndexes === undefined || Array.isArray(description.eligibleIndexes)
65
+ && description.eligibleIndexes.length > 0 && description.eligibleIndexes.length <= 2
66
+ && description.eligibleIndexes.every(index => index === 0 || index === 1))
67
+ && description.models.length <= catalog.length && [null, 0, 1].includes(description.index)
68
+ && (!description.capacity || Number.isSafeInteger(description.capacity.availableSlots)
69
+ && description.capacity.availableSlots >= 0 && Number.isSafeInteger(description.capacity.active)
70
+ && description.capacity.active >= 0 && typeof description.capacity.closed === 'boolean')
71
+ && ['idle', 'waiting', 'loading', 'ready', 'stopping', 'failed'].includes(description.phase),
72
+ 'Partition capability identity or inventory mismatch');
73
+ // A describe already in flight cannot restore a hint rejected by a
74
+ // newer authoritative entry check. The next observation may restore it.
75
+ if (!closed && revision === peer.revision) { peer.description = structuredClone(description); peer.observedAt = now(); peer.reason = null; }
76
+ } catch { if (revision === peer.revision) { peer.description = null; peer.reason = 'description-unavailable'; } }
77
+ }));
78
+ notify(); return snapshot();
79
+ })().finally(() => { refreshing = null; });
80
+ return refreshing;
81
+ },
82
+ close() {
83
+ if (closing) return closing;
84
+ closed = true; lifetime.abort(new Error('Partition discovery closed'));
85
+ closing = (async () => {
86
+ const results = await Promise.allSettled([Promise.resolve().then(async () => {
87
+ if (!networkSettled) { await network.close(); networkSettled = true; }
88
+ }), refreshing]);
89
+ connections.clear();
90
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason);
91
+ if (failures.length) throw new AggregateError(failures, 'Partition discovery settlement failed');
92
+ })().catch(error => { closing = null; throw error; });
93
+ return closing;
94
+ }
95
+ });
96
+ }
@@ -0,0 +1,183 @@
1
+ import { assertPartition as assert, canonicalPartitionJson, samePartitionIdentity,
2
+ validatePartitionIdentity, canonicalRouteAttemptBinding } from './partition-contract.js';
3
+ import { ORDERED_PUBLICATION_ATTEMPT_SCHEMA, validateOrderedPublicationContext } from './partition-ordered-publication.js';
4
+
5
+ export const ENTRY_PUBLICATION_SCHEMA = 'reploid.partition-publication/v1';
6
+ const same = (a, b) => canonicalPartitionJson(a) === canonicalPartitionJson(b);
7
+ const epoch = value => typeof value === 'string' && /^[a-f0-9]{32}$/.test(value);
8
+ const digest = value => typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value);
9
+ const methods = ['head', 'admit', 'publish', 'publish-batch', 'cancel'];
10
+ const orderedInput = request => request?.schema === 'reploid.partition-input/v3';
11
+ const orderedKeys = ['meshId', 'requesterId', 'coordinatorId', 'modelId', 'modelIdentity', 'planId', 'threadId',
12
+ 'attemptId', 'placementGeneration'];
13
+ /** The owner interface a coordinator calls; the requester's scope answers it. */
14
+ export function entryPublicationPort(call) {
15
+ assert(typeof call === 'function', 'Publication call port required');
16
+ return Object.freeze({ requiresCommitEvidence: true,
17
+ reconcile: () => call('head', {}), admitAttempt: body => call('admit', body),
18
+ publish: (value, { evidence } = {}) => call('publish', { publication: value, evidence }),
19
+ // Consecutive records committed by the requester in one transaction and projected once.
20
+ publishBatch: async (values, { evidence } = {}) => (await call('publish-batch', { publications: values, evidence }))?.receipts,
21
+ cancel: (attemptId, coordinatorEpoch) => call('cancel', { attemptId, coordinatorEpoch }) });
22
+ }
23
+ export function validateEntryPublicationContext(value, request) {
24
+ assert(value?.schema === 'reploid.partition-publication-context/v1' && epoch(value.publisherEpoch)
25
+ && epoch(value.coordinatorEpoch) && digest(value.routeId) && digest(value.generation?.generationId)
26
+ && digest(value.generation.executionIdentity) && value.generation.policy
27
+ && Number.isSafeInteger(value.generation.policy.maxGeneratedTokens) && value.generation.policy.maxGeneratedTokens >= 0
28
+ && Number.isSafeInteger(value.vocabSize) && value.vocabSize > 0
29
+ && Number.isSafeInteger(value.deadlineAt) && value.deadlineAt > 0
30
+ && Array.isArray(value.stages) && value.stages.length === 2
31
+ && value.stages.every((stage, i) => stage.participantId === [request.participantA, request.participantB][i]
32
+ && stage.partitionIndex === i && digest(stage.stageId) && epoch(stage.participantEpoch)
33
+ && epoch(stage.deviceEpoch) && typeof stage.residentId === 'string' && stage.residentId.length > 0
34
+ && digest(stage.executionId)), 'Invalid entry publication context');
35
+ return structuredClone(value);
36
+ }
37
+ /** A publication request travels on the already authenticated requester/A channel.
38
+ * It never conveys another conversation's history, a tensor or a raw text delta. */
39
+ export function entryPublicationEnvelope(request, method, body) {
40
+ assert(methods.includes(method), 'Unknown publication method');
41
+ if (orderedInput(request)) {
42
+ canonicalRouteAttemptBinding({ schema: 'reploid.partition-attempt/v2', route: request.route, threadId: request.threadId,
43
+ attemptId: request.attemptId, requesterId: request.requesterId, placementGeneration: request.placementGeneration });
44
+ return { schema: ENTRY_PUBLICATION_SCHEMA, operation: 'publication', method,
45
+ ...Object.fromEntries(orderedKeys.map(key => [key, request[key]])), route: structuredClone(request.route),
46
+ publication: validateOrderedPublicationContext(request.publication), body: structuredClone(body) };
47
+ }
48
+ validatePartitionIdentity(request);
49
+ const { meshId, requesterId, participantA, participantB, modelId, modelIdentity, planId,
50
+ threadId, attemptId, placementGeneration, canonical } = request;
51
+ return { schema: ENTRY_PUBLICATION_SCHEMA, operation: 'publication', method, meshId, requesterId,
52
+ participantA, participantB, modelId, modelIdentity, planId, threadId, attemptId, placementGeneration,
53
+ canonical: structuredClone(canonical), body: structuredClone(body) };
54
+ }
55
+ export function matchingEntryPublication(message, request) {
56
+ if (orderedInput(request)) {
57
+ try {
58
+ return request.publication !== undefined && message?.schema === ENTRY_PUBLICATION_SCHEMA && message.operation === 'publication'
59
+ && methods.includes(message.method) && orderedKeys.every(key => message[key] === request[key])
60
+ && message.canonical === undefined && message.participantA === undefined && message.participantB === undefined
61
+ && same(message.route, request.route) && same(message.publication, request.publication);
62
+ } catch { return false; }
63
+ }
64
+ return request?.schema === 'reploid.partition-input/v2' && message.schema === ENTRY_PUBLICATION_SCHEMA
65
+ && message.operation === 'publication' && methods.includes(message.method)
66
+ && message.meshId === request.meshId && samePartitionIdentity(message, request)
67
+ && same(message.canonical, request.canonical);
68
+ }
69
+ /** This port belongs to the requester, not the input executor. The transaction
70
+ * owner validates cursors/signatures and fences uncertain storage outcomes. */
71
+ export function createEntryPublicationScope({ request, owner, authorize, onPublished, maxControlBytes, maxOutputCharacters }) {
72
+ assert(owner?.requiresCommitEvidence === true && ['reconcile', 'admitAttempt', 'publish', 'cancel']
73
+ .every(name => typeof owner[name] === 'function') && typeof authorize === 'function'
74
+ && typeof onPublished === 'function', 'Requester durable publication and projection ports required');
75
+ assert(Number.isSafeInteger(maxControlBytes) && maxControlBytes > 0
76
+ && Number.isSafeInteger(maxOutputCharacters) && maxOutputCharacters > 0, 'Publication channel bounds required');
77
+ const copy = value => {
78
+ const json = canonicalPartitionJson(value);
79
+ assert(new TextEncoder().encode(json).byteLength <= maxControlBytes, 'Publication control budget exhausted');
80
+ return JSON.parse(json);
81
+ };
82
+ const selected = copy(request), ordered = orderedInput(selected);
83
+ const context = ordered ? validateOrderedPublicationContext(selected.publication) : validateEntryPublicationContext(selected.canonical, selected);
84
+ let closed = false, admitted = null, admittedPolicy = null, content = '', count = 0, terminal = false;
85
+ const projections = new Map(), pending = new Set();
86
+ let cancellation = null;
87
+ const permit = async method => {
88
+ assert(!closed && await authorize(method) === true && !closed, 'Publication delivery authority closed or revoked');
89
+ };
90
+ // An ordered coordinator chooses its epoch at admission; canonical routes advertise it.
91
+ const coordinatorEpoch = () => ordered ? admitted?.coordinatorEpoch : context.coordinatorEpoch;
92
+ const sequenceLimit = () => ordered ? admittedPolicy.maxTokens : Math.max(1, context.generation.policy.maxGeneratedTokens);
93
+ const identityMatches = identity => ordered
94
+ ? identity?.schema === ORDERED_PUBLICATION_ATTEMPT_SCHEMA && identity.attemptId === selected.attemptId
95
+ && identity.threadId === selected.threadId && identity.requesterId === selected.requesterId
96
+ && identity.placementGeneration === selected.placementGeneration && identity.publisherEpoch === context.publisherEpoch
97
+ && epoch(identity.coordinatorEpoch) && same(identity.route, selected.route)
98
+ : identity?.attemptId === selected.attemptId
99
+ && identity.requesterId === selected.requesterId && identity.publisherEpoch === context.publisherEpoch
100
+ && identity.coordinatorEpoch === context.coordinatorEpoch && identity.routeId === context.routeId
101
+ && identity.generationId === context.generation.generationId && identity.routeKind === 'two-stage-layer'
102
+ && same(identity.stages, context.stages);
103
+ const admissionMatches = value => identityMatches(value.identity) && (ordered
104
+ ? value.executionIdentity === value.identity.generationDigest && Number.isSafeInteger(value.vocabSize) && value.vocabSize > 0
105
+ && Number.isSafeInteger(value.generationPolicy?.maxTokens) && value.generationPolicy.maxTokens > 0
106
+ : same(value.generationPolicy, context.generation.policy)
107
+ && value.executionIdentity === context.generation.executionIdentity && value.vocabSize === context.vocabSize);
108
+ const call = async (method, body) => {
109
+ assert(methods.includes(method), 'Unknown publication method');
110
+ const value = copy(body); // Snapshot before any asynchronous authorization or storage.
111
+ await permit(method);
112
+ if (method === 'head') {
113
+ assert(same(value, {}), 'Unexpected publication head input');
114
+ const state = await owner.reconcile(); await permit(method);
115
+ return copy({ head: state.head, attempts: state.attempts.filter(item => item.identity.attemptId === selected.attemptId
116
+ && (ordered || item.identity.coordinatorEpoch === context.coordinatorEpoch)).map(item => ({ identity: item.identity })) });
117
+ }
118
+ if (method === 'admit') {
119
+ assert(admissionMatches(value) && value.deadlineAt === context.deadlineAt && (!admitted || same(admitted, value.identity)),
120
+ 'Publication admission differs from approved context');
121
+ const result = await owner.admitAttempt(value); admitted = value.identity; admittedPolicy = value.generationPolicy;
122
+ await permit(method); return copy(result);
123
+ }
124
+ assert(admitted, 'Publication attempt has not been admitted');
125
+ if (method === 'cancel') {
126
+ assert(same(value, { attemptId: selected.attemptId, coordinatorEpoch: coordinatorEpoch() }),
127
+ 'Cancellation differs from admitted attempt');
128
+ const result = await owner.cancel(value.attemptId, value.coordinatorEpoch);
129
+ await permit(method); return copy(result);
130
+ }
131
+ const batch = method === 'publish-batch';
132
+ const publications = batch ? value.publications : [value.publication], evidence = batch ? value.evidence : [value.evidence];
133
+ assert(Array.isArray(publications) && publications.length > 0 && Array.isArray(evidence) && evidence.length === publications.length
134
+ && publications.every((publication, index) => publication?.attemptId === selected.attemptId && publication.coordinatorEpoch === coordinatorEpoch()
135
+ && Number.isSafeInteger(publication.sequence) && publication.sequence >= 0
136
+ && publication.sequence < sequenceLimit() && Array.isArray(evidence[index])), 'Publication differs from admitted attempt');
137
+ const receipts = copy(batch ? await owner.publishBatch(publications, { evidence })
138
+ : [await owner.publish(publications[0], { evidence: evidence[0] })]);
139
+ assert(Array.isArray(receipts) && receipts.length === publications.length && receipts.every((receipt, index) =>
140
+ receipt.conversationId === selected.threadId && receipt.writerEpoch === context.publisherEpoch
141
+ && receipt.attemptId === selected.attemptId && receipt.coordinatorEpoch === coordinatorEpoch()
142
+ && receipt.sequence === publications[index].sequence && receipt.publicationDigest === publications[index].publicationDigest),
143
+ 'Durable publication receipt identity mismatch');
144
+ await permit(method);
145
+ // Duplicate delivery reuses the same projection promise. A failed projection
146
+ // cannot be acknowledged or regenerated; the host reconciles saved history.
147
+ // A batch is projected once, from its last durable record.
148
+ const last = receipts.length - 1;
149
+ let projected = projections.get(receipts[last].publicationDigest);
150
+ if (!projected) {
151
+ const fresh = publications.filter(publication => publication.sequence >= count);
152
+ const text = fresh.map(publication => publication.delta?.text ?? '').join('');
153
+ assert(fresh.every(publication => typeof (publication.delta?.text ?? '') === 'string')
154
+ && content.length + text.length <= maxOutputCharacters, 'Published output exceeds entry budget');
155
+ assert(fresh.length > 0 && fresh[0].sequence === count, 'Published projection sequence mismatch');
156
+ const publication = publications[last];
157
+ projected = Promise.resolve().then(async () => {
158
+ await permit(method);
159
+ await onPublished(copy({ publication, receipt: receipts[last], evidence: evidence[last] }));
160
+ content += text; count += fresh.length;
161
+ terminal = publication.schema === 'doppler.admission-publication/v1' || publication.successor?.terminal === true;
162
+ });
163
+ projections.set(receipts[last].publicationDigest, projected);
164
+ }
165
+ await projected; await permit(method); return batch ? { receipts } : receipts[0];
166
+ };
167
+ return Object.freeze({
168
+ async close({ cancel = false } = {}) {
169
+ closed = true;
170
+ await Promise.allSettled([...pending]);
171
+ if (cancel && admitted) {
172
+ if (!cancellation) cancellation = Promise.resolve().then(() => owner.cancel(selected.attemptId, coordinatorEpoch()));
173
+ try { await cancellation; } catch (error) { cancellation = null; throw error; }
174
+ }
175
+ },
176
+ getProjection() { return { content, count, terminal }; },
177
+ call(method, body) {
178
+ const operation = call(method, body); pending.add(operation);
179
+ operation.then(() => pending.delete(operation), () => pending.delete(operation));
180
+ return operation;
181
+ },
182
+ });
183
+ }