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.
- package/apps/poolday/config/chat-files.json +27 -0
- package/apps/poolday/config/partition-generation.js +21 -0
- package/apps/poolday/config/partition-policy.json +74 -0
- package/apps/poolday/config/partition-releases.json +3 -0
- package/apps/poolday/config/work-profile.json +122 -0
- package/apps/poolday/host/chat-view.js +47 -0
- package/apps/poolday/host/earning/client.js +199 -0
- package/apps/poolday/host/earning/contribution.js +168 -0
- package/apps/poolday/host/partition-release.js +33 -0
- package/apps/poolday/host/work-partition-artifact-source.js +37 -0
- package/apps/poolday/host/work-partition-capsule.js +39 -0
- package/apps/poolday/host/work-partition-plan.js +167 -0
- package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
- package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
- package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
- package/apps/poolday/pool/pack-operation.js +161 -0
- package/apps/poolday/pool/pack-release-policy.js +44 -0
- package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
- package/apps/poolday/providers/work-adapter.js +67 -0
- package/apps/poolday/providers/work-device.js +33 -0
- package/apps/poolday/providers/work-generation-completion.js +55 -0
- package/apps/poolday/providers/work-provider.js +209 -0
- package/apps/poolday/providers/work-resident-provider.js +113 -0
- package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
- package/docs/fleet-service.md +214 -0
- package/docs/native-participant.md +534 -0
- package/native-delivery.json +1174 -0
- package/package.json +29 -4
- package/packages/reploid/src/artifacts/binary.js +25 -0
- package/packages/reploid/src/artifacts/canonical-json.js +23 -0
- package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
- package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
- package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
- package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
- package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
- package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
- package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
- package/packages/reploid/src/artifacts/identity.js +91 -0
- package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
- package/packages/reploid/src/artifacts/receipt.js +131 -0
- package/packages/reploid/src/artifacts/signatures.js +94 -0
- package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
- package/packages/reploid/src/cancellation.js +44 -0
- package/packages/reploid/src/config/defaults.json +110 -0
- package/packages/reploid/src/config/index.js +209 -0
- package/packages/reploid/src/config/request-overrides.json +9 -0
- package/packages/reploid/src/config/transport-defaults.json +7 -0
- package/packages/reploid/src/hashing.js +30 -0
- package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
- package/packages/reploid/src/measurement/index.js +5 -0
- package/packages/reploid/src/measurement/readiness-records.js +310 -0
- package/packages/reploid/src/mesh/computer-identity.js +50 -0
- package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
- package/packages/reploid/src/mesh/contribution.js +141 -0
- package/packages/reploid/src/mesh/generation-recovery.js +58 -0
- package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
- package/packages/reploid/src/mesh/gpu-signature.js +136 -0
- package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
- package/packages/reploid/src/mesh/index.js +66 -0
- package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
- package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
- package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
- package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
- package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
- package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
- package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
- package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
- package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
- package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
- package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
- package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
- package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
- package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
- package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
- package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
- package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
- package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
- package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
- package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
- package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
- package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
- package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
- package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
- package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
- package/packages/reploid/src/mesh/peer-generation.js +896 -0
- package/packages/reploid/src/mesh/peer-identity.js +87 -0
- package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
- package/packages/reploid/src/mesh/preparation-progress.js +46 -0
- package/packages/reploid/src/mesh/provider-hardware.js +11 -0
- package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
- package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
- package/packages/reploid/src/mesh/web-access-network.js +112 -0
- package/packages/reploid/src/mesh/web-access-policy.json +17 -0
- package/packages/reploid/src/retrieval/citations.js +156 -0
- package/packages/reploid/src/retrieval/claim-support.js +113 -0
- package/packages/reploid/src/retrieval/costs.js +101 -0
- package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
- package/packages/reploid/src/retrieval/index.js +20 -0
- package/packages/reploid/src/retrieval/lexical-support.js +27 -0
- package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
- package/packages/reploid/src/retrieval/page-fetch.json +10 -0
- package/packages/reploid/src/retrieval/page-text.js +182 -0
- package/packages/reploid/src/retrieval/passage.js +37 -0
- package/packages/reploid/src/retrieval/policy.json +14 -0
- package/packages/reploid/src/retrieval/retrieval.js +23 -0
- package/packages/reploid/src/retrieval/review-queue.js +76 -0
- package/packages/reploid/src/retrieval/sources.js +263 -0
- package/packages/reploid/src/retrieval/support-review.js +155 -0
- package/packages/reploid/src/retrieval/text.js +21 -0
- package/packages/reploid/src/rules/contribution.rules.json +8 -0
- package/packages/reploid/src/rules/routing.rules.json +24 -0
- package/packages/reploid/src/transport/assignment.js +571 -0
- package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
- package/packages/reploid/src/transport/index.js +11 -0
- package/packages/reploid/src/transport/introduction-admission.js +24 -0
- package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
- package/packages/reploid/src/transport/introduction-policy.json +12 -0
- package/packages/reploid/src/transport/introduction-records.js +90 -0
- package/packages/reploid/src/transport/introductions.js +209 -0
- package/packages/reploid/src/transport/partition-data-channel.js +421 -0
- package/packages/reploid/src/transport/partition-errors.json +16 -0
- package/packages/reploid/src/transport/retry-policy.js +36 -0
- package/packages/reploid/src/transport/room.js +502 -0
- package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
- package/packages/reploid/src/transport/signaling.js +571 -0
- package/packages/reploid/src/transport/swarm.js +1247 -0
- package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
- package/server/browser-roots.js +42 -0
- package/server/browser-web-worker.js +94 -0
- package/server/chat-completions.js +45 -0
- package/server/fleet/browser-bridge.js +145 -0
- package/server/fleet/contract.js +42 -0
- package/server/fleet/credit-evidence.js +103 -0
- package/server/fleet/credit-recovery.js +175 -0
- package/server/fleet/credits.js +178 -0
- package/server/fleet/execution-admission.js +32 -0
- package/server/fleet/native-host.js +817 -0
- package/server/fleet/placement.js +132 -0
- package/server/fleet/requester.js +346 -0
- package/server/fleet/service.js +311 -0
- package/server/fleet/terminal-policy.js +83 -0
- package/server/fleet/text-api.js +94 -0
- package/server/hashing.js +35 -0
- package/server/http-request-owner.js +49 -0
- package/server/messages.js +57 -0
- package/server/native-canonical-epochs.js +90 -0
- package/server/native-change-log.js +40 -0
- package/server/native-earning.js +203 -0
- package/server/native-generation-journal.js +46 -0
- package/server/native-model-config.js +57 -0
- package/server/native-model-files.js +382 -0
- package/server/native-model-origin.js +54 -0
- package/server/native-model-origins.json +12 -0
- package/server/native-node.js +83 -0
- package/server/native-owner/controls.js +162 -0
- package/server/native-owner/monitor.js +251 -0
- package/server/native-owner/policy.js +273 -0
- package/server/native-owner/rate.js +92 -0
- package/server/native-participant.js +866 -0
- package/server/native-readiness-log.js +30 -0
- package/server/native-release-storage.js +64 -0
- package/server/native-shader-cache.js +369 -0
- package/server/native-supply-budget.js +66 -0
- package/server/native-whole-earning.js +67 -0
- package/server/native-whole-model-fill.js +182 -0
- package/server/native-whole-model.js +130 -0
- package/server/public-web-worker.js +66 -0
- package/server/responses.js +92 -0
- package/server/search/errors.js +47 -0
- package/server/search/page-fetch.js +185 -0
- package/server/text-api.js +83 -0
- package/server/text-recovery.js +45 -0
- package/server/text-request-options.js +23 -0
- package/server/text-requester.js +231 -0
- package/server/token-auth.js +9 -0
- package/server/whole-model-requester.js +99 -0
- package/shared/config/chat-models.json +4685 -0
- package/shared/config/doppler-execution-contracts.js +32 -0
- package/shared/config/doppler-execution-contracts.json +43 -0
- package/shared/config/doppler-generation-contract.js +223 -0
- package/shared/config/doppler-local-models.js +79 -0
- package/shared/config/doppler-package.json +7 -0
- package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
- package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
- package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
- package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
- package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
- package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
- package/shared/config/reploid-library.json +49 -0
- package/shared/config/swarm-bootstrap.json +21 -0
- package/shared/infrastructure/doppler-runtime-service.js +261 -0
- package/shared/pool/executable-pack.js +87 -0
- package/shared/pool/pack-operation-adapters.js +124 -0
- package/shared/pool/pack-operation-policy.js +75 -0
- package/shared/pool/pool-config.json +2280 -0
- package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
- package/shared/vendor/reploid/artifacts/binary.js +25 -0
- package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
- package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
- package/shared/vendor/reploid/artifacts/identity.js +91 -0
- package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
- package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
- package/shared/vendor/reploid/artifacts/signatures.js +94 -0
- package/shared/vendor/reploid/config/defaults.json +110 -0
- package/shared/vendor/reploid/config/index.js +209 -0
- package/shared/vendor/reploid/config/request-overrides.json +9 -0
- package/shared/vendor/reploid/hashing.js +30 -0
- package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
- package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
- package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
- package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
- package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
- package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
- package/shared/vendor/reploid/rules/routing.rules.json +24 -0
- package/shared/vendor/reploid/transport/partition-errors.json +16 -0
- 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
|
+
}
|