reploid 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/apps/poolday/config/chat-files.json +27 -0
  2. package/apps/poolday/config/partition-generation.js +21 -0
  3. package/apps/poolday/config/partition-policy.json +74 -0
  4. package/apps/poolday/config/partition-releases.json +3 -0
  5. package/apps/poolday/config/work-profile.json +122 -0
  6. package/apps/poolday/host/chat-view.js +47 -0
  7. package/apps/poolday/host/earning/client.js +199 -0
  8. package/apps/poolday/host/earning/contribution.js +168 -0
  9. package/apps/poolday/host/partition-release.js +33 -0
  10. package/apps/poolday/host/work-partition-artifact-source.js +37 -0
  11. package/apps/poolday/host/work-partition-capsule.js +39 -0
  12. package/apps/poolday/host/work-partition-plan.js +167 -0
  13. package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
  14. package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
  15. package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
  16. package/apps/poolday/pool/pack-operation.js +161 -0
  17. package/apps/poolday/pool/pack-release-policy.js +44 -0
  18. package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
  19. package/apps/poolday/providers/work-adapter.js +67 -0
  20. package/apps/poolday/providers/work-device.js +33 -0
  21. package/apps/poolday/providers/work-generation-completion.js +55 -0
  22. package/apps/poolday/providers/work-provider.js +209 -0
  23. package/apps/poolday/providers/work-resident-provider.js +113 -0
  24. package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
  25. package/docs/fleet-service.md +214 -0
  26. package/docs/native-participant.md +542 -0
  27. package/native-delivery.json +1174 -0
  28. package/package.json +29 -4
  29. package/packages/reploid/src/artifacts/binary.js +25 -0
  30. package/packages/reploid/src/artifacts/canonical-json.js +23 -0
  31. package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
  32. package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
  33. package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
  34. package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
  35. package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
  36. package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
  37. package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
  38. package/packages/reploid/src/artifacts/identity.js +91 -0
  39. package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
  40. package/packages/reploid/src/artifacts/receipt.js +131 -0
  41. package/packages/reploid/src/artifacts/signatures.js +94 -0
  42. package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
  43. package/packages/reploid/src/cancellation.js +44 -0
  44. package/packages/reploid/src/config/defaults.json +110 -0
  45. package/packages/reploid/src/config/index.js +209 -0
  46. package/packages/reploid/src/config/request-overrides.json +9 -0
  47. package/packages/reploid/src/config/transport-defaults.json +7 -0
  48. package/packages/reploid/src/hashing.js +30 -0
  49. package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
  50. package/packages/reploid/src/measurement/index.js +5 -0
  51. package/packages/reploid/src/measurement/readiness-records.js +310 -0
  52. package/packages/reploid/src/mesh/computer-identity.js +50 -0
  53. package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
  54. package/packages/reploid/src/mesh/contribution.js +141 -0
  55. package/packages/reploid/src/mesh/generation-recovery.js +58 -0
  56. package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
  57. package/packages/reploid/src/mesh/gpu-signature.js +136 -0
  58. package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
  59. package/packages/reploid/src/mesh/index.js +66 -0
  60. package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
  61. package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
  62. package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
  63. package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
  64. package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
  65. package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
  66. package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
  67. package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
  68. package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
  69. package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
  70. package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
  71. package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
  72. package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
  73. package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
  74. package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
  75. package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
  76. package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
  77. package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
  78. package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
  79. package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
  80. package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
  81. package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
  82. package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
  83. package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
  84. package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
  85. package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
  86. package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
  87. package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
  88. package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
  89. package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
  90. package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
  91. package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
  92. package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
  93. package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
  94. package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
  95. package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
  96. package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
  97. package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
  98. package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
  99. package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
  100. package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
  101. package/packages/reploid/src/mesh/peer-generation.js +896 -0
  102. package/packages/reploid/src/mesh/peer-identity.js +87 -0
  103. package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
  104. package/packages/reploid/src/mesh/preparation-progress.js +46 -0
  105. package/packages/reploid/src/mesh/provider-hardware.js +11 -0
  106. package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
  107. package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
  108. package/packages/reploid/src/mesh/web-access-network.js +112 -0
  109. package/packages/reploid/src/mesh/web-access-policy.json +17 -0
  110. package/packages/reploid/src/retrieval/citations.js +156 -0
  111. package/packages/reploid/src/retrieval/claim-support.js +113 -0
  112. package/packages/reploid/src/retrieval/costs.js +101 -0
  113. package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
  114. package/packages/reploid/src/retrieval/index.js +20 -0
  115. package/packages/reploid/src/retrieval/lexical-support.js +27 -0
  116. package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
  117. package/packages/reploid/src/retrieval/page-fetch.json +10 -0
  118. package/packages/reploid/src/retrieval/page-text.js +182 -0
  119. package/packages/reploid/src/retrieval/passage.js +37 -0
  120. package/packages/reploid/src/retrieval/policy.json +14 -0
  121. package/packages/reploid/src/retrieval/retrieval.js +23 -0
  122. package/packages/reploid/src/retrieval/review-queue.js +76 -0
  123. package/packages/reploid/src/retrieval/sources.js +263 -0
  124. package/packages/reploid/src/retrieval/support-review.js +155 -0
  125. package/packages/reploid/src/retrieval/text.js +21 -0
  126. package/packages/reploid/src/rules/contribution.rules.json +8 -0
  127. package/packages/reploid/src/rules/routing.rules.json +24 -0
  128. package/packages/reploid/src/transport/assignment.js +571 -0
  129. package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
  130. package/packages/reploid/src/transport/index.js +11 -0
  131. package/packages/reploid/src/transport/introduction-admission.js +24 -0
  132. package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
  133. package/packages/reploid/src/transport/introduction-policy.json +12 -0
  134. package/packages/reploid/src/transport/introduction-records.js +90 -0
  135. package/packages/reploid/src/transport/introductions.js +209 -0
  136. package/packages/reploid/src/transport/partition-data-channel.js +421 -0
  137. package/packages/reploid/src/transport/partition-errors.json +16 -0
  138. package/packages/reploid/src/transport/retry-policy.js +36 -0
  139. package/packages/reploid/src/transport/room.js +502 -0
  140. package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
  141. package/packages/reploid/src/transport/signaling.js +571 -0
  142. package/packages/reploid/src/transport/swarm.js +1247 -0
  143. package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
  144. package/server/browser-roots.js +42 -0
  145. package/server/browser-web-worker.js +94 -0
  146. package/server/chat-completions.js +45 -0
  147. package/server/fleet/browser-bridge.js +145 -0
  148. package/server/fleet/contract.js +42 -0
  149. package/server/fleet/credit-evidence.js +103 -0
  150. package/server/fleet/credit-recovery.js +175 -0
  151. package/server/fleet/credits.js +178 -0
  152. package/server/fleet/execution-admission.js +32 -0
  153. package/server/fleet/native-host.js +817 -0
  154. package/server/fleet/placement.js +132 -0
  155. package/server/fleet/requester.js +346 -0
  156. package/server/fleet/service.js +311 -0
  157. package/server/fleet/terminal-policy.js +83 -0
  158. package/server/fleet/text-api.js +94 -0
  159. package/server/hashing.js +35 -0
  160. package/server/http-request-owner.js +49 -0
  161. package/server/messages.js +57 -0
  162. package/server/native-canonical-epochs.js +90 -0
  163. package/server/native-change-log.js +40 -0
  164. package/server/native-earning.js +203 -0
  165. package/server/native-generation-journal.js +46 -0
  166. package/server/native-model-config.js +57 -0
  167. package/server/native-model-files.js +382 -0
  168. package/server/native-model-origin.js +54 -0
  169. package/server/native-model-origins.json +12 -0
  170. package/server/native-node.js +85 -0
  171. package/server/native-owner/controls.js +162 -0
  172. package/server/native-owner/monitor.js +251 -0
  173. package/server/native-owner/policy.js +273 -0
  174. package/server/native-owner/rate.js +92 -0
  175. package/server/native-participant.js +866 -0
  176. package/server/native-readiness-log.js +30 -0
  177. package/server/native-release-storage.js +64 -0
  178. package/server/native-shader-cache.js +369 -0
  179. package/server/native-supply-budget.js +66 -0
  180. package/server/native-whole-earning.js +67 -0
  181. package/server/native-whole-model-fill.js +182 -0
  182. package/server/native-whole-model.js +130 -0
  183. package/server/public-web-worker.js +66 -0
  184. package/server/responses.js +92 -0
  185. package/server/search/errors.js +47 -0
  186. package/server/search/page-fetch.js +185 -0
  187. package/server/text-api.js +83 -0
  188. package/server/text-recovery.js +45 -0
  189. package/server/text-request-options.js +23 -0
  190. package/server/text-requester.js +231 -0
  191. package/server/token-auth.js +9 -0
  192. package/server/whole-model-requester.js +99 -0
  193. package/shared/config/chat-models.json +4685 -0
  194. package/shared/config/doppler-execution-contracts.js +32 -0
  195. package/shared/config/doppler-execution-contracts.json +43 -0
  196. package/shared/config/doppler-generation-contract.js +223 -0
  197. package/shared/config/doppler-local-models.js +79 -0
  198. package/shared/config/doppler-package.json +7 -0
  199. package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
  200. package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
  201. package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
  202. package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
  203. package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
  204. package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
  205. package/shared/config/reploid-library.json +49 -0
  206. package/shared/config/swarm-bootstrap.json +21 -0
  207. package/shared/infrastructure/doppler-runtime-service.js +261 -0
  208. package/shared/pool/executable-pack.js +87 -0
  209. package/shared/pool/pack-operation-adapters.js +124 -0
  210. package/shared/pool/pack-operation-policy.js +75 -0
  211. package/shared/pool/pool-config.json +2280 -0
  212. package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
  213. package/shared/vendor/reploid/artifacts/binary.js +25 -0
  214. package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
  215. package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
  216. package/shared/vendor/reploid/artifacts/identity.js +91 -0
  217. package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
  218. package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
  219. package/shared/vendor/reploid/artifacts/signatures.js +94 -0
  220. package/shared/vendor/reploid/config/defaults.json +110 -0
  221. package/shared/vendor/reploid/config/index.js +209 -0
  222. package/shared/vendor/reploid/config/request-overrides.json +9 -0
  223. package/shared/vendor/reploid/hashing.js +30 -0
  224. package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
  225. package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
  226. package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
  227. package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
  228. package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
  229. package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
  230. package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
  231. package/shared/vendor/reploid/rules/routing.rules.json +24 -0
  232. package/shared/vendor/reploid/transport/partition-errors.json +16 -0
  233. package/README.md +0 -3
@@ -0,0 +1,91 @@
1
+ import { toBase64Url, fromBase64Url } from './binary.js';
2
+ import { canonicalize as stableJson } from './canonical-json.js';
3
+ import { sha256HexUnprefixed } from './signatures.js';
4
+ const encoder = new TextEncoder();
5
+
6
+
7
+ const getCryptoApi = (cryptoApi) => {
8
+ const api = cryptoApi || globalThis.crypto;
9
+ if (!api?.subtle) {
10
+ throw new Error('WebCrypto unavailable');
11
+ }
12
+ return api;
13
+ };
14
+
15
+
16
+ function inferIdentityAlgorithm(bundleOrJwk = {}) {
17
+ const algorithm = String(bundleOrJwk?.algorithm || '').trim();
18
+ const crv = String(bundleOrJwk?.crv || bundleOrJwk?.publicJwk?.crv || '').trim();
19
+ if (algorithm === 'Ed25519' || crv === 'Ed25519') {
20
+ return 'Ed25519';
21
+ }
22
+ return 'ECDSA';
23
+ }
24
+
25
+ export function getIdentityImportAlgorithm(bundleOrJwk = {}) {
26
+ return inferIdentityAlgorithm(bundleOrJwk) === 'Ed25519'
27
+ ? { name: 'Ed25519' }
28
+ : { name: 'ECDSA', namedCurve: 'P-256' };
29
+ }
30
+
31
+ export function getIdentitySignAlgorithm(bundleOrJwk = {}) {
32
+ return inferIdentityAlgorithm(bundleOrJwk) === 'Ed25519'
33
+ ? { name: 'Ed25519' }
34
+ : { name: 'ECDSA', hash: 'SHA-256' };
35
+ }
36
+
37
+ export async function createPeerIdFromPublicJwk(publicJwk, cryptoApi = globalThis.crypto) {
38
+ const publicShape = publicJwk ? {
39
+ kty: publicJwk.kty || null,
40
+ crv: publicJwk.crv || null,
41
+ x: publicJwk.x || null,
42
+ y: publicJwk.y || null,
43
+ n: publicJwk.n || null,
44
+ e: publicJwk.e || null
45
+ } : {};
46
+ const digest = await sha256HexUnprefixed(stableJson(publicShape), getCryptoApi(cryptoApi));
47
+ return `peer:${digest.slice(0, 24)}`;
48
+ }
49
+
50
+ export async function importSigningKey(bundle, cryptoApi = globalThis.crypto) {
51
+ if (!bundle?.privateJwk) {
52
+ throw new Error('Missing private JWK');
53
+ }
54
+ return getCryptoApi(cryptoApi).subtle.importKey(
55
+ 'jwk',
56
+ bundle.privateJwk,
57
+ getIdentityImportAlgorithm(bundle),
58
+ false,
59
+ ['sign']
60
+ );
61
+ }
62
+
63
+ export async function importVerificationKey(bundleOrJwk, cryptoApi = globalThis.crypto) {
64
+ const publicJwk = bundleOrJwk?.publicJwk || bundleOrJwk;
65
+ if (!publicJwk) {
66
+ throw new Error('Missing public JWK');
67
+ }
68
+ return getCryptoApi(cryptoApi).subtle.importKey(
69
+ 'jwk',
70
+ publicJwk,
71
+ getIdentityImportAlgorithm(publicJwk),
72
+ false,
73
+ ['verify']
74
+ );
75
+ }
76
+
77
+ export function encodeBytes(value) {
78
+ return encoder.encode(String(value || ''));
79
+ }
80
+
81
+ export { fromBase64Url, toBase64Url };
82
+
83
+
84
+ export async function createSigningIdentity({ algorithm, cryptoApi = globalThis.crypto }) {
85
+ if (!['Ed25519', 'ECDSA'].includes(algorithm)) throw new TypeError('Explicit supported signing algorithm required');
86
+ const params = algorithm === 'Ed25519' ? { name: algorithm } : { name: algorithm, namedCurve: 'P-256' };
87
+ const keys = await cryptoApi.subtle.generateKey(params, true, ['sign', 'verify']);
88
+ const publicJwk = await cryptoApi.subtle.exportKey('jwk', keys.publicKey);
89
+ const privateJwk = await cryptoApi.subtle.exportKey('jwk', keys.privateKey);
90
+ return { version: 1, algorithm, peerId: await createPeerIdFromPublicJwk(publicJwk, cryptoApi), publicJwk, privateJwk };
91
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * @fileoverview Host-neutral participant identity proofs.
3
+ *
4
+ * A device's persistent participant identity (the Ed25519 or ECDSA P-256
5
+ * bundle browser and native hosts already keep) signs domain-separated
6
+ * canonical statements: device registration proofs and session bindings here, custody-key
7
+ * certificates in custody-certificate.js. WebCrypto only, so browser and
8
+ * native hosts can sign with the same code the server verifies with.
9
+ */
10
+
11
+ import { canonicalize } from './canonical-json.js';
12
+ import {
13
+ createPeerIdFromPublicJwk,
14
+ encodeBytes,
15
+ fromBase64Url,
16
+ getIdentitySignAlgorithm,
17
+ importSigningKey,
18
+ importVerificationKey,
19
+ toBase64Url
20
+ } from './identity.js';
21
+
22
+ export const REGISTRATION_PROOF_SCHEMA = 'reploid.device-registration-proof/v1';
23
+ export const SESSION_BINDING_SCHEMA = 'reploid.device-session-binding/v1';
24
+ export const DEVICE_HOSTS = Object.freeze(['browser', 'native']);
25
+
26
+ const PROOF_KEYS = Object.freeze(['schema', 'challengeId', 'nonce', 'accountId', 'audience', 'participantId', 'publicJwk', 'host', 'signature']);
27
+ const SESSION_BINDING_KEYS = Object.freeze(['schema', 'challengeId', 'nonce', 'accountId', 'sessionId', 'participantId', 'signature']);
28
+ const COORDINATE = /^[A-Za-z0-9_-]{16,128}$/;
29
+
30
+ const sameKeys = (value, keys) => Boolean(value) && typeof value === 'object' && !Array.isArray(value)
31
+ && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
32
+
33
+ /**
34
+ * The public half of a participant identity: an Ed25519 or ECDSA P-256 JWK
35
+ * reduced to its coordinates. Returns null for anything else, including a
36
+ * JWK carrying private key material.
37
+ */
38
+ export function normalizeParticipantPublicKey(jwk) {
39
+ if (!jwk || typeof jwk !== 'object' || Array.isArray(jwk) || Object.hasOwn(jwk, 'd')) return null;
40
+ if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && COORDINATE.test(jwk.x ?? '') && jwk.y === undefined) {
41
+ return { kty: 'OKP', crv: 'Ed25519', x: jwk.x };
42
+ }
43
+ if (jwk.kty === 'EC' && jwk.crv === 'P-256' && COORDINATE.test(jwk.x ?? '') && COORDINATE.test(jwk.y ?? '')) {
44
+ return { kty: 'EC', crv: 'P-256', x: jwk.x, y: jwk.y };
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /** The participant id every host derives from its identity public key. */
50
+ export const participantIdFor = (publicJwk) => createPeerIdFromPublicJwk(publicJwk);
51
+
52
+ const signedBytes = (domain, body) => encodeBytes(canonicalize({ signatureDomain: domain, payload: body }));
53
+
54
+ /** Sign `body` under `domain` with a participant identity bundle ({ publicJwk, privateJwk, algorithm? }). */
55
+ export async function signAsParticipant(identity, domain, body) {
56
+ const key = await importSigningKey(identity);
57
+ return toBase64Url(await crypto.subtle.sign(getIdentitySignAlgorithm(identity), key, signedBytes(domain, body)));
58
+ }
59
+
60
+ /** Verify a participant signature; malformed keys or signatures fail closed. */
61
+ export async function verifyParticipantSignature(publicJwk, domain, body, signature) {
62
+ if (typeof signature !== 'string' || signature.length === 0 || signature.length > 256) return false;
63
+ try {
64
+ const key = await importVerificationKey(publicJwk);
65
+ return await crypto.subtle.verify(getIdentitySignAlgorithm(publicJwk), key, fromBase64Url(signature), signedBytes(domain, body));
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ /** The statement a device signs to answer one registration challenge. */
72
+ export const registrationProofBody = ({ challenge, participantId, publicJwk, host }) => ({
73
+ schema: REGISTRATION_PROOF_SCHEMA,
74
+ challengeId: challenge.challengeId,
75
+ nonce: challenge.nonce,
76
+ accountId: challenge.accountId,
77
+ audience: challenge.audience,
78
+ participantId,
79
+ publicJwk,
80
+ host
81
+ });
82
+
83
+ /** Device side: answer a server registration challenge with the persistent participant identity. */
84
+ export async function createDeviceRegistrationProof({ identity, challenge, host }) {
85
+ const publicJwk = normalizeParticipantPublicKey(identity?.publicJwk);
86
+ if (!publicJwk || !identity.privateJwk) throw new TypeError('A persistent participant signing identity is required');
87
+ if (!DEVICE_HOSTS.includes(host)) throw new TypeError(`host must be one of ${DEVICE_HOSTS.join(', ')}`);
88
+ const body = registrationProofBody({ challenge, participantId: await participantIdFor(publicJwk), publicJwk, host });
89
+ return { ...body, signature: await signAsParticipant(identity, REGISTRATION_PROOF_SCHEMA, body) };
90
+ }
91
+
92
+ /**
93
+ * The statement a device signs to make the session that asked for
94
+ * `challenge` act as that device. Only a host holding that session signs it;
95
+ * a device registered from another session's challenge signs only the proof.
96
+ */
97
+ export const sessionBindingBody = ({ challenge, participantId }) => ({
98
+ schema: SESSION_BINDING_SCHEMA,
99
+ challengeId: challenge.challengeId,
100
+ nonce: challenge.nonce,
101
+ accountId: challenge.accountId,
102
+ sessionId: challenge.sessionId,
103
+ participantId
104
+ });
105
+
106
+ /** Device side: bind the challenge's session to this persistent participant identity. */
107
+ export async function createSessionBinding({ identity, challenge }) {
108
+ const publicJwk = normalizeParticipantPublicKey(identity?.publicJwk);
109
+ if (!publicJwk || !identity.privateJwk) throw new TypeError('A persistent participant signing identity is required');
110
+ if (typeof challenge?.sessionId !== 'string' || challenge.sessionId.length === 0) throw new TypeError('The challenge must name its session');
111
+ const body = sessionBindingBody({ challenge, participantId: await participantIdFor(publicJwk) });
112
+ return { ...body, signature: await signAsParticipant(identity, SESSION_BINDING_SCHEMA, body) };
113
+ }
114
+
115
+ /** Whether `binding` is `publicJwk`'s signed statement binding `challenge.sessionId` to it. */
116
+ export async function checkSessionBinding(binding, { publicJwk, challenge }) {
117
+ if (!sameKeys(binding, SESSION_BINDING_KEYS) || binding.schema !== SESSION_BINDING_SCHEMA) return false;
118
+ const participantId = await participantIdFor(publicJwk);
119
+ const { signature, ...body } = binding;
120
+ const expected = sessionBindingBody({ challenge, participantId });
121
+ if (Object.keys(expected).some((key) => body[key] !== expected[key])) return false;
122
+ return verifyParticipantSignature(publicJwk, SESSION_BINDING_SCHEMA, body, signature);
123
+ }
124
+
125
+ /**
126
+ * Check a registration proof's form, key-to-participant binding and
127
+ * signature. Challenge freshness and ownership are the store's checks.
128
+ * Returns { problem, publicJwk, participantId }; problem is null when valid.
129
+ */
130
+ export async function checkDeviceRegistrationProof(proof) {
131
+ if (!sameKeys(proof, PROOF_KEYS) || proof.schema !== REGISTRATION_PROOF_SCHEMA) return { problem: 'proof_malformed' };
132
+ const publicJwk = normalizeParticipantPublicKey(proof.publicJwk);
133
+ if (!publicJwk || !sameKeys(proof.publicJwk, Object.keys(publicJwk))) return { problem: 'proof_malformed' };
134
+ if (!DEVICE_HOSTS.includes(proof.host)) return { problem: 'proof_malformed' };
135
+ for (const key of ['challengeId', 'nonce', 'accountId', 'audience', 'participantId']) {
136
+ if (typeof proof[key] !== 'string' || proof[key].length === 0 || proof[key].length > 256) return { problem: 'proof_malformed' };
137
+ }
138
+ const participantId = await participantIdFor(publicJwk);
139
+ if (participantId !== proof.participantId) return { problem: 'participant_key_mismatch' };
140
+ const { signature, ...body } = proof;
141
+ if (!await verifyParticipantSignature(publicJwk, REGISTRATION_PROOF_SCHEMA, body, signature)) return { problem: 'proof_signature_invalid' };
142
+ return { problem: null, publicJwk, participantId };
143
+ }
144
+
145
+ export default {
146
+ REGISTRATION_PROOF_SCHEMA,
147
+ SESSION_BINDING_SCHEMA,
148
+ DEVICE_HOSTS,
149
+ normalizeParticipantPublicKey,
150
+ participantIdFor,
151
+ signAsParticipant,
152
+ verifyParticipantSignature,
153
+ registrationProofBody,
154
+ createDeviceRegistrationProof,
155
+ checkDeviceRegistrationProof,
156
+ sessionBindingBody,
157
+ createSessionBinding,
158
+ checkSessionBinding
159
+ };
@@ -0,0 +1,131 @@
1
+ import { canonicalize as stableJson } from './canonical-json.js';
2
+ import { wholeModelReceiptFields } from './whole-model-receipt.js';
3
+ import { sha256HexUnprefixed } from './signatures.js';
4
+ /**
5
+ * @fileoverview Signed receipt primitives for swarm contribution proof.
6
+ */
7
+
8
+ import {
9
+ encodeBytes,
10
+ getIdentitySignAlgorithm,
11
+ importSigningKey,
12
+ importVerificationKey,
13
+ toBase64Url,
14
+ fromBase64Url,
15
+ createPeerIdFromPublicJwk
16
+ } from './identity.js';
17
+
18
+
19
+
20
+ const getCryptoApi = (cryptoApi) => {
21
+ const api = cryptoApi || globalThis.crypto;
22
+ if (!api?.subtle) {
23
+ throw new Error('WebCrypto unavailable');
24
+ }
25
+ return api;
26
+ };
27
+
28
+
29
+ export function getReceiptPayload(receipt = {}) {
30
+ return {
31
+ version: Number(receipt.version || 1),
32
+ receiptId: String(receipt.receiptId || ''),
33
+ provider: String(receipt.provider || ''),
34
+ consumer: String(receipt.consumer || ''),
35
+ jobHash: String(receipt.jobHash || ''),
36
+ model: String(receipt.model || ''),
37
+ inputTokens: Math.max(0, Number(receipt.inputTokens || 0)),
38
+ outputTokens: Math.max(0, Number(receipt.outputTokens || 0)),
39
+ status: String(receipt.status || 'completed'),
40
+ timestamp: Number(receipt.timestamp || Date.now()),
41
+ ...(Number(receipt.version) === 2 ? wholeModelReceiptFields({ ...receipt, version: 2 }) : {})
42
+ };
43
+ }
44
+
45
+ export function canonicalizeReceiptPayload(receipt = {}) {
46
+ return stableJson(getReceiptPayload(receipt));
47
+ }
48
+
49
+ export async function createReceiptDraft(input = {}, cryptoApi = globalThis.crypto) {
50
+ const payload = getReceiptPayload({
51
+ ...input,
52
+ receiptId: ''
53
+ });
54
+ const digest = await sha256HexUnprefixed(canonicalizeReceiptPayload(payload), getCryptoApi(cryptoApi));
55
+
56
+ return {
57
+ ...payload,
58
+ receiptId: String(input.receiptId || `receipt:${digest.slice(0, 24)}`)
59
+ };
60
+ }
61
+
62
+ async function signPayload(payload, bundle, cryptoApi = globalThis.crypto) {
63
+ const subtle = getCryptoApi(cryptoApi).subtle;
64
+ const signingKey = await importSigningKey(bundle, cryptoApi);
65
+ const signature = await subtle.sign(
66
+ getIdentitySignAlgorithm(bundle),
67
+ signingKey,
68
+ encodeBytes(canonicalizeReceiptPayload(payload))
69
+ );
70
+ return toBase64Url(signature);
71
+ }
72
+
73
+ async function verifyPayload(payload, signature, publicJwk, cryptoApi = globalThis.crypto) {
74
+ const subtle = getCryptoApi(cryptoApi).subtle;
75
+ const verificationKey = await importVerificationKey(publicJwk, cryptoApi);
76
+ return subtle.verify(
77
+ getIdentitySignAlgorithm(publicJwk),
78
+ verificationKey,
79
+ fromBase64Url(signature),
80
+ encodeBytes(canonicalizeReceiptPayload(payload))
81
+ );
82
+ }
83
+
84
+ export async function signReceiptDraft(draft, providerBundle, cryptoApi = globalThis.crypto) {
85
+ const payload = getReceiptPayload(draft);
86
+ return {
87
+ ...payload,
88
+ providerSignature: await signPayload(payload, providerBundle, cryptoApi),
89
+ providerKey: providerBundle.publicJwk
90
+ };
91
+ }
92
+
93
+ export async function countersignReceipt(receipt, consumerBundle, cryptoApi = globalThis.crypto) {
94
+ const payload = getReceiptPayload(receipt);
95
+ return {
96
+ ...receipt,
97
+ consumerSignature: await signPayload(payload, consumerBundle, cryptoApi),
98
+ consumerKey: consumerBundle.publicJwk
99
+ };
100
+ }
101
+
102
+ export async function verifyReceipt(receipt, cryptoApi = globalThis.crypto) {
103
+ const payload = getReceiptPayload(receipt);
104
+ if (payload.version === 2 && (await createPeerIdFromPublicJwk(receipt.providerKey, cryptoApi) !== receipt.provider
105
+ || receipt.consumerSignature && await createPeerIdFromPublicJwk(receipt.consumerKey, cryptoApi) !== receipt.consumer)) {
106
+ return { providerValid: false, consumerValid: false, valid: false };
107
+ }
108
+
109
+ const providerValid = receipt?.providerSignature && receipt?.providerKey
110
+ ? await verifyPayload(payload, receipt.providerSignature, receipt.providerKey, cryptoApi)
111
+ : false;
112
+
113
+ const consumerValid = !receipt?.consumerSignature || !receipt?.consumerKey
114
+ ? false
115
+ : await verifyPayload(payload, receipt.consumerSignature, receipt.consumerKey, cryptoApi);
116
+
117
+ return {
118
+ providerValid,
119
+ consumerValid,
120
+ valid: providerValid && (!receipt?.consumerSignature || consumerValid)
121
+ };
122
+ }
123
+
124
+ export default {
125
+ canonicalizeReceiptPayload,
126
+ countersignReceipt,
127
+ createReceiptDraft,
128
+ getReceiptPayload,
129
+ signReceiptDraft,
130
+ verifyReceipt
131
+ };
@@ -0,0 +1,94 @@
1
+ import { bytesToBase64, base64ToBytes } from './binary.js';
2
+ import { canonicalize } from './canonical-json.js';
3
+ import { sha256Bytes, sha256HexUnprefixed, sha256Hex } from '../hashing.js';
4
+
5
+ export { sha256Bytes, sha256HexUnprefixed, sha256Hex };
6
+
7
+ const textEncoder = new TextEncoder();
8
+
9
+ export async function hashJson(value, cryptoApi = globalThis.crypto) {
10
+ return sha256Hex(canonicalize(value), cryptoApi);
11
+ }
12
+
13
+ export async function createSigningKeyPair() {
14
+ return crypto.subtle.generateKey(
15
+ { name: 'ECDSA', namedCurve: 'P-256' },
16
+ true,
17
+ ['sign', 'verify']
18
+ );
19
+ }
20
+
21
+ export async function exportPublicKey(publicKey) {
22
+ const spki = await crypto.subtle.exportKey('spki', publicKey);
23
+ return bytesToBase64(new Uint8Array(spki));
24
+ }
25
+
26
+ export async function exportPrivateKey(privateKey) {
27
+ const pkcs8 = await crypto.subtle.exportKey('pkcs8', privateKey);
28
+ return bytesToBase64(new Uint8Array(pkcs8));
29
+ }
30
+
31
+ export async function importPublicKey(publicKeyBase64) {
32
+ const bytes = base64ToBytes(publicKeyBase64);
33
+ return crypto.subtle.importKey(
34
+ 'spki',
35
+ bytes,
36
+ { name: 'ECDSA', namedCurve: 'P-256' },
37
+ true,
38
+ ['verify']
39
+ );
40
+ }
41
+
42
+ export async function importPrivateKey(privateKeyBase64) {
43
+ const bytes = base64ToBytes(privateKeyBase64);
44
+ return crypto.subtle.importKey(
45
+ 'pkcs8',
46
+ bytes,
47
+ { name: 'ECDSA', namedCurve: 'P-256' },
48
+ true,
49
+ ['sign']
50
+ );
51
+ }
52
+
53
+ export async function importSigningKeyPair({ privateKey, publicKey } = {}) {
54
+ if (!privateKey || !publicKey) throw new Error('privateKey and publicKey are required');
55
+ return {
56
+ privateKey: await importPrivateKey(privateKey),
57
+ publicKey: await importPublicKey(publicKey)
58
+ };
59
+ }
60
+
61
+ export function domainSeparatedPayload(domain, payload) {
62
+ const normalized = String(domain || '').trim();
63
+ if (!normalized) throw new Error('signature domain is required');
64
+ return {
65
+ signatureDomain: normalized,
66
+ payload
67
+ };
68
+ }
69
+
70
+ export async function signCanonical(value, privateKey, { domain = null } = {}) {
71
+ const signingValue = domain ? domainSeparatedPayload(domain, value) : value;
72
+ const payload = textEncoder.encode(canonicalize(signingValue));
73
+ const signature = await crypto.subtle.sign(
74
+ { name: 'ECDSA', hash: 'SHA-256' },
75
+ privateKey,
76
+ payload
77
+ );
78
+ return bytesToBase64(new Uint8Array(signature));
79
+ }
80
+
81
+ export async function verifyCanonicalSignature(value, publicKeyBase64, signatureBase64, { domain = null, allowLegacy = false } = {}) {
82
+ const publicKey = await importPublicKey(publicKeyBase64);
83
+ const verifyValue = async (candidate) => crypto.subtle.verify(
84
+ { name: 'ECDSA', hash: 'SHA-256' },
85
+ publicKey,
86
+ base64ToBytes(signatureBase64),
87
+ textEncoder.encode(canonicalize(candidate))
88
+ );
89
+ if (!domain) return verifyValue(value);
90
+ const domainOk = await verifyValue(domainSeparatedPayload(domain, value));
91
+ if (domainOk || !allowLegacy) return domainOk;
92
+ return verifyValue(value);
93
+ }
94
+
@@ -0,0 +1,42 @@
1
+ /** Measured whole-model receipt fields. Signatures bind observations; they do not
2
+ * establish answer quality or independent correctness of the provider's GPU. */
3
+ import { hashJson, sha256Hex } from './signatures.js';
4
+ import { canonicalize } from './canonical-json.js';
5
+
6
+ const digest = value => typeof value === 'string' && /^sha256:[a-f0-9]{64}$/.test(value);
7
+ const count = value => Number.isSafeInteger(value) && value >= 0;
8
+ const requireValue = (ok, message) => { if (!ok) throw Error(message); };
9
+
10
+ export function wholeModelReceiptFields(receipt) {
11
+ requireValue(receipt.version === 2 && receipt.status === 'completed' && receipt.usage === 'raw-model-tokens/v1'
12
+ && receipt.cleanup === 'settled' && digest(receipt.modelIdentity) && digest(receipt.inputDigest) && digest(receipt.outputDigest)
13
+ && count(receipt.inputTokens) && count(receipt.outputTokens)
14
+ && Array.isArray(receipt.adapterIdentities) && receipt.adapterIdentities.length <= 1 && receipt.adapterIdentities.every(digest)
15
+ && (receipt.ownerPolicyDigest === null || digest(receipt.ownerPolicyDigest))
16
+ && typeof receipt.stopReason === 'string' && receipt.stopReason.length > 0 && receipt.stopReason.length <= 64,
17
+ 'Invalid measured whole-model receipt');
18
+ return { modelIdentity: receipt.modelIdentity, adapterIdentities: [...receipt.adapterIdentities],
19
+ inputDigest: receipt.inputDigest, outputDigest: receipt.outputDigest, usage: receipt.usage,
20
+ stopReason: receipt.stopReason, cleanup: receipt.cleanup, ownerPolicyDigest: receipt.ownerPolicyDigest };
21
+ }
22
+
23
+ /** Missing measurements stay missing. Never infer token counts from text. */
24
+ export async function measureWholeModelReceipt(messages, result) {
25
+ const completion = result.completion ?? result.evidence?.output?.completion;
26
+ if (!completion) return null;
27
+ requireValue(completion.schema === 'doppler.generation-completion/v1'
28
+ && count(completion.promptTokenCount) && count(completion.generatedTokenCount)
29
+ && typeof result.content === 'string', 'Measured generation completion required');
30
+ const measured = { version: 2, status: 'completed', modelIdentity: result.modelIdentity,
31
+ adapterIdentities: result.adapterIdentities || [], inputDigest: await hashJson(messages),
32
+ outputDigest: await sha256Hex(result.content), inputTokens: completion.promptTokenCount,
33
+ outputTokens: completion.generatedTokenCount, usage: 'raw-model-tokens/v1',
34
+ stopReason: completion.stopReason, cleanup: 'settled', ownerPolicyDigest: result.ownerPolicyDigest ?? null };
35
+ wholeModelReceiptFields(measured);
36
+ return measured;
37
+ }
38
+
39
+ export async function matchesWholeModelReceipt(receipt, messages, result) {
40
+ const measured = await measureWholeModelReceipt(messages, result);
41
+ return measured !== null && Object.keys(measured).every(key => canonicalize(receipt[key]) === canonicalize(measured[key]));
42
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The library's one abort race. Agent execution, transport, custody, retrieval
3
+ * and hosts stop awaiting borrowed work when its signal aborts; the borrowed
4
+ * work itself may keep running until its owner settles it.
5
+ */
6
+
7
+ /** @template T @param {() => T | Promise<T>} operation @param {AbortSignal} signal @returns {Promise<T>} */
8
+ export function abortable(operation, signal) {
9
+ if (signal.aborted) return Promise.reject(signal.reason);
10
+ return new Promise((resolve, reject) => {
11
+ const abort = () => {
12
+ signal.removeEventListener('abort', abort);
13
+ reject(signal.reason);
14
+ };
15
+ signal.addEventListener('abort', abort, { once: true });
16
+ Promise.resolve().then(() => {
17
+ signal.throwIfAborted();
18
+ return operation();
19
+ }).then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
20
+ });
21
+ }
22
+
23
+ /**
24
+ * abortable() that also stops waiting at `deadline` (epoch milliseconds),
25
+ * rejecting with `expired()`; the operation itself is not cancelled.
26
+ * @template T @param {() => T | Promise<T>} operation @param {AbortSignal} signal
27
+ * @param {{ deadline: number, expired: () => unknown }} limit @returns {Promise<T>}
28
+ */
29
+ export function abortableBefore(operation, signal, { deadline, expired }) {
30
+ const timeout = new AbortController();
31
+ const timer = setTimeout(() => timeout.abort(expired()), Math.max(0, deadline - Date.now()));
32
+ return abortable(operation, AbortSignal.any([signal, timeout.signal])).finally(() => clearTimeout(timer));
33
+ }
34
+
35
+ /**
36
+ * Awaits work that has already started unless `signal` aborts first. A failure
37
+ * the work reports after the abort stays handled.
38
+ * @template T @param {T | Promise<T>} pending @param {AbortSignal} signal @returns {Promise<T>}
39
+ */
40
+ export function untilAborted(pending, signal) {
41
+ const started = Promise.resolve(pending);
42
+ started.catch(() => {});
43
+ return abortable(() => started, signal);
44
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "schema": "reploid.config/v1",
3
+ "agent": {
4
+ "maxCycles": 2048,
5
+ "maxBatchTools": 5,
6
+ "singleToolNudgeThreshold": 3,
7
+ "milestoneAutoparkThreshold": 3,
8
+ "maxContextChars": 2000000,
9
+ "timeoutMs": 300000,
10
+ "retryDelayMs": 30000,
11
+ "maxRetryDelayMs": 300000,
12
+ "rgrArchiveLimit": 24,
13
+ "requiredAnchorObservations": 3,
14
+ "toolStringPreviewChars": 2048,
15
+ "toolResultPayloadChars": 4096,
16
+ "toolBatchBodyChars": 12288,
17
+ "toolSanitizeMaxDepth": 4,
18
+ "toolSanitizeArrayLimit": 25
19
+ },
20
+ "tools": {
21
+ "allowed": [],
22
+ "parallelSafe": [
23
+ "ReadFile"
24
+ ],
25
+ "ordered": [
26
+ "WriteFile",
27
+ "LoadModule"
28
+ ],
29
+ "exclusive": [
30
+ "Promote"
31
+ ],
32
+ "loaderId": null,
33
+ "allowDynamic": false
34
+ },
35
+ "memory": {
36
+ "storeId": null,
37
+ "checkpointPrefix": "/checkpoints",
38
+ "maxCheckpointBytes": 16777216
39
+ },
40
+ "models": {
41
+ "providerId": null,
42
+ "contract": null
43
+ },
44
+ "mesh": {
45
+ "enabled": false,
46
+ "executeJobs": false,
47
+ "supplyArtifacts": false,
48
+ "shareCandidates": false,
49
+ "roomId": null,
50
+ "routing": "local-only",
51
+ "generationTimeoutMs": 45000,
52
+ "maxPendingJobs": 8,
53
+ "maxInboundJobs": 1,
54
+ "maxRetainedJobs": 32
55
+ },
56
+ "webrtc": {
57
+ "protocol": "assignment/v1",
58
+ "signalingUrl": null,
59
+ "rtcConfig": {
60
+ "iceServers": [],
61
+ "iceTransportPolicy": "all"
62
+ },
63
+ "dataChannelLabel": "reploid-pool",
64
+ "dataChannelOptions": {
65
+ "ordered": true,
66
+ "maxRetransmits": null
67
+ },
68
+ "maxPendingRemoteIceCandidates": 64,
69
+ "pendingRemoteIceTtlMs": 30000,
70
+ "maxMessageBytes": 65536,
71
+ "maxBufferedBytes": 1048576,
72
+ "connectTimeoutMs": 30000,
73
+ "reconnectBaseMs": 1000,
74
+ "maxBackoffMs": 30000,
75
+ "heartbeatIntervalMs": 30000,
76
+ "peerTimeoutMs": 60000,
77
+ "announceIntervalMs": 15000,
78
+ "signalingProbeTimeoutMs": 2000,
79
+ "transportOrder": [
80
+ "webrtc"
81
+ ],
82
+ "sessionId": null,
83
+ "signaling": {
84
+ "pollIntervalMs": 1000,
85
+ "pollTimeoutMs": 5000,
86
+ "failureThreshold": 3,
87
+ "backoffBaseMs": 1000,
88
+ "backoffMaxMs": 30000
89
+ },
90
+ "broadcastRoomId": null
91
+ },
92
+ "artifacts": {
93
+ "root": "/artifacts",
94
+ "maxBytes": 268435456
95
+ },
96
+ "improvement": {
97
+ "enabled": false,
98
+ "evaluatorId": null,
99
+ "approvalId": null,
100
+ "isolationId": null
101
+ },
102
+ "observation": {
103
+ "enabled": true
104
+ },
105
+ "legacyAgent": {
106
+ "discoveryLimit": 3,
107
+ "maxToolCalls": 8,
108
+ "settings": null
109
+ }
110
+ }