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.
- 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 +542 -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 +85 -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,33 @@
|
|
|
1
|
+
import policy from '../config/partition-releases.json' with { type: 'json' };
|
|
2
|
+
import { canonicalPartitionJson, partitionAdapterIdentities } from '../../../shared/vendor/reploid/mesh/partitions/partition-contract.js';
|
|
3
|
+
|
|
4
|
+
/** Owner-reviewed public releases, never peer-advertised qualification. An approval
|
|
5
|
+
* binds the exact artifact, adapters and route descriptor (including stage identities).
|
|
6
|
+
* Evidence from diagnostic/test-signing journeys does not approve public service.
|
|
7
|
+
* Empty policy intentionally leaves public partition execution closed. */
|
|
8
|
+
export function qualifiedPartitionRoute(model, approvals = policy.approvals) {
|
|
9
|
+
if (!model?.partition || !model.identity) return false;
|
|
10
|
+
const partition = model.partition;
|
|
11
|
+
const ids = partition.route?.schema === 'reploid.partition-route/v1'
|
|
12
|
+
? Array.isArray(partition.route.stages) && partition.route.stages.map(stage => stage?.participantId)
|
|
13
|
+
: [partition.participantA, partition.participantB];
|
|
14
|
+
if (!ids || ids.length < 2 || ids.some(id => typeof id !== 'string' || !id) || new Set(ids).size !== ids.length) return false;
|
|
15
|
+
let adapterIdentities;
|
|
16
|
+
try { adapterIdentities = partitionAdapterIdentities(model); }
|
|
17
|
+
catch { return false; } // Malformed discovery is ineligible, not a catalog outage.
|
|
18
|
+
return Array.isArray(approvals) && approvals.some(approval => approval?.partition && Array.isArray(approval.adapterIdentities) && approval.modelId === model.id && approval.modelIdentity === model.identity
|
|
19
|
+
&& typeof approval.evidence === 'string' && approval.evidence.trim()
|
|
20
|
+
&& canonicalPartitionJson(approval.adapterIdentities) === canonicalPartitionJson(adapterIdentities)
|
|
21
|
+
&& canonicalPartitionJson(approval.partition) === canonicalPartitionJson(partition));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const availablePartitionRoute = (model, approvals) => qualifiedPartitionRoute(model, approvals)
|
|
25
|
+
&& ['ready', 'busy'].includes(model.networkAvailability ?? model.availability);
|
|
26
|
+
|
|
27
|
+
/** Saved selections must still name the currently admitted route. Never substitute. */
|
|
28
|
+
export function currentPartitionRoute(model, models, approvals) {
|
|
29
|
+
return qualifiedPartitionRoute(model, approvals) && models.some(current => availablePartitionRoute(current, approvals)
|
|
30
|
+
&& current.id === model.id && current.identity === model.identity
|
|
31
|
+
&& canonicalPartitionJson(partitionAdapterIdentities(current)) === canonicalPartitionJson(partitionAdapterIdentities(model))
|
|
32
|
+
&& canonicalPartitionJson(current.partition) === canonicalPartitionJson(model.partition));
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Resolve signed Capsule paths against their declared manifest location and pinned source. */
|
|
2
|
+
export function resolvePartitionArtifactSource(model, manifest, files, artifact) {
|
|
3
|
+
const reject = () => { throw new Error('Capsule artifact differs from the pinned catalog source'); };
|
|
4
|
+
let sourcePath = artifact?.path;
|
|
5
|
+
if (model.capsule !== undefined) {
|
|
6
|
+
const capsule = model.capsule;
|
|
7
|
+
const signed = capsule.artifacts?.find(row => row.artifactId === artifact?.artifactId);
|
|
8
|
+
const signedManifest = capsule.artifacts?.find(row => row.artifactId === capsule.program?.manifestArtifactId);
|
|
9
|
+
if (!signed || !signedManifest || signedManifest.role !== 'manifest'
|
|
10
|
+
|| signedManifest.hash !== model.identity
|
|
11
|
+
|| ['artifactId', 'role', 'path', 'hash', 'sizeBytes'].some(key => signed[key] !== artifact[key])) reject();
|
|
12
|
+
const origin = 'https://reploid-capsule.invalid/';
|
|
13
|
+
const manifestURL = new URL(signedManifest.path, origin), artifactURL = new URL(artifact.path, origin);
|
|
14
|
+
for (const [url, path] of [[manifestURL, signedManifest.path], [artifactURL, artifact.path]]) {
|
|
15
|
+
if (url.origin !== new URL(origin).origin || url.search || url.hash || url.pathname.slice(1) !== path) reject();
|
|
16
|
+
}
|
|
17
|
+
const directory = signedManifest.path.slice(0, signedManifest.path.lastIndexOf('/') + 1);
|
|
18
|
+
// Metadata outside the manifest directory needs an exact catalog path.
|
|
19
|
+
// Model-local files keep their exact manifest-relative path, never a basename alias.
|
|
20
|
+
if (artifact.path.startsWith(directory)) sourcePath = artifact.path.slice(directory.length);
|
|
21
|
+
}
|
|
22
|
+
const shard = manifest.shards.find(row => row.filename === sourcePath);
|
|
23
|
+
const file = files.find(row => row.path === sourcePath)
|
|
24
|
+
|| (shard && { path: shard.filename, role: 'model-weights', sizeBytes: shard.size,
|
|
25
|
+
hash: shard.hash, hashAlgorithm: manifest.hashAlgorithm });
|
|
26
|
+
// Signed pieces authenticate the selected ranges. The Capsule's SHA256
|
|
27
|
+
// whole-artifact identity and the source manifest's BLAKE3 shard identity
|
|
28
|
+
// are distinct; retain the latter for custody instead of relabeling it.
|
|
29
|
+
if (model.capsule && shard && manifest.hashAlgorithm === 'blake3'
|
|
30
|
+
&& artifact.role === 'weight-shard' && model.capsule.program.weightArtifactIds?.includes(artifact.artifactId)
|
|
31
|
+
&& /^sha256:[a-f0-9]{64}$/.test(artifact.hash) && /^[a-f0-9]{64}$/.test(shard.hash)
|
|
32
|
+
&& file?.role === 'model-weights' && file.hashAlgorithm === manifest.hashAlgorithm
|
|
33
|
+
&& file.hash === shard.hash && file.sizeBytes === shard.size && artifact.sizeBytes === shard.size) return file;
|
|
34
|
+
if (!file || file.sizeBytes !== artifact.sizeBytes || 'sha256:' + file.hash !== artifact.hash
|
|
35
|
+
|| file.hashAlgorithm !== 'sha256') reject();
|
|
36
|
+
return file;
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Exact signed resident admission; complete-job receipt rules remain unchanged. */
|
|
2
|
+
import { hashDopplerEvidence, validateExecutablePack } from '../../../shared/pool/executable-pack.js';
|
|
3
|
+
import { resolveDopplerExecutionContract } from '../../../shared/config/doppler-execution-contracts.js';
|
|
4
|
+
|
|
5
|
+
export async function assertWorkResidentCapsule(model, session) {
|
|
6
|
+
const binding = model.executablePack, validation = validateExecutablePack(binding);
|
|
7
|
+
if (!validation.ok) throw new Error(validation.reasons.join('; '));
|
|
8
|
+
const contract = resolveDopplerExecutionContract(binding.schema);
|
|
9
|
+
if (contract.openMethod !== 'openCapsule' || binding.requiredOperation !== 'residentPartition'
|
|
10
|
+
|| session?.schema !== contract.sessionSchema || !session.loaded || !session.residentPartition) {
|
|
11
|
+
throw new Error('Exact public Capsule resident session required');
|
|
12
|
+
}
|
|
13
|
+
if (await hashDopplerEvidence(binding.artifacts) !== binding.artifactClosureDigest
|
|
14
|
+
|| contract.identityFields.some(field => binding[field] !== session[contract.sessionIdentity]?.[field])) {
|
|
15
|
+
throw new Error('Resident Capsule identity differs from the approved binding');
|
|
16
|
+
}
|
|
17
|
+
if (!binding.acceptedTargetPlanDigests.includes(session.selectedTargetPlanDigest)) {
|
|
18
|
+
throw new Error('Resident Capsule selected an unaccepted TargetPlan');
|
|
19
|
+
}
|
|
20
|
+
const verification = session.verification, capsule = verification?.capsule;
|
|
21
|
+
if (capsule?.schema !== binding.schema || capsule.modelId !== model.id
|
|
22
|
+
|| session.manifestHash !== model.identity || verification.lifecycle?.authorization?.mode === 'retained-local') {
|
|
23
|
+
throw new Error('Resident Capsule model or delegation authorization differs');
|
|
24
|
+
}
|
|
25
|
+
const pieces = verification.pieceVerification;
|
|
26
|
+
const index = binding.artifacts.find(row => row.artifactId === capsule.program.pieceIndexArtifactId);
|
|
27
|
+
if (verification.artifactVerificationScope !== 'resident-pieces' || pieces?.mode !== 'signed-piece-index'
|
|
28
|
+
|| index?.role !== 'piece-index' || pieces.indexArtifactId !== index.artifactId
|
|
29
|
+
|| pieces.indexIdentity !== index.hash || pieces.manifestIdentity !== model.identity) {
|
|
30
|
+
throw new Error('Resident Capsule requires exact signed piece verification');
|
|
31
|
+
}
|
|
32
|
+
const full = binding.artifacts.filter(row => row.role !== 'weight-shard')
|
|
33
|
+
.map(({ artifactId, hash, sizeBytes }) => ({ artifactId, hash, sizeBytes }));
|
|
34
|
+
if (await hashDopplerEvidence(full) !== await hashDopplerEvidence(verification.artifactReceipts)
|
|
35
|
+
|| await hashDopplerEvidence(pieces.declaredWeightArtifactIds)
|
|
36
|
+
!== await hashDopplerEvidence(capsule.program.weightArtifactIds)) {
|
|
37
|
+
throw new Error('Resident Capsule verification does not cover its declared closure');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** Hosts carry an explicit Doppler plan; Doppler validates its geometry and storage. */
|
|
2
|
+
import { freezeJson } from '../../../shared/vendor/reploid/config/index.js';
|
|
3
|
+
import { partitionActivationContract } from '../../../shared/vendor/reploid/mesh/partitions/partition-contract.js';
|
|
4
|
+
import { validateRouteCostPolicy } from '../../../shared/vendor/reploid/mesh/partitions/partition-route-ranking.js';
|
|
5
|
+
import { selectWorkAdapters } from '../providers/work-adapter.js';
|
|
6
|
+
import partitionPolicy from '../config/partition-policy.json' with { type: 'json' };
|
|
7
|
+
|
|
8
|
+
export function snapshotWorkPartitionSelection(selected) {
|
|
9
|
+
const adapters = selectWorkAdapters(selected);
|
|
10
|
+
selected = { ...selected, adapters };
|
|
11
|
+
if (selected.partitionPlan === undefined) return selected;
|
|
12
|
+
const plan = selected.partitionPlan;
|
|
13
|
+
if (!plan || plan.modelId !== selected.id
|
|
14
|
+
|| selected.partitionSplitLayer !== undefined && plan.splitLayer !== selected.partitionSplitLayer) {
|
|
15
|
+
throw new Error('Declared partition plan differs from the selected model or split');
|
|
16
|
+
}
|
|
17
|
+
return { ...selected, partitionPlan: freezeJson({ ...structuredClone(plan), ...(adapters.length ? { adapterIdentities: adapters.map(adapter => adapter.identity) } : {}) }) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A catalog model participates in ordered routes only through its declared Doppler plan version. */
|
|
21
|
+
export const isWorkOrderedModel = model => model?.partitionPlan?.schema === 'doppler.layer-partition-contract/v3';
|
|
22
|
+
|
|
23
|
+
/** Host stage identity is explicit; discovery cannot invent a resident session. */
|
|
24
|
+
export function snapshotWorkPartitionStage(selected, index, participantId, ordered, canonical) {
|
|
25
|
+
const isOrdered = selected.partitionPlan?.schema === 'doppler.layer-partition-contract/v3';
|
|
26
|
+
if (!isOrdered && !ordered) return null;
|
|
27
|
+
const stage = ordered?.stage;
|
|
28
|
+
if (!isOrdered || canonical || !stage || !Number.isSafeInteger(index) || index < 0
|
|
29
|
+
|| index >= selected.partitionPlan.stageCount || stage.index !== index || stage.participantId !== participantId
|
|
30
|
+
|| !['participantId', 'transportId', 'residentId', 'executionId'].every(key => typeof stage[key] === 'string' && stage[key].length > 0)
|
|
31
|
+
|| Object.keys(stage).some(key => !['index', 'participantId', 'transportId', 'residentId', 'executionId'].includes(key))) {
|
|
32
|
+
throw new Error('Ordered contribution requires an explicit matching host stage and ordinary execution');
|
|
33
|
+
}
|
|
34
|
+
return freezeJson(structuredClone(stage));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function workOrderedResidentOptions(runtime, stage) {
|
|
38
|
+
if (!stage) return {};
|
|
39
|
+
if (runtime.ORDERED_LAYER_PARTITION_SCHEMA !== 'doppler.layer-partition-contract/v3'
|
|
40
|
+
|| !['validateOrderedLayerPartitionPlan', 'resolveLayerPartition', 'hashLayerPartitionPlan',
|
|
41
|
+
'hashPartitionGeneration', 'getPartitionActivationContract'].every(key => typeof runtime[key] === 'function')) {
|
|
42
|
+
throw new Error('Installed Doppler does not support the ordered resident contract');
|
|
43
|
+
}
|
|
44
|
+
return { ordered: { runtime, stage } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createWorkPartitionPlan(runtime, manifest, selected, index = 0) {
|
|
48
|
+
selected = snapshotWorkPartitionSelection(selected);
|
|
49
|
+
if (selected.partitionPlan === undefined) {
|
|
50
|
+
return runtime.createLayerPartitionPlan({ modelId: manifest.modelId, ...manifest.architecture,
|
|
51
|
+
splitLayer: selected.partitionSplitLayer,
|
|
52
|
+
...(selected.adapters.length ? { adapterIdentities: selected.adapters.map(adapter => adapter.identity) } : {}),
|
|
53
|
+
activationDtype: manifest.inference.session.compute.defaults.activationDtype });
|
|
54
|
+
}
|
|
55
|
+
if (manifest.modelId !== selected.id || typeof runtime.resolveLayerPartition !== 'function') {
|
|
56
|
+
throw new Error('Declared partition plan requires the matching manifest and Doppler plan validator');
|
|
57
|
+
}
|
|
58
|
+
const plan = selected.partitionPlan;
|
|
59
|
+
if (plan.schema === 'doppler.layer-partition-contract/v3') {
|
|
60
|
+
if (runtime.ORDERED_LAYER_PARTITION_SCHEMA !== plan.schema
|
|
61
|
+
|| typeof runtime.validateOrderedLayerPartitionPlan !== 'function') {
|
|
62
|
+
throw new Error('Installed Doppler does not support the declared ordered plan');
|
|
63
|
+
}
|
|
64
|
+
runtime.validateOrderedLayerPartitionPlan(manifest, plan);
|
|
65
|
+
}
|
|
66
|
+
runtime.resolveLayerPartition(manifest, { plan, index });
|
|
67
|
+
partitionActivationContract(runtime, plan);
|
|
68
|
+
return plan;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Launch 3 policy members; each stays off unless the operator configures it. */
|
|
72
|
+
const positiveInteger = value => Number.isSafeInteger(value) && value > 0;
|
|
73
|
+
const exactKeys = (value, keys) => !!value && typeof value === 'object' && !Array.isArray(value)
|
|
74
|
+
&& Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
75
|
+
const validCostPolicy = value => { try { validateRouteCostPolicy(value); return true; } catch { return false; } };
|
|
76
|
+
const ORCHESTRATION_POLICIES = Object.freeze({
|
|
77
|
+
autoStage: value => exactKeys(value, ['dwellMs']) && Number.isSafeInteger(value.dwellMs) && value.dwellMs >= 0,
|
|
78
|
+
// Prices, priors and reserves come only from the operator's versioned cost policy.
|
|
79
|
+
routeRanking: value => exactKeys(value, ['margin', 'smoothing', 'maxObservations', 'workload', 'costs'])
|
|
80
|
+
&& typeof value.margin === 'number' && value.margin >= 0 && value.margin < 1
|
|
81
|
+
&& typeof value.smoothing === 'number' && value.smoothing > 0 && value.smoothing <= 1 && positiveInteger(value.maxObservations)
|
|
82
|
+
&& exactKeys(value.workload, ['promptTokens', 'expectedTokens']) && positiveInteger(value.workload.promptTokens)
|
|
83
|
+
&& positiveInteger(value.workload.expectedTokens) && validCostPolicy(value.costs),
|
|
84
|
+
// Never above the operator's explicit stage budget (legacy hosts keep their ceiling).
|
|
85
|
+
admission: (value, maxGpuBufferBytes) => exactKeys(value, ['maxBytes', 'maxConcurrentAttempts', 'attemptBytes', ...(value?.queue === undefined ? [] : ['queue'])])
|
|
86
|
+
&& positiveInteger(value.maxBytes) && value.maxBytes <= maxGpuBufferBytes
|
|
87
|
+
&& positiveInteger(value.maxConcurrentAttempts) && positiveInteger(value.attemptBytes) && value.attemptBytes <= value.maxBytes
|
|
88
|
+
// A queued reservation must be answered before the execution channel's request timeout.
|
|
89
|
+
&& (value.queue === undefined || exactKeys(value.queue, ['maxQueued', 'maxQueuedPerRequester', 'maxWaitMs'])
|
|
90
|
+
&& positiveInteger(value.queue.maxQueued) && positiveInteger(value.queue.maxQueuedPerRequester)
|
|
91
|
+
&& positiveInteger(value.queue.maxWaitMs) && value.queue.maxWaitMs < partitionPolicy.executionChannel.timeoutMs),
|
|
92
|
+
replicas: value => exactKeys(value, ['queueBound', 'bottleneckRatio']) && Number.isSafeInteger(value.queueBound)
|
|
93
|
+
&& value.queueBound >= 0 && typeof value.bottleneckRatio === 'number' && Number.isFinite(value.bottleneckRatio) && value.bottleneckRatio > 1
|
|
94
|
+
});
|
|
95
|
+
const isAutoStage = stage => stage?.assignment === 'auto';
|
|
96
|
+
|
|
97
|
+
/** Snapshot operator-selected geometry and limits before host connection awaits. */
|
|
98
|
+
export function snapshotWorkOrderedParticipation(ordered, canonical, models) {
|
|
99
|
+
if (ordered === null) return null;
|
|
100
|
+
if (!ordered || canonical) throw new Error('Ordered participation requires an explicit ordinary host configuration');
|
|
101
|
+
const copy = structuredClone(ordered);
|
|
102
|
+
const selected = models.find(model => model.id === copy.stage?.modelId);
|
|
103
|
+
const budgets = selected?.partitionRuntime?.maxGpuBufferBytes;
|
|
104
|
+
let maxGpuBufferBytes = partitionPolicy.maxGpuBufferBytes;
|
|
105
|
+
if (budgets !== undefined) {
|
|
106
|
+
if (!Array.isArray(budgets) || budgets.length !== selected.partitionPlan?.stageCount || !budgets.every(positiveInteger)) {
|
|
107
|
+
throw new Error('Ordered model requires one explicit positive GPU budget per stage');
|
|
108
|
+
}
|
|
109
|
+
// Automatic reassignment may pick any stage, so its shared admission bound
|
|
110
|
+
// must fit the smallest of the authorized stage budgets.
|
|
111
|
+
maxGpuBufferBytes = isAutoStage(copy.stage) ? Math.min(...budgets) : budgets[copy.stage.index];
|
|
112
|
+
}
|
|
113
|
+
for (const key of ['maxStages', 'maxStageOffersPerPeer', 'maxRouteBytes', 'maxSelectionSteps', 'maxRoutes']) {
|
|
114
|
+
if (!Number.isSafeInteger(copy[key]) || copy[key] <= 0) throw new Error('Explicit ordered host limit required: ' + key);
|
|
115
|
+
}
|
|
116
|
+
if (copy.orchestration !== undefined) {
|
|
117
|
+
const policy = copy.orchestration;
|
|
118
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)
|
|
119
|
+
|| Object.entries(policy).some(([key, value]) => !Object.hasOwn(ORCHESTRATION_POLICIES, key) || !ORCHESTRATION_POLICIES[key](value, maxGpuBufferBytes))) {
|
|
120
|
+
throw new Error('Ordered orchestration policy must name known, explicitly bounded mechanisms');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (isAutoStage(copy.stage) !== (copy.orchestration?.autoStage !== undefined)
|
|
124
|
+
|| copy.orchestration?.replicas !== undefined && !isAutoStage(copy.stage)) {
|
|
125
|
+
throw new Error('Automatic ordered stage requires both the operator auto choice and its orchestration policy');
|
|
126
|
+
}
|
|
127
|
+
if (isAutoStage(copy.stage)) {
|
|
128
|
+
// The operator authorizes this contributor's own policy to pick a stage of this model.
|
|
129
|
+
const stage = copy.stage, selected = models.find(model => model.id === stage.modelId);
|
|
130
|
+
if (!selected || selected.partitionPlan?.schema !== 'doppler.layer-partition-contract/v3'
|
|
131
|
+
|| selected.partitionPlan.stageCount > copy.maxStages
|
|
132
|
+
|| Object.keys(stage).some(key => !['modelId', 'assignment'].includes(key))) {
|
|
133
|
+
throw new Error('Ordered host stage requires a matching declared model plan and allocation identity');
|
|
134
|
+
}
|
|
135
|
+
} else if (copy.stage !== null) {
|
|
136
|
+
const stage = copy.stage, selected = models.find(model => model.id === stage?.modelId);
|
|
137
|
+
if (!selected || selected.partitionPlan?.schema !== 'doppler.layer-partition-contract/v3'
|
|
138
|
+
|| !Number.isSafeInteger(stage.index) || stage.index < 0 || stage.index >= selected.partitionPlan.stageCount
|
|
139
|
+
|| selected.partitionPlan.stageCount > copy.maxStages
|
|
140
|
+
|| !['residentId', 'executionId'].every(key => typeof stage[key] === 'string' && stage[key].length > 0)
|
|
141
|
+
|| Object.keys(stage).some(key => !['modelId', 'index', 'residentId', 'executionId', 'participantId', 'transportId'].includes(key))) {
|
|
142
|
+
throw new Error('Ordered host stage requires a matching declared model plan and allocation identity');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return freezeJson(copy);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Bind identity from the authenticated host, never from discovery or a caller route. */
|
|
149
|
+
export function bindWorkOrderedParticipation(options, participantId, transportId, policy) {
|
|
150
|
+
if (!options) return { ordered: null, policy };
|
|
151
|
+
const configured = options.stage;
|
|
152
|
+
if (![participantId, transportId].every(value => typeof value === 'string' && value.length > 0)
|
|
153
|
+
|| configured && (configured.participantId !== undefined && configured.participantId !== participantId
|
|
154
|
+
|| configured.transportId !== undefined && configured.transportId !== transportId)) {
|
|
155
|
+
throw new Error('Ordered host identity differs from the authenticated transport');
|
|
156
|
+
}
|
|
157
|
+
const stage = configured === null || isAutoStage(configured) ? null : { index: configured.index, participantId, transportId,
|
|
158
|
+
residentId: configured.residentId, executionId: configured.executionId };
|
|
159
|
+
// Absent orchestration keeps the Launch 1 binding unchanged.
|
|
160
|
+
const orchestration = options.orchestration === undefined ? null : structuredClone(options.orchestration);
|
|
161
|
+
if (orchestration?.autoStage) orchestration.autoStage = { ...orchestration.autoStage, transportId };
|
|
162
|
+
return { ordered: { stage, maxRouteBytes: options.maxRouteBytes, maxSelectionSteps: options.maxSelectionSteps, maxRoutes: options.maxRoutes,
|
|
163
|
+
...(orchestration ? { orchestration } : {}) },
|
|
164
|
+
// Every ordered stage, the coordinator's own included, runs under the declared execution-channel bound.
|
|
165
|
+
policy: { ...policy, maxStageOffersPerPeer: options.maxStageOffersPerPeer,
|
|
166
|
+
limits: { ...policy.limits, maxStages: options.maxStages, stageTimeoutMs: policy.executionChannel?.timeoutMs } } };
|
|
167
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Keep an authoritative tokenizer acquisition failure visible during resident opening. */
|
|
2
|
+
export function createPartitionTokenizerGuard(storage) {
|
|
3
|
+
let failure = null, guarded = storage;
|
|
4
|
+
for (const name of ['loadTokenizerJson', 'loadTokenizerModel']) {
|
|
5
|
+
if (typeof storage[name] !== 'function') continue;
|
|
6
|
+
if (guarded === storage) guarded = { ...storage };
|
|
7
|
+
guarded[name] = async (...args) => {
|
|
8
|
+
try { return await storage[name](...args); }
|
|
9
|
+
catch (error) { failure ||= { error }; throw error; }
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
storage: guarded,
|
|
14
|
+
assertAvailable() { if (failure) throw failure.error; },
|
|
15
|
+
explain(error) {
|
|
16
|
+
if (!failure || error === failure.error) return error;
|
|
17
|
+
const cause = failure.error;
|
|
18
|
+
return new AggregateError([cause, error], cause?.message || String(cause), { cause });
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Origin-wide cache use. A failed cleanup retains its lease until cleanup succeeds. */
|
|
2
|
+
export const MODEL_STORAGE_LOCK = 'reploid:model-storage-use';
|
|
3
|
+
|
|
4
|
+
export function retainModelStorage({ signal, locks = globalThis.navigator?.locks } = {}) {
|
|
5
|
+
// Hosts without browser storage can still execute injected models. Removal requires locks.
|
|
6
|
+
if (!locks?.request) return Promise.resolve(() => {});
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const holding = locks.request(MODEL_STORAGE_LOCK, { mode: 'shared', ...(signal ? { signal } : {}) },
|
|
9
|
+
() => new Promise(release => resolve(() => { release(); return holding; })));
|
|
10
|
+
holding.catch(reject);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function withUnusedModelStorage(operation, locks = globalThis.navigator?.locks) {
|
|
15
|
+
if (!locks?.request) throw new Error('Safe storage removal is unavailable in this browser.');
|
|
16
|
+
const names = [MODEL_STORAGE_LOCK, 'reploid:model-acquisition', 'reploid-chat-artifacts-v1:write'];
|
|
17
|
+
const enter = index => index === names.length ? operation() : locks.request(names[index], { ifAvailable: true }, lock => {
|
|
18
|
+
if (!lock) throw new Error('Model files are in use. Stop local models, downloads and file sharing in all Reploid tabs first.');
|
|
19
|
+
return enter(index + 1);
|
|
20
|
+
});
|
|
21
|
+
return enter(0);
|
|
22
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Browser-owned monotonic release checkpoints. Doppler verifies signed history. */
|
|
2
|
+
const requireValue = (value, message) => { if (!value) throw new Error(`Pack release storage: ${message}`); };
|
|
3
|
+
const valid = checkpoint => Number.isSafeInteger(checkpoint?.sequence) && checkpoint.sequence >= 0
|
|
4
|
+
&& (checkpoint.sequence === 0 ? checkpoint.digest === null : /^sha256:[a-f0-9]{64}$/.test(checkpoint.digest));
|
|
5
|
+
const same = (left, right) => left.sequence === right.sequence && left.digest === right.digest;
|
|
6
|
+
|
|
7
|
+
export async function openPackReleaseCheckpoints({ name = 'reploid-pack-releases-v1', indexedDB = globalThis.indexedDB } = {}) {
|
|
8
|
+
requireValue(indexedDB, 'IndexedDB unavailable');
|
|
9
|
+
const opening = indexedDB.open(name, 1);
|
|
10
|
+
opening.onupgradeneeded = () => opening.result.createObjectStore('checkpoints');
|
|
11
|
+
const db = await new Promise((resolve, reject) => {
|
|
12
|
+
opening.onsuccess = () => resolve(opening.result);
|
|
13
|
+
opening.onerror = () => reject(opening.error);
|
|
14
|
+
});
|
|
15
|
+
let closed = false;
|
|
16
|
+
db.onversionchange = () => { closed = true; db.close(); };
|
|
17
|
+
const transact = (key, update) => new Promise((resolve, reject) => {
|
|
18
|
+
requireValue(!closed, 'store closed');
|
|
19
|
+
requireValue(/^sha256:[a-f0-9]{64}$/.test(key), 'bounded namespace digest required');
|
|
20
|
+
const transaction = db.transaction('checkpoints', 'readwrite');
|
|
21
|
+
const store = transaction.objectStore('checkpoints');
|
|
22
|
+
let result;
|
|
23
|
+
let failure;
|
|
24
|
+
transaction.oncomplete = () => resolve(result);
|
|
25
|
+
transaction.onabort = () => reject(failure || transaction.error || new Error('Pack release checkpoint transaction aborted'));
|
|
26
|
+
transaction.onerror = () => {};
|
|
27
|
+
const read = store.get(key);
|
|
28
|
+
read.onsuccess = () => {
|
|
29
|
+
try {
|
|
30
|
+
const current = read.result ?? { sequence: 0, digest: null };
|
|
31
|
+
requireValue(valid(current), 'stored checkpoint is invalid');
|
|
32
|
+
result = update(current);
|
|
33
|
+
store.put(result, key);
|
|
34
|
+
} catch (error) { failure = error; transaction.abort(); }
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
read: key => transact(key, current => current),
|
|
39
|
+
advance(key, expected, next) {
|
|
40
|
+
requireValue(valid(expected) && valid(next) && next.sequence > 0, 'valid checkpoints required');
|
|
41
|
+
const prior = structuredClone(expected);
|
|
42
|
+
const observed = structuredClone(next);
|
|
43
|
+
return transact(key, current => {
|
|
44
|
+
if (same(current, observed)) return current;
|
|
45
|
+
requireValue(same(current, prior), 'checkpoint changed during verification');
|
|
46
|
+
requireValue(observed.sequence > current.sequence, 'rollback or fork rejected');
|
|
47
|
+
return observed;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
close() { closed = true; db.close(); }
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** Local execution/evidence bridge. This module neither sends inputs nor authorizes delegation. */
|
|
2
|
+
import { assertPackSession, assertPackExecutionEvidence, hashDopplerEvidence } from '../../../shared/pool/executable-pack.js';
|
|
3
|
+
import { createPackOperationRegistry } from '../../../shared/pool/pack-operation-adapters.js';
|
|
4
|
+
import { assertOperationLimits } from '../../../shared/pool/pack-operation-policy.js';
|
|
5
|
+
import { resolveDopplerOperationContract } from '../../../shared/config/doppler-execution-contracts.js';
|
|
6
|
+
import { assertDopplerGenerationContract, DopplerRuntimeService } from '../../../shared/infrastructure/doppler-runtime-service.js';
|
|
7
|
+
import { dopplerOperationEvents } from '../../../shared/vendor/reploid/adapters/doppler-session.js';
|
|
8
|
+
|
|
9
|
+
const requireValue = (value, message) => { if (!value) throw new Error(`Pack operation: ${message}`); };
|
|
10
|
+
const equal = async (left, right) => await hashDopplerEvidence(left) === await hashDopplerEvidence(right);
|
|
11
|
+
|
|
12
|
+
export function snapshotPackOperationData(value, depth = 0) {
|
|
13
|
+
requireValue(depth <= 64, 'observation depth exceeded');
|
|
14
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
15
|
+
if (typeof value === 'number') { requireValue(Number.isFinite(value), 'non-finite observation'); return value; }
|
|
16
|
+
requireValue(value && typeof value === 'object', 'JSON observation required');
|
|
17
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) value = Array.from(value);
|
|
18
|
+
requireValue(Array.isArray(value) || Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null, 'unsupported observation object');
|
|
19
|
+
return Object.freeze(Array.isArray(value)
|
|
20
|
+
? Array.from(value, (item) => snapshotPackOperationData(item, depth + 1))
|
|
21
|
+
: Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshotPackOperationData(item, depth + 1)])));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function operationAdapter(registry, operation) {
|
|
25
|
+
requireValue(operation && Object.hasOwn(registry, operation.name) && registry[operation.name].version === operation.version, 'unknown operation or version');
|
|
26
|
+
return registry[operation.name];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function assertPackOperationReceipt(binding, receipt, { request, output, runtimeVersion }) {
|
|
30
|
+
await assertPackExecutionEvidence(binding, receipt);
|
|
31
|
+
requireValue(receipt.schema === resolveDopplerOperationContract(binding.schema, request.schema).receiptSchema, 'receipt schema mismatch');
|
|
32
|
+
const { receiptDigest, ...payload } = receipt;
|
|
33
|
+
requireValue(receiptDigest === await hashDopplerEvidence(payload), 'receipt digest mismatch');
|
|
34
|
+
requireValue(typeof runtimeVersion === 'string' && runtimeVersion.length > 0 && receipt.runtimeVersion === runtimeVersion, 'runtime version mismatch');
|
|
35
|
+
requireValue(binding.requiredOperation === request.operation.name && await equal(receipt.operation, request.operation), 'receipt operation mismatch');
|
|
36
|
+
requireValue(receipt.requestHash === await hashDopplerEvidence(request), 'request mismatch');
|
|
37
|
+
requireValue(receipt.assignmentHash === (request.assignment === null ? null : await hashDopplerEvidence(request.assignment)), 'assignment mismatch');
|
|
38
|
+
requireValue(receipt.inputHash === await hashDopplerEvidence({ input: request.input, options: request.options }), 'input mismatch');
|
|
39
|
+
requireValue(receipt.outputHash === await hashDopplerEvidence(output), 'output mismatch');
|
|
40
|
+
if (request.adapterSet?.length) {
|
|
41
|
+
requireValue(Array.isArray(receipt.adapterReceipts) && receipt.adapterReceipts.length === request.adapterSet.length, 'adapter execution evidence required');
|
|
42
|
+
for (const [index, entry] of request.adapterSet.entries()) {
|
|
43
|
+
const observed = receipt.adapterReceipts[index];
|
|
44
|
+
requireValue(observed.identity === entry.identity && observed.sourceDigest === entry.artifact.hash
|
|
45
|
+
&& observed.requestDigest === await hashDopplerEvidence(entry)
|
|
46
|
+
&& observed.runtimeIdentity?.schema === 'doppler.lora-execution-identity/v1'
|
|
47
|
+
&& /^sha256:[a-f0-9]{64}$/.test(observed.runtimeIdentity.digest), 'adapter execution identity mismatch');
|
|
48
|
+
}
|
|
49
|
+
} else requireValue(!receipt.adapterReceipts?.length, 'unexpected active adapter');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function assertPackOperationRequest(binding, request, registry = createPackOperationRegistry()) {
|
|
53
|
+
const adapter = operationAdapter(registry, request.operation);
|
|
54
|
+
requireValue(request.schema === resolveDopplerOperationContract(binding.schema, request.schema).requestSchema && binding.requiredOperation === request.operation.name, 'request operation binding mismatch');
|
|
55
|
+
requireValue(Object.keys(request).every((key) => ['schema', 'operation', 'input', 'options', 'assignment', 'limits', 'adapterSet'].includes(key)), 'unknown request field');
|
|
56
|
+
requireValue(request.assignment === null || (request.assignment && typeof request.assignment === 'object' && !Array.isArray(request.assignment)), 'explicit assignment or null required');
|
|
57
|
+
for (const key of ['maxInputBytes', 'maxOutputBytes', 'deadlineAt']) requireValue(Number.isSafeInteger(request.limits?.[key]) && request.limits[key] > 0, `${key} required`);
|
|
58
|
+
assertOperationLimits(request.limits, adapter.definition);
|
|
59
|
+
requireValue(new TextEncoder().encode(JSON.stringify({ input: request.input, options: request.options })).length <= request.limits.maxInputBytes, 'input byte limit exceeded');
|
|
60
|
+
adapter.validateRequest(request);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function createPackOperationStream(binding, request, runtimeVersion, runtimeService = DopplerRuntimeService) {
|
|
64
|
+
return resolveDopplerOperationContract(binding.schema, request.schema).incremental
|
|
65
|
+
? runtimeService.createStreamAccumulator(request, { bindingSchema: binding.schema, runtimeVersion }) : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Shared stream verifier for local execution and signed remote delivery. */
|
|
69
|
+
export async function assertPackOperationEvent({ binding, request, runtimeVersion, event,
|
|
70
|
+
eventIndex, previousEventDigest, registry = createPackOperationRegistry(), streamAccumulator = null }) {
|
|
71
|
+
const adapter = operationAdapter(registry, request.operation);
|
|
72
|
+
const contract = resolveDopplerOperationContract(binding.schema, request.schema);
|
|
73
|
+
const { eventDigest, ...payload } = event;
|
|
74
|
+
requireValue(event.schema === contract.eventSchema && ['partial', 'completed'].includes(event.status), 'event schema or status mismatch');
|
|
75
|
+
requireValue(event.status !== 'partial' || adapter.definition.streaming.partial, 'operation policy forbids partial output');
|
|
76
|
+
if (contract.incremental) {
|
|
77
|
+
requireValue(streamAccumulator, 'incremental stream reconstruction is required');
|
|
78
|
+
requireValue(event.eventIndex === eventIndex && event.previousEventDigest === previousEventDigest, 'duplicate, missing, or reordered event');
|
|
79
|
+
streamAccumulator.accept(event);
|
|
80
|
+
if (event.status === 'completed') {
|
|
81
|
+
adapter.validateOutput(event.output, request, { completed: true });
|
|
82
|
+
await assertPackOperationReceipt(binding, event.receipt, { request, output: event.output, runtimeVersion });
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
requireValue(eventDigest === await hashDopplerEvidence(payload), 'event digest mismatch');
|
|
87
|
+
requireValue(event.eventIndex === eventIndex && event.previousEventDigest === previousEventDigest, 'duplicate, missing, or reordered event');
|
|
88
|
+
requireValue(event.requestHash === await hashDopplerEvidence(request)
|
|
89
|
+
&& event.assignmentHash === (request.assignment === null ? null : await hashDopplerEvidence(request.assignment))
|
|
90
|
+
&& await equal(event.operation, request.operation), 'event belongs to another request');
|
|
91
|
+
requireValue(new TextEncoder().encode(JSON.stringify(event.output)).length <= request.limits.maxOutputBytes, 'output byte limit exceeded');
|
|
92
|
+
adapter.validateOutput(event.output, request, { completed: event.status === 'completed' });
|
|
93
|
+
if (event.status === 'completed') await assertPackOperationReceipt(binding, event.receipt, { request, output: event.output, runtimeVersion });
|
|
94
|
+
else requireValue(!Object.hasOwn(event, 'receipt'), 'partial output cannot carry completion evidence');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function runPackOperation({ binding: bindingInput, session, request: requestInput, runtimeVersion,
|
|
98
|
+
registry = createPackOperationRegistry(), signal = null, adapterArtifactStore = null, onPartial = null, beforeExecute = null, assertCurrent = async () => {}, runtimeService = DopplerRuntimeService }) {
|
|
99
|
+
const binding = snapshotPackOperationData(bindingInput);
|
|
100
|
+
const request = snapshotPackOperationData(requestInput);
|
|
101
|
+
assertPackOperationRequest(binding, request, registry);
|
|
102
|
+
const definition = operationAdapter(registry, request.operation).definition;
|
|
103
|
+
requireValue(request.limits.deadlineAt - Date.now() <= definition.maximumLimits.maxJobMs, 'deadline exceeds configured operation limit');
|
|
104
|
+
requireValue(typeof runtimeVersion === 'string' && runtimeVersion.length > 0, 'runtime version required');
|
|
105
|
+
const current = async () => {
|
|
106
|
+
signal?.throwIfAborted();
|
|
107
|
+
requireValue(Date.now() < request.limits.deadlineAt, 'deadline exceeded');
|
|
108
|
+
await assertCurrent();
|
|
109
|
+
signal?.throwIfAborted();
|
|
110
|
+
requireValue(Date.now() < request.limits.deadlineAt, 'deadline exceeded');
|
|
111
|
+
};
|
|
112
|
+
await current();
|
|
113
|
+
await assertPackSession(binding, session);
|
|
114
|
+
if (request.operation.name === 'generate') assertDopplerGenerationContract(session);
|
|
115
|
+
requireValue(typeof session.executeOperation === 'function', 'public executeOperation is required; no legacy operation fallback');
|
|
116
|
+
const streamAccumulator = await createPackOperationStream(binding, request, runtimeVersion, runtimeService);
|
|
117
|
+
await beforeExecute?.();
|
|
118
|
+
await current();
|
|
119
|
+
let previousEventDigest = null;
|
|
120
|
+
let eventCount = 0;
|
|
121
|
+
let streamBytes = 0;
|
|
122
|
+
let completed = null;
|
|
123
|
+
for await (const received of dopplerOperationEvents(session, request, { signal, ...(request.adapterSet?.length ? { adapterArtifactStore } : {}) })) {
|
|
124
|
+
await current();
|
|
125
|
+
requireValue(!completed, 'output after completion');
|
|
126
|
+
const event = snapshotPackOperationData(received);
|
|
127
|
+
streamBytes += new TextEncoder().encode(JSON.stringify(event)).length;
|
|
128
|
+
requireValue(eventCount < definition.maximumLimits.maxEvents && streamBytes <= definition.maximumLimits.maxStreamBytes,
|
|
129
|
+
'configured operation stream limit exceeded');
|
|
130
|
+
await assertPackOperationEvent({ binding, request, runtimeVersion, event, eventIndex: eventCount, previousEventDigest, registry, streamAccumulator });
|
|
131
|
+
await current();
|
|
132
|
+
if (event.status === 'completed') {
|
|
133
|
+
completed = event;
|
|
134
|
+
} else {
|
|
135
|
+
await onPartial?.(event);
|
|
136
|
+
}
|
|
137
|
+
eventCount++;
|
|
138
|
+
previousEventDigest = event.eventDigest;
|
|
139
|
+
}
|
|
140
|
+
// Do not expose completion until iterator cleanup and the current-attempt check finish.
|
|
141
|
+
await current();
|
|
142
|
+
requireValue(completed, 'stream ended without completion');
|
|
143
|
+
streamAccumulator?.finish();
|
|
144
|
+
return Object.freeze({ request, output: completed.output, receipt: completed.receipt, eventCount, finalEventDigest: previousEventDigest, completion: completed });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function assessPackOperation({ execution, reference: referenceInput, policy: policyInput, registry = createPackOperationRegistry() }) {
|
|
148
|
+
const policy = snapshotPackOperationData(policyInput);
|
|
149
|
+
const reference = snapshotPackOperationData(referenceInput);
|
|
150
|
+
const adapter = operationAdapter(registry, execution.request.operation);
|
|
151
|
+
requireValue(policy.schema === 'poolday.operation-comparison/v1' && await equal(policy.operation, execution.request.operation), 'comparison operation mismatch');
|
|
152
|
+
const policyDigest = await hashDopplerEvidence(policy);
|
|
153
|
+
requireValue(execution.request.assignment?.comparisonPolicyDigest === policyDigest, 'comparison policy was not frozen in the assignment');
|
|
154
|
+
requireValue(policy.referenceDigest === await hashDopplerEvidence(reference), 'reference digest mismatch');
|
|
155
|
+
adapter.validateOutput(reference, execution.request, { completed: true });
|
|
156
|
+
adapter.validateOutput(execution.output, execution.request, { completed: true });
|
|
157
|
+
const accepted = adapter.compare(execution.output, reference, policy);
|
|
158
|
+
requireValue(typeof accepted === 'boolean', 'comparison must return a decision');
|
|
159
|
+
return Object.freeze({ schema: 'poolday.operation-assessment/v1', accepted, policyDigest,
|
|
160
|
+
receiptDigest: execution.receipt.receiptDigest, claim: 'bounded-reference-comparison' });
|
|
161
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { openPackReleaseCheckpoints } from '../infrastructure/pack-release-storage.js';
|
|
2
|
+
import { hashDopplerEvidence } from '../../../shared/pool/executable-pack.js';
|
|
3
|
+
import { snapshotPackOperationData } from './pack-operation.js';
|
|
4
|
+
import { resolveDopplerExecutionContract } from '../../../shared/config/doppler-execution-contracts.js';
|
|
5
|
+
|
|
6
|
+
const requireValue = (value, message) => { if (!value) throw new Error(`Pack release policy: ${message}`); };
|
|
7
|
+
|
|
8
|
+
/** The application retains anti-rollback state; Doppler retains verification authority. */
|
|
9
|
+
export async function prepareLocalPackRelease({ model, now = Date.now, openCheckpoints = openPackReleaseCheckpoints }) {
|
|
10
|
+
const options = model.packOpenOptions;
|
|
11
|
+
if (!resolveDopplerExecutionContract(model.executablePack.schema).releaseHistory) return { options, assertCurrent() {}, close() {} };
|
|
12
|
+
const authority = options.releaseEvents?.[0]?.signature?.authority;
|
|
13
|
+
const applicationId = model.application?.applicationId;
|
|
14
|
+
requireValue(typeof authority === 'string' && authority.length > 0 && typeof applicationId === 'string'
|
|
15
|
+
&& applicationId.length > 0, 'explicit release authority and application required');
|
|
16
|
+
requireValue(Number.isSafeInteger(options.releasePolicy?.minimumSequence)
|
|
17
|
+
&& options.releasePolicy.minimumSequence > 0, 'explicit minimum release sequence required');
|
|
18
|
+
const namespace = await hashDopplerEvidence({ authority, applicationId });
|
|
19
|
+
const storage = await openCheckpoints();
|
|
20
|
+
try {
|
|
21
|
+
let checkpoint = await storage.read(namespace);
|
|
22
|
+
const minimumSequence = Math.max(checkpoint.sequence, options.releasePolicy.minimumSequence);
|
|
23
|
+
const checkTime = session => {
|
|
24
|
+
const lifecycle = session.verification?.lifecycle;
|
|
25
|
+
requireValue(lifecycle && lifecycle.event.sequence >= minimumSequence, 'verified lifecycle required');
|
|
26
|
+
requireValue(Date.parse(lifecycle.event.issuedAtUtc) <= now(), 'release issuance is in the future');
|
|
27
|
+
requireValue(Date.parse(lifecycle.event.expiresAtUtc) > now(), 'release eligibility expired');
|
|
28
|
+
return lifecycle;
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
options: { ...options, releasePolicy: { ...options.releasePolicy, minimumSequence,
|
|
32
|
+
now: new Date(now()).toISOString(), checkpoint: snapshotPackOperationData(checkpoint) },
|
|
33
|
+
async persistReleaseCheckpoint(next) { checkpoint = await storage.advance(namespace, checkpoint, next); } },
|
|
34
|
+
checkTime,
|
|
35
|
+
async assertCurrent(session) {
|
|
36
|
+
const lifecycle = checkTime(session);
|
|
37
|
+
const saved = await storage.read(namespace);
|
|
38
|
+
requireValue(lifecycle.checkpoint.sequence === saved.sequence && lifecycle.checkpoint.digest === saved.digest,
|
|
39
|
+
'release checkpoint changed; reopen with current signed history');
|
|
40
|
+
},
|
|
41
|
+
close: () => storage.close()
|
|
42
|
+
};
|
|
43
|
+
} catch (error) { storage.close(); throw error; }
|
|
44
|
+
}
|