@agentplat/mesh-sim 0.3.0-beta.2 → 0.3.0-beta.5
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/README.md +447 -2
- package/dist/collective-closed-loop-action.d.ts +80 -0
- package/dist/collective-closed-loop-action.d.ts.map +1 -0
- package/dist/collective-closed-loop-action.js +471 -0
- package/dist/collective-closed-loop-action.js.map +1 -0
- package/dist/collective-closed-loop-contracts.d.ts +75 -0
- package/dist/collective-closed-loop-contracts.d.ts.map +1 -0
- package/dist/collective-closed-loop-contracts.js +420 -0
- package/dist/collective-closed-loop-contracts.js.map +1 -0
- package/dist/collective-closed-loop-fault-matrix.d.ts +101 -0
- package/dist/collective-closed-loop-fault-matrix.d.ts.map +1 -0
- package/dist/collective-closed-loop-fault-matrix.js +253 -0
- package/dist/collective-closed-loop-fault-matrix.js.map +1 -0
- package/dist/collective-closed-loop-recovery.d.ts +70 -0
- package/dist/collective-closed-loop-recovery.d.ts.map +1 -0
- package/dist/collective-closed-loop-recovery.js +823 -0
- package/dist/collective-closed-loop-recovery.js.map +1 -0
- package/dist/collective-closed-loop-reference.d.ts +54 -0
- package/dist/collective-closed-loop-reference.d.ts.map +1 -0
- package/dist/collective-closed-loop-reference.js +1696 -0
- package/dist/collective-closed-loop-reference.js.map +1 -0
- package/dist/collective-closed-loop-replanning.d.ts +27 -0
- package/dist/collective-closed-loop-replanning.d.ts.map +1 -0
- package/dist/collective-closed-loop-replanning.js +151 -0
- package/dist/collective-closed-loop-replanning.js.map +1 -0
- package/dist/collective-closed-loop-resilience-campaign.d.ts +22 -0
- package/dist/collective-closed-loop-resilience-campaign.d.ts.map +1 -0
- package/dist/collective-closed-loop-resilience-campaign.js +196 -0
- package/dist/collective-closed-loop-resilience-campaign.js.map +1 -0
- package/dist/collective-closed-loop-resilience-contracts.d.ts +138 -0
- package/dist/collective-closed-loop-resilience-contracts.d.ts.map +1 -0
- package/dist/collective-closed-loop-resilience-contracts.js +677 -0
- package/dist/collective-closed-loop-resilience-contracts.js.map +1 -0
- package/dist/collective-closed-loop-runner.d.ts +133 -0
- package/dist/collective-closed-loop-runner.d.ts.map +1 -0
- package/dist/collective-closed-loop-runner.js +1429 -0
- package/dist/collective-closed-loop-runner.js.map +1 -0
- package/dist/collective-closed-loop-runtime.d.ts +95 -0
- package/dist/collective-closed-loop-runtime.d.ts.map +1 -0
- package/dist/collective-closed-loop-runtime.js +1192 -0
- package/dist/collective-closed-loop-runtime.js.map +1 -0
- package/dist/collective-effect-provenance.d.ts +5 -0
- package/dist/collective-effect-provenance.d.ts.map +1 -0
- package/dist/collective-effect-provenance.js +32 -0
- package/dist/collective-effect-provenance.js.map +1 -0
- package/dist/collective-environment.d.ts +49 -0
- package/dist/collective-environment.d.ts.map +1 -0
- package/dist/collective-environment.js +907 -0
- package/dist/collective-environment.js.map +1 -0
- package/dist/collective-progressive-scale-overlay.d.ts +39 -0
- package/dist/collective-progressive-scale-overlay.d.ts.map +1 -0
- package/dist/collective-progressive-scale-overlay.js +147 -0
- package/dist/collective-progressive-scale-overlay.js.map +1 -0
- package/dist/collective-progressive-scale.d.ts +202 -0
- package/dist/collective-progressive-scale.d.ts.map +1 -0
- package/dist/collective-progressive-scale.js +755 -0
- package/dist/collective-progressive-scale.js.map +1 -0
- package/dist/collective-statistical-campaign-aggregation.d.ts +97 -0
- package/dist/collective-statistical-campaign-aggregation.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-aggregation.js +446 -0
- package/dist/collective-statistical-campaign-aggregation.js.map +1 -0
- package/dist/collective-statistical-campaign-artifact-stream.d.ts +66 -0
- package/dist/collective-statistical-campaign-artifact-stream.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-artifact-stream.js +207 -0
- package/dist/collective-statistical-campaign-artifact-stream.js.map +1 -0
- package/dist/collective-statistical-campaign-bundle.d.ts +151 -0
- package/dist/collective-statistical-campaign-bundle.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-bundle.js +885 -0
- package/dist/collective-statistical-campaign-bundle.js.map +1 -0
- package/dist/collective-statistical-campaign-executor.d.ts +131 -0
- package/dist/collective-statistical-campaign-executor.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-executor.js +856 -0
- package/dist/collective-statistical-campaign-executor.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-analysis.d.ts +85 -0
- package/dist/collective-statistical-campaign-normative-analysis.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-analysis.js +483 -0
- package/dist/collective-statistical-campaign-normative-analysis.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-operation.d.ts +89 -0
- package/dist/collective-statistical-campaign-normative-operation.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-operation.js +266 -0
- package/dist/collective-statistical-campaign-normative-operation.js.map +1 -0
- package/dist/collective-statistical-campaign-normative-registry.d.ts +24 -0
- package/dist/collective-statistical-campaign-normative-registry.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-normative-registry.js +163 -0
- package/dist/collective-statistical-campaign-normative-registry.js.map +1 -0
- package/dist/collective-statistical-campaign-registered-adapter.d.ts +16 -0
- package/dist/collective-statistical-campaign-registered-adapter.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-registered-adapter.js +518 -0
- package/dist/collective-statistical-campaign-registered-adapter.js.map +1 -0
- package/dist/collective-statistical-campaign-scale.d.ts +85 -0
- package/dist/collective-statistical-campaign-scale.d.ts.map +1 -0
- package/dist/collective-statistical-campaign-scale.js +349 -0
- package/dist/collective-statistical-campaign-scale.js.map +1 -0
- package/dist/collective-trace-journal.d.ts +20 -0
- package/dist/collective-trace-journal.d.ts.map +1 -0
- package/dist/collective-trace-journal.js +105 -0
- package/dist/collective-trace-journal.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/multi-domain-environment-contracts.d.ts +122 -0
- package/dist/multi-domain-environment-contracts.d.ts.map +1 -0
- package/dist/multi-domain-environment-contracts.js +14 -0
- package/dist/multi-domain-environment-contracts.js.map +1 -0
- package/dist/multi-domain-environment-runtime.d.ts +25 -0
- package/dist/multi-domain-environment-runtime.d.ts.map +1 -0
- package/dist/multi-domain-environment-runtime.js +493 -0
- package/dist/multi-domain-environment-runtime.js.map +1 -0
- package/dist/multi-domain-environment-validation.d.ts +33 -0
- package/dist/multi-domain-environment-validation.d.ts.map +1 -0
- package/dist/multi-domain-environment-validation.js +415 -0
- package/dist/multi-domain-environment-validation.js.map +1 -0
- package/dist/multi-domain-environment.d.ts +4 -0
- package/dist/multi-domain-environment.d.ts.map +1 -0
- package/dist/multi-domain-environment.js +4 -0
- package/dist/multi-domain-environment.js.map +1 -0
- package/dist/reducer-scenario.d.ts.map +1 -1
- package/dist/reducer-scenario.js +67 -19
- package/dist/reducer-scenario.js.map +1 -1
- package/dist/scalable-evaluation-contracts.d.ts +835 -0
- package/dist/scalable-evaluation-contracts.d.ts.map +1 -0
- package/dist/scalable-evaluation-contracts.js +22 -0
- package/dist/scalable-evaluation-contracts.js.map +1 -0
- package/dist/scalable-evaluation-durability.d.ts +16 -0
- package/dist/scalable-evaluation-durability.d.ts.map +1 -0
- package/dist/scalable-evaluation-durability.js +339 -0
- package/dist/scalable-evaluation-durability.js.map +1 -0
- package/dist/scalable-evaluation-evidence.d.ts +93 -0
- package/dist/scalable-evaluation-evidence.d.ts.map +1 -0
- package/dist/scalable-evaluation-evidence.js +470 -0
- package/dist/scalable-evaluation-evidence.js.map +1 -0
- package/dist/scalable-evaluation-reference-integrated.d.ts +87 -0
- package/dist/scalable-evaluation-reference-integrated.d.ts.map +1 -0
- package/dist/scalable-evaluation-reference-integrated.js +1318 -0
- package/dist/scalable-evaluation-reference-integrated.js.map +1 -0
- package/dist/scalable-evaluation-runner.d.ts +8 -0
- package/dist/scalable-evaluation-runner.d.ts.map +1 -0
- package/dist/scalable-evaluation-runner.js +1810 -0
- package/dist/scalable-evaluation-runner.js.map +1 -0
- package/dist/scalable-evaluation-runtime.d.ts +86 -0
- package/dist/scalable-evaluation-runtime.d.ts.map +1 -0
- package/dist/scalable-evaluation-runtime.js +1310 -0
- package/dist/scalable-evaluation-runtime.js.map +1 -0
- package/dist/scalable-evaluation-validation.d.ts +59 -0
- package/dist/scalable-evaluation-validation.d.ts.map +1 -0
- package/dist/scalable-evaluation-validation.js +377 -0
- package/dist/scalable-evaluation-validation.js.map +1 -0
- package/dist/scalable-evaluation.d.ts +8 -0
- package/dist/scalable-evaluation.d.ts.map +1 -0
- package/dist/scalable-evaluation.js +8 -0
- package/dist/scalable-evaluation.js.map +1 -0
- package/dist/sharded-simulation-contracts.d.ts +240 -0
- package/dist/sharded-simulation-contracts.d.ts.map +1 -0
- package/dist/sharded-simulation-contracts.js +199 -0
- package/dist/sharded-simulation-contracts.js.map +1 -0
- package/dist/sharded-simulation-runtime.d.ts +64 -0
- package/dist/sharded-simulation-runtime.d.ts.map +1 -0
- package/dist/sharded-simulation-runtime.js +755 -0
- package/dist/sharded-simulation-runtime.js.map +1 -0
- package/dist/sharded-simulation-validation.d.ts +10 -0
- package/dist/sharded-simulation-validation.d.ts.map +1 -0
- package/dist/sharded-simulation-validation.js +250 -0
- package/dist/sharded-simulation-validation.js.map +1 -0
- package/dist/sharded-simulation.d.ts +46 -0
- package/dist/sharded-simulation.d.ts.map +1 -0
- package/dist/sharded-simulation.js +32 -0
- package/dist/sharded-simulation.js.map +1 -0
- package/dist/verified-benchmark-contracts.d.ts +132 -0
- package/dist/verified-benchmark-contracts.d.ts.map +1 -0
- package/dist/verified-benchmark-contracts.js +2 -0
- package/dist/verified-benchmark-contracts.js.map +1 -0
- package/dist/verified-benchmark-runtime.d.ts +20 -0
- package/dist/verified-benchmark-runtime.d.ts.map +1 -0
- package/dist/verified-benchmark-runtime.js +199 -0
- package/dist/verified-benchmark-runtime.js.map +1 -0
- package/dist/verified-benchmark-validation.d.ts +64 -0
- package/dist/verified-benchmark-validation.d.ts.map +1 -0
- package/dist/verified-benchmark-validation.js +481 -0
- package/dist/verified-benchmark-validation.js.map +1 -0
- package/dist/verified-benchmark.d.ts +4 -0
- package/dist/verified-benchmark.d.ts.map +1 -0
- package/dist/verified-benchmark.js +4 -0
- package/dist/verified-benchmark.js.map +1 -0
- package/package.json +18 -8
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
import { acceptDelegationMandateV1, createCollectiveAuthorityStateV1, } from '@agentplat/collective-control';
|
|
2
|
+
import { DELEGATION_MANDATE_REFERENCE_PREFIX_V1, createGovernedMeshObjectiveInboundProcessorV1, } from '@agentplat/collective-control/mesh';
|
|
3
|
+
import { createPlanningReducerCommandV1, createPlanningReducerStateV1, digestPlanningJsonV1, reducePlanningCommandV1, validateMissionIntentV1, validateMissionObservationV1, validatePlanFragmentProposalV1, validatePlanSelectionPolicyV1, } from '@agentplat/collective-planning';
|
|
4
|
+
import { validateCollectiveProtectedEffectAttemptV1, validateCollectiveProtectedEffectReceiptV1, } from '@agentplat/collective-planning/evaluation';
|
|
5
|
+
import { InMemoryPlanningFragmentRepositoryV1, PLANNING_MESH_CAPABILITY_PROFILE_V1, PLANNING_WORK_EXTENSION_KEY_V1, createPlanningAdaptiveRoleV1, createPlanningLocalWorkProjectionV1, planningWorkItemIdV1, selectPlanningOfferRecipientsV1, } from '@agentplat/collective-planning/mesh';
|
|
6
|
+
import { DEFAULT_MESH_CRYPTO_POLICY, createWebCryptoMeshEnvelopeSigner, verifyMeshEnvelope, } from '@agentplat/mesh-crypto';
|
|
7
|
+
import { canonicalizeMeshPayload, validateSignedMeshEnvelope, } from '@agentplat/mesh-protocol';
|
|
8
|
+
import { createMeshAllocationInboundProcessor, createMeshAllocationInboundRuntimeState, createMeshAllocationRuntimeState, createMeshAllocationState, createMeshCoordinationInboundState, createMeshCoordinationState, createMeshDiscoveryRuntimeState, createMeshDiscoveryState, createMeshObjectiveInboundProcessor, createMeshObjectiveInboundRuntimeState, createMeshObjectiveWorkRuntimeState, createMeshObjectiveWorkState, evaluateMeshAllocationCommand, evaluateMeshObjectiveWorkCommand, evaluateVerifiedMeshDiscoveryEnvelope, restoreMeshAllocationState, restoreMeshDiscoveryState, restoreMeshObjectiveWorkState, selectMeshAllocationBid, } from '@agentplat/mesh/coordination';
|
|
9
|
+
import { collectiveTraceJournalForOwnerV2 } from './collective-trace-journal.js';
|
|
10
|
+
import { assertCollectiveEffectReceiptProvenanceV1 } from './collective-effect-provenance.js';
|
|
11
|
+
const MINIMUM_PEERS = 3;
|
|
12
|
+
// Keep the runtime admission bound aligned with the public reference scenario
|
|
13
|
+
// and registered empirical campaign, which both support scales through 500.
|
|
14
|
+
const MAXIMUM_PEERS = 500;
|
|
15
|
+
const MAXIMUM_NEIGHBORS = MAXIMUM_PEERS - 1;
|
|
16
|
+
const MAXIMUM_RECIPIENTS = 32;
|
|
17
|
+
const MAXIMUM_INTERACTIONS = 5_000;
|
|
18
|
+
const WIRE_VERSION = 0;
|
|
19
|
+
/**
|
|
20
|
+
* Binds externally managed key handles to the closed-loop runtime. Reusing the
|
|
21
|
+
* returned object reuses exactly the same signing authority during replay.
|
|
22
|
+
*/
|
|
23
|
+
export function createCollectiveClosedLoopRuntimeRunnerV1(input) {
|
|
24
|
+
const crypto = input.crypto ?? globalThis.crypto;
|
|
25
|
+
if (!crypto?.subtle)
|
|
26
|
+
throw new TypeError('closed_loop_crypto_unavailable');
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
signer: input.signer ??
|
|
29
|
+
createWebCryptoMeshEnvelopeSigner({
|
|
30
|
+
signingPolicy: { allowedWireVersions: [WIRE_VERSION] },
|
|
31
|
+
}),
|
|
32
|
+
resolver: input.resolver,
|
|
33
|
+
cryptoPolicy: input.cryptoPolicy ?? DEFAULT_MESH_CRYPTO_POLICY,
|
|
34
|
+
crypto,
|
|
35
|
+
privateKeys: Object.freeze({ ...input.privateKeys }),
|
|
36
|
+
mandateVerification: Object.freeze({ ...input.mandateVerification }),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Executes the nominal peer-local planning and Mesh allocation spine. It stops
|
|
41
|
+
* after a real checkpoint while assignment authority remains active.
|
|
42
|
+
*/
|
|
43
|
+
export async function runCollectiveClosedLoopMeshRuntimeV1(rawInput) {
|
|
44
|
+
const input = validateInput(rawInput);
|
|
45
|
+
const owner = input.peers[0];
|
|
46
|
+
const context = {
|
|
47
|
+
input,
|
|
48
|
+
owner,
|
|
49
|
+
peersById: new Map(input.peers.map((peer) => [peer.peerId, peer])),
|
|
50
|
+
sequences: new Map(),
|
|
51
|
+
baseWallTimeMs: Date.parse(input.missionIntent.validFrom),
|
|
52
|
+
logicalTimeMs: 1,
|
|
53
|
+
interactionCount: 0,
|
|
54
|
+
};
|
|
55
|
+
const authorityState = installMandate(input);
|
|
56
|
+
const planning = createPlanning(context);
|
|
57
|
+
let planningStates = planning.states;
|
|
58
|
+
const projection = planning.projection;
|
|
59
|
+
const repository = new InMemoryPlanningFragmentRepositoryV1();
|
|
60
|
+
await repository.put(projection.repositoryRecord);
|
|
61
|
+
const retainedProjection = await repository.get(projection.repositoryRecord.contentReference);
|
|
62
|
+
if (!retainedProjection ||
|
|
63
|
+
retainedProjection.fragmentDigest !==
|
|
64
|
+
projection.repositoryRecord.fragmentDigest)
|
|
65
|
+
throw new Error('closed_loop_planning_repository_mismatch');
|
|
66
|
+
let meshStates = Object.fromEntries(input.peers.map((peer) => [peer.peerId, createPeerMeshState(context, peer)]));
|
|
67
|
+
const allocationInbound = createMeshAllocationInboundProcessor({
|
|
68
|
+
resolver: input.runner.resolver,
|
|
69
|
+
cryptoPolicy: input.runner.cryptoPolicy,
|
|
70
|
+
crypto: input.runner.crypto,
|
|
71
|
+
supportedCriticalExtensions: [PLANNING_WORK_EXTENSION_KEY_V1],
|
|
72
|
+
});
|
|
73
|
+
const discoverablePeers = owner.neighborPeerIds
|
|
74
|
+
.map((peerId) => context.peersById.get(peerId))
|
|
75
|
+
.filter((peer) => peer !== undefined && peer.peerId !== owner.peerId);
|
|
76
|
+
for (const peer of discoverablePeers) {
|
|
77
|
+
meshStates[owner.peerId] = await discoverPeer(context, meshStates[owner.peerId], peer);
|
|
78
|
+
}
|
|
79
|
+
const discoveredPeerIds = Object.freeze(discoverablePeers.map((peer) => peer.peerId).sort());
|
|
80
|
+
const requiredCapabilityKeys = projection.work.requiredCapabilityKeys;
|
|
81
|
+
const recipients = selectPlanningOfferRecipientsV1({
|
|
82
|
+
discovery: meshStates[owner.peerId].discovery,
|
|
83
|
+
logicalTimeMs: context.logicalTimeMs,
|
|
84
|
+
verifiedAt: wallTime(context, 1_000),
|
|
85
|
+
localSupportedCriticalExtensions: [PLANNING_WORK_EXTENSION_KEY_V1],
|
|
86
|
+
requiredCapabilityKeys,
|
|
87
|
+
maximumRecipients: Math.min(MAXIMUM_RECIPIENTS, discoverablePeers.length),
|
|
88
|
+
});
|
|
89
|
+
if (recipients.length === 0)
|
|
90
|
+
throw new Error('closed_loop_no_eligible_recipients');
|
|
91
|
+
const objectiveEnvelope = await signEnvelope(context, {
|
|
92
|
+
senderPeerId: owner.peerId,
|
|
93
|
+
type: 'objective.announce',
|
|
94
|
+
audience: { kind: 'mesh', topic: 'objective' },
|
|
95
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
96
|
+
payload: objectivePayload(context, recipients.map((recipient) => recipient.peerId)),
|
|
97
|
+
sentAtOffsetMs: 2_000,
|
|
98
|
+
expiresAtOffsetMs: 120_000,
|
|
99
|
+
});
|
|
100
|
+
const objectiveInbound = createGovernedMeshObjectiveInboundProcessorV1({
|
|
101
|
+
processor: createMeshObjectiveInboundProcessor({
|
|
102
|
+
resolver: input.runner.resolver,
|
|
103
|
+
cryptoPolicy: input.runner.cryptoPolicy,
|
|
104
|
+
crypto: input.runner.crypto,
|
|
105
|
+
}),
|
|
106
|
+
authority: { read: () => authorityState },
|
|
107
|
+
});
|
|
108
|
+
// Only the owner and the selected offer recipients need this objective in
|
|
109
|
+
// the bounded planning round. Broadcasting it to every member of a
|
|
110
|
+
// 500-peer mesh is observationally irrelevant to the resulting allocation.
|
|
111
|
+
const objectiveRecipients = new Set([
|
|
112
|
+
owner.peerId,
|
|
113
|
+
...recipients.map((recipient) => recipient.peerId),
|
|
114
|
+
]);
|
|
115
|
+
for (const peer of input.peers) {
|
|
116
|
+
if (!objectiveRecipients.has(peer.peerId))
|
|
117
|
+
continue;
|
|
118
|
+
const receivedAt = advance(context);
|
|
119
|
+
const admitted = await objectiveInbound.process(createMeshObjectiveInboundRuntimeState(meshStates[peer.peerId].coordination, meshStates[peer.peerId].discovery, meshStates[peer.peerId].objectives, meshStates[peer.peerId].inbound), {
|
|
120
|
+
envelope: objectiveEnvelope.signed,
|
|
121
|
+
verifiedAt: wallTime(context, 3_000),
|
|
122
|
+
receivedAt,
|
|
123
|
+
});
|
|
124
|
+
countInteraction(context, objectiveEnvelope.signed, peer.peerId);
|
|
125
|
+
if (!admitted.accepted)
|
|
126
|
+
throw new Error(`closed_loop_objective_rejected:${admitted.code}`);
|
|
127
|
+
meshStates[peer.peerId] = replaceMeshDomains(meshStates[peer.peerId], admitted.state, receivedAt);
|
|
128
|
+
}
|
|
129
|
+
const workCreatedAt = advance(context);
|
|
130
|
+
const createdWork = evaluateMeshObjectiveWorkCommand(createMeshObjectiveWorkRuntimeState(meshStates[owner.peerId].coordination, meshStates[owner.peerId].discovery, meshStates[owner.peerId].objectives), { kind: 'work.create', input: projection.work }, { verifiedAt: wallTime(context, 4_000), receivedAt: workCreatedAt });
|
|
131
|
+
if (!createdWork.accepted)
|
|
132
|
+
throw new Error(`closed_loop_work_rejected:${createdWork.code}`);
|
|
133
|
+
meshStates[owner.peerId] = replaceMeshDomains(meshStates[owner.peerId], createdWork.state, workCreatedAt);
|
|
134
|
+
const offerId = scopedId(context, 'offer', 1);
|
|
135
|
+
const offerPayload = createOfferPayload(context, projection, offerId);
|
|
136
|
+
const preparedOffers = await Promise.all(recipients.map(async (recipient) => ({
|
|
137
|
+
recipientPeerId: recipient.peerId,
|
|
138
|
+
preparedAt: context.logicalTimeMs + 1,
|
|
139
|
+
envelope: (await signEnvelope(context, {
|
|
140
|
+
senderPeerId: owner.peerId,
|
|
141
|
+
type: 'work.offer',
|
|
142
|
+
audience: { kind: 'peer', peerId: recipient.peerId },
|
|
143
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
144
|
+
payload: offerPayload,
|
|
145
|
+
extensions: projection.extensions,
|
|
146
|
+
criticalExtensions: projection.criticalExtensions,
|
|
147
|
+
sentAtOffsetMs: 5_000,
|
|
148
|
+
expiresAtOffsetMs: 60_000,
|
|
149
|
+
})).signed,
|
|
150
|
+
})));
|
|
151
|
+
const offeredAt = advance(context);
|
|
152
|
+
const offered = evaluateMeshAllocationCommand(allocationRuntime(meshStates[owner.peerId]), {
|
|
153
|
+
kind: 'allocation.offer',
|
|
154
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
155
|
+
workItemId: projection.workItemId,
|
|
156
|
+
expectedWorkItemRevision: projection.workItemRevision,
|
|
157
|
+
recipients: preparedOffers.map((prepared) => ({
|
|
158
|
+
...prepared,
|
|
159
|
+
preparedAt: offeredAt,
|
|
160
|
+
})),
|
|
161
|
+
}, wallTime(context, 5_000), offeredAt, [PLANNING_WORK_EXTENSION_KEY_V1], recipients.map((recipient) => recipient.peerId));
|
|
162
|
+
if (!offered.accepted)
|
|
163
|
+
throw new Error(`closed_loop_offer_rejected:${offered.code}`);
|
|
164
|
+
meshStates[owner.peerId] = retainInbound(meshStates[owner.peerId], offered.state);
|
|
165
|
+
for (const prepared of preparedOffers) {
|
|
166
|
+
const admitted = await allocationInbound.process(meshStates[prepared.recipientPeerId], {
|
|
167
|
+
envelope: prepared.envelope,
|
|
168
|
+
verifiedAt: wallTime(context, 5_000),
|
|
169
|
+
receivedAt: offeredAt,
|
|
170
|
+
});
|
|
171
|
+
countInteraction(context, prepared.envelope, prepared.recipientPeerId);
|
|
172
|
+
if (!admitted.accepted)
|
|
173
|
+
throw new Error(`closed_loop_planning_offer_rejected:${admitted.code}`);
|
|
174
|
+
meshStates[prepared.recipientPeerId] = admitted.state;
|
|
175
|
+
}
|
|
176
|
+
const offersByPeer = new Map(preparedOffers.map((prepared) => [prepared.recipientPeerId, prepared]));
|
|
177
|
+
const bidsById = new Map();
|
|
178
|
+
for (const [index, recipient] of recipients.entries()) {
|
|
179
|
+
const peerId = recipient.peerId;
|
|
180
|
+
const preparedOffer = offersByPeer.get(peerId);
|
|
181
|
+
if (!preparedOffer)
|
|
182
|
+
throw new Error('closed_loop_offer_state_missing');
|
|
183
|
+
const bidId = scopedId(context, `bid-${peerId}`, 1);
|
|
184
|
+
const bidEnvelope = await signEnvelope(context, {
|
|
185
|
+
senderPeerId: peerId,
|
|
186
|
+
type: 'work.bid',
|
|
187
|
+
audience: { kind: 'peer', peerId: owner.peerId },
|
|
188
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
189
|
+
causationId: preparedOffer.envelope.messageId,
|
|
190
|
+
payload: {
|
|
191
|
+
type: 'work.bid',
|
|
192
|
+
bidId,
|
|
193
|
+
bidRevision: 1,
|
|
194
|
+
offerId,
|
|
195
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
196
|
+
objectiveDocumentId: input.missionIntent.objective.objectiveDocumentId,
|
|
197
|
+
objectiveRevision: input.missionIntent.objective.objectiveRevision,
|
|
198
|
+
workItemId: projection.workItemId,
|
|
199
|
+
workItemRevision: projection.workItemRevision,
|
|
200
|
+
ownerPeerId: owner.peerId,
|
|
201
|
+
ownerEpoch: 1,
|
|
202
|
+
offerAttempt: 1,
|
|
203
|
+
bidderPeerId: peerId,
|
|
204
|
+
advertisementId: capabilityAdvertisementId(peerId, requiredCapabilityKeys[0]),
|
|
205
|
+
capabilityId: capabilityId(peerId, requiredCapabilityKeys[0]),
|
|
206
|
+
capabilityRevision: 1,
|
|
207
|
+
capacityReservationUnits: 1,
|
|
208
|
+
budgetUnits: 1,
|
|
209
|
+
bidDeadline: offerPayload.bidDeadline,
|
|
210
|
+
workDeadline: offerPayload.workDeadline,
|
|
211
|
+
expectedCompletionAt: wallTime(context, 600_000 + index * 1_000),
|
|
212
|
+
bidExpiresAt: wallTime(context, 50_000),
|
|
213
|
+
assumptions: [],
|
|
214
|
+
},
|
|
215
|
+
sentAtOffsetMs: 6_000,
|
|
216
|
+
expiresAtOffsetMs: 50_000,
|
|
217
|
+
});
|
|
218
|
+
const preparedAt = advance(context);
|
|
219
|
+
const localBid = evaluateMeshAllocationCommand(allocationRuntime(meshStates[peerId]), {
|
|
220
|
+
kind: 'allocation.bid',
|
|
221
|
+
offerId,
|
|
222
|
+
preparedAt,
|
|
223
|
+
envelope: bidEnvelope.signed,
|
|
224
|
+
}, wallTime(context, 7_000), preparedAt);
|
|
225
|
+
if (!localBid.accepted)
|
|
226
|
+
throw new Error(`closed_loop_local_bid_rejected:${localBid.code}`);
|
|
227
|
+
meshStates[peerId] = retainInbound(meshStates[peerId], localBid.state);
|
|
228
|
+
const receivedAt = advance(context);
|
|
229
|
+
const receivedBid = await allocationInbound.process(meshStates[owner.peerId], {
|
|
230
|
+
envelope: bidEnvelope.signed,
|
|
231
|
+
verifiedAt: wallTime(context, 7_000),
|
|
232
|
+
receivedAt,
|
|
233
|
+
});
|
|
234
|
+
countInteraction(context, bidEnvelope.signed, owner.peerId);
|
|
235
|
+
if (!receivedBid.accepted)
|
|
236
|
+
throw new Error(`closed_loop_bid_rejected:${receivedBid.code}`);
|
|
237
|
+
meshStates[owner.peerId] = receivedBid.state;
|
|
238
|
+
bidsById.set(bidId, bidEnvelope.signed);
|
|
239
|
+
}
|
|
240
|
+
const selected = selectMeshAllocationBid(allocationRuntime(meshStates[owner.peerId]), { offerId, evaluatedAt: advance(context) });
|
|
241
|
+
if (selected.reason !== 'selected' || !selected.bid)
|
|
242
|
+
throw new Error(`closed_loop_bid_not_selected:${selected.reason}`);
|
|
243
|
+
const winnerPeerId = selected.bid.bidderPeerId;
|
|
244
|
+
const offerRecipientPeerIds = Object.freeze(recipients.map((recipient) => recipient.peerId).sort());
|
|
245
|
+
const bidderPeerIds = Object.freeze([...offerRecipientPeerIds]);
|
|
246
|
+
const winningBidEnvelope = bidsById.get(selected.bid.bidId);
|
|
247
|
+
if (!winningBidEnvelope)
|
|
248
|
+
throw new Error('closed_loop_selected_bid_evidence_missing');
|
|
249
|
+
const awardId = scopedId(context, 'award', 1);
|
|
250
|
+
const leaseExpiresAt = wallTime(context, 1_800_000);
|
|
251
|
+
const acceptanceDeadline = wallTime(context, 35_000);
|
|
252
|
+
const awardEnvelope = await signEnvelope(context, {
|
|
253
|
+
senderPeerId: owner.peerId,
|
|
254
|
+
type: 'work.award',
|
|
255
|
+
audience: { kind: 'peer', peerId: winnerPeerId },
|
|
256
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
257
|
+
causationId: winningBidEnvelope.messageId,
|
|
258
|
+
payload: {
|
|
259
|
+
type: 'work.award',
|
|
260
|
+
awardId,
|
|
261
|
+
offerId,
|
|
262
|
+
bidId: selected.bid.bidId,
|
|
263
|
+
bidRevision: selected.bid.bidRevision,
|
|
264
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
265
|
+
objectiveDocumentId: input.missionIntent.objective.objectiveDocumentId,
|
|
266
|
+
objectiveRevision: input.missionIntent.objective.objectiveRevision,
|
|
267
|
+
workItemId: projection.workItemId,
|
|
268
|
+
workItemRevision: projection.workItemRevision,
|
|
269
|
+
ownerPeerId: owner.peerId,
|
|
270
|
+
ownerEpoch: 1,
|
|
271
|
+
offerAttempt: 1,
|
|
272
|
+
assigneePeerId: winnerPeerId,
|
|
273
|
+
assignmentEpoch: 1,
|
|
274
|
+
authorityKind: 'award',
|
|
275
|
+
assignmentAuthorityId: awardId,
|
|
276
|
+
fencingToken: awardId,
|
|
277
|
+
budgetReservationUnits: offerPayload.budgetReservationUnits,
|
|
278
|
+
workDeadline: offerPayload.workDeadline,
|
|
279
|
+
leaseStartsAt: wallTime(context, 10_000),
|
|
280
|
+
leaseExpiresAt,
|
|
281
|
+
acceptanceDeadline,
|
|
282
|
+
},
|
|
283
|
+
sentAtOffsetMs: 10_000,
|
|
284
|
+
expiresAtOffsetMs: 35_000,
|
|
285
|
+
});
|
|
286
|
+
const awardedAt = advance(context);
|
|
287
|
+
const awarded = evaluateMeshAllocationCommand(allocationRuntime(meshStates[owner.peerId]), {
|
|
288
|
+
kind: 'allocation.award',
|
|
289
|
+
offerId,
|
|
290
|
+
bidId: selected.bid.bidId,
|
|
291
|
+
bidRevision: selected.bid.bidRevision,
|
|
292
|
+
recipient: {
|
|
293
|
+
recipientPeerId: winnerPeerId,
|
|
294
|
+
preparedAt: awardedAt,
|
|
295
|
+
envelope: awardEnvelope.signed,
|
|
296
|
+
},
|
|
297
|
+
}, wallTime(context, 10_000), awardedAt);
|
|
298
|
+
if (!awarded.accepted)
|
|
299
|
+
throw new Error(`closed_loop_award_rejected:${awarded.code}`);
|
|
300
|
+
meshStates[owner.peerId] = retainInbound(meshStates[owner.peerId], awarded.state);
|
|
301
|
+
const receivedAward = await allocationInbound.process(meshStates[winnerPeerId], {
|
|
302
|
+
envelope: awardEnvelope.signed,
|
|
303
|
+
verifiedAt: wallTime(context, 10_000),
|
|
304
|
+
receivedAt: awardedAt,
|
|
305
|
+
});
|
|
306
|
+
countInteraction(context, awardEnvelope.signed, winnerPeerId);
|
|
307
|
+
if (!receivedAward.accepted)
|
|
308
|
+
throw new Error(`closed_loop_received_award_rejected:${receivedAward.code}`);
|
|
309
|
+
meshStates[winnerPeerId] = receivedAward.state;
|
|
310
|
+
const acceptanceId = scopedId(context, 'acceptance', 1);
|
|
311
|
+
const acceptanceEnvelope = await signEnvelope(context, {
|
|
312
|
+
senderPeerId: winnerPeerId,
|
|
313
|
+
type: 'work.accept',
|
|
314
|
+
audience: { kind: 'peer', peerId: owner.peerId },
|
|
315
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
316
|
+
causationId: awardEnvelope.signed.messageId,
|
|
317
|
+
payload: {
|
|
318
|
+
type: 'work.accept',
|
|
319
|
+
acceptanceId,
|
|
320
|
+
awardId,
|
|
321
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
322
|
+
objectiveDocumentId: input.missionIntent.objective.objectiveDocumentId,
|
|
323
|
+
objectiveRevision: input.missionIntent.objective.objectiveRevision,
|
|
324
|
+
workItemId: projection.workItemId,
|
|
325
|
+
workItemRevision: projection.workItemRevision,
|
|
326
|
+
ownerPeerId: owner.peerId,
|
|
327
|
+
ownerEpoch: 1,
|
|
328
|
+
assigneePeerId: winnerPeerId,
|
|
329
|
+
assignmentEpoch: 1,
|
|
330
|
+
assignmentAuthorityId: awardId,
|
|
331
|
+
fencingToken: awardId,
|
|
332
|
+
acceptanceDeadline,
|
|
333
|
+
},
|
|
334
|
+
sentAtOffsetMs: 11_000,
|
|
335
|
+
expiresAtOffsetMs: 35_000,
|
|
336
|
+
});
|
|
337
|
+
const acceptedAt = advance(context);
|
|
338
|
+
const locallyAccepted = evaluateMeshAllocationCommand(allocationRuntime(meshStates[winnerPeerId]), {
|
|
339
|
+
kind: 'allocation.assignment_response',
|
|
340
|
+
awardId,
|
|
341
|
+
preparedAt: acceptedAt,
|
|
342
|
+
envelope: acceptanceEnvelope.signed,
|
|
343
|
+
}, wallTime(context, 11_000), acceptedAt);
|
|
344
|
+
if (!locallyAccepted.accepted)
|
|
345
|
+
throw new Error(`closed_loop_local_acceptance_rejected:${locallyAccepted.code}`);
|
|
346
|
+
meshStates[winnerPeerId] = retainInbound(meshStates[winnerPeerId], locallyAccepted.state);
|
|
347
|
+
const ownerAccepted = await allocationInbound.process(meshStates[owner.peerId], {
|
|
348
|
+
envelope: acceptanceEnvelope.signed,
|
|
349
|
+
verifiedAt: wallTime(context, 11_000),
|
|
350
|
+
receivedAt: acceptedAt,
|
|
351
|
+
});
|
|
352
|
+
countInteraction(context, acceptanceEnvelope.signed, owner.peerId);
|
|
353
|
+
if (!ownerAccepted.accepted)
|
|
354
|
+
throw new Error(`closed_loop_acceptance_rejected:${ownerAccepted.code}`);
|
|
355
|
+
meshStates[owner.peerId] = ownerAccepted.state;
|
|
356
|
+
const checkpointId = scopedId(context, 'checkpoint', 1);
|
|
357
|
+
const checkpointEnvelope = await signEnvelope(context, {
|
|
358
|
+
senderPeerId: winnerPeerId,
|
|
359
|
+
type: 'work.checkpoint',
|
|
360
|
+
audience: { kind: 'peer', peerId: owner.peerId },
|
|
361
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
362
|
+
causationId: acceptanceEnvelope.signed.messageId,
|
|
363
|
+
payload: {
|
|
364
|
+
type: 'work.checkpoint',
|
|
365
|
+
checkpointId,
|
|
366
|
+
checkpointSequence: 1,
|
|
367
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
368
|
+
objectiveDocumentId: input.missionIntent.objective.objectiveDocumentId,
|
|
369
|
+
objectiveRevision: input.missionIntent.objective.objectiveRevision,
|
|
370
|
+
workItemId: projection.workItemId,
|
|
371
|
+
workItemRevision: projection.workItemRevision,
|
|
372
|
+
ownerPeerId: owner.peerId,
|
|
373
|
+
ownerEpoch: 1,
|
|
374
|
+
assigneePeerId: winnerPeerId,
|
|
375
|
+
awardId,
|
|
376
|
+
acceptanceId,
|
|
377
|
+
assignmentEpoch: 1,
|
|
378
|
+
assignmentAuthorityId: awardId,
|
|
379
|
+
fencingToken: awardId,
|
|
380
|
+
leaseExpiresAt,
|
|
381
|
+
checkpointDigest: await contentDigest(input.runner.crypto, `${scopedId(context, 'checkpoint-content', 1)}:${projection.workItemId}`),
|
|
382
|
+
checkpointSummary: 'The governed effect boundary is ready.',
|
|
383
|
+
},
|
|
384
|
+
sentAtOffsetMs: 12_000,
|
|
385
|
+
expiresAtOffsetMs: 60_000,
|
|
386
|
+
});
|
|
387
|
+
const checkpointedAt = advance(context);
|
|
388
|
+
const locallyCheckpointed = evaluateMeshAllocationCommand(allocationRuntime(meshStates[winnerPeerId]), {
|
|
389
|
+
kind: 'allocation.execution',
|
|
390
|
+
preparedAt: checkpointedAt,
|
|
391
|
+
envelope: checkpointEnvelope.signed,
|
|
392
|
+
}, wallTime(context, 12_000), checkpointedAt);
|
|
393
|
+
if (!locallyCheckpointed.accepted)
|
|
394
|
+
throw new Error(`closed_loop_local_checkpoint_rejected:${locallyCheckpointed.code}`);
|
|
395
|
+
meshStates[winnerPeerId] = retainInbound(meshStates[winnerPeerId], locallyCheckpointed.state);
|
|
396
|
+
const ownerCheckpointed = await allocationInbound.process(meshStates[owner.peerId], {
|
|
397
|
+
envelope: checkpointEnvelope.signed,
|
|
398
|
+
verifiedAt: wallTime(context, 12_000),
|
|
399
|
+
receivedAt: checkpointedAt,
|
|
400
|
+
});
|
|
401
|
+
countInteraction(context, checkpointEnvelope.signed, owner.peerId);
|
|
402
|
+
if (!ownerCheckpointed.accepted)
|
|
403
|
+
throw new Error(`closed_loop_checkpoint_rejected:${ownerCheckpointed.code}`);
|
|
404
|
+
meshStates[owner.peerId] = ownerCheckpointed.state;
|
|
405
|
+
const authorities = currentAuthorities(meshStates, owner.peerId, winnerPeerId, input.missionIntent.objective.objectiveId, projection.workItemId, checkpointId);
|
|
406
|
+
const winnerPlanning = planningStates[owner.peerId];
|
|
407
|
+
const winnerHeadDigest = winnerPlanning.planView.selectedHeads.find((head) => head.semanticSlotKey === planning.proposal.semanticSlotKey)?.fragmentDigest;
|
|
408
|
+
const winnerFragment = winnerPlanning.planView.fragments.find((fragment) => fragment.fragmentDigest === winnerHeadDigest);
|
|
409
|
+
if (!winnerFragment)
|
|
410
|
+
throw new Error('closed_loop_winner_fragment_missing');
|
|
411
|
+
const winnerWorkMapping = winnerPlanning.planView.workMappings.find((mapping) => mapping.fragmentDigest === winnerFragment.fragmentDigest);
|
|
412
|
+
if (!winnerWorkMapping)
|
|
413
|
+
throw new Error('closed_loop_winner_work_mapping_missing');
|
|
414
|
+
const adaptiveRole = createPlanningAdaptiveRoleV1({
|
|
415
|
+
source: {
|
|
416
|
+
workContractId: scopedId(context, 'work-contract', 1),
|
|
417
|
+
identity: identity(context, winnerPeerId),
|
|
418
|
+
objective: authorities.objective,
|
|
419
|
+
workItem: authorities.workItem,
|
|
420
|
+
execution: authorities.execution,
|
|
421
|
+
fenceHead: authorities.fenceHead,
|
|
422
|
+
mandate: input.mandate,
|
|
423
|
+
roleKey: winnerFragment.roleKey,
|
|
424
|
+
trustPolicyId: scopedId(context, 'trust-policy', 1),
|
|
425
|
+
inferencePolicyId: scopedId(context, 'inference-policy', 1),
|
|
426
|
+
maximumActionBudgetUnits: input.mandate.statement.budget.maximumActionBudgetUnits,
|
|
427
|
+
createdAtLogicalMs: checkpointedAt,
|
|
428
|
+
},
|
|
429
|
+
missionIntent: input.missionIntent,
|
|
430
|
+
planView: winnerPlanning.planView,
|
|
431
|
+
fragment: winnerFragment,
|
|
432
|
+
repositoryRecord: projection.repositoryRecord,
|
|
433
|
+
extension: projection.extension,
|
|
434
|
+
roleBindingId: scopedId(context, 'role-binding', 1),
|
|
435
|
+
targetStatus: 'assigned',
|
|
436
|
+
});
|
|
437
|
+
planningStates = {
|
|
438
|
+
...planningStates,
|
|
439
|
+
[owner.peerId]: applyPlanning(winnerPlanning, {
|
|
440
|
+
schemaVersion: 1,
|
|
441
|
+
kind: 'fragment.assignment.observe',
|
|
442
|
+
expectedStateDigest: null,
|
|
443
|
+
fragmentId: winnerFragment.fragmentId,
|
|
444
|
+
previousFragmentDigest: winnerFragment.fragmentDigest,
|
|
445
|
+
expectedWorkMapping: winnerWorkMapping,
|
|
446
|
+
roleBinding: adaptiveRole.roleBinding,
|
|
447
|
+
}),
|
|
448
|
+
};
|
|
449
|
+
const immutablePlanningStates = Object.freeze({ ...planningStates });
|
|
450
|
+
const immutableMeshStates = Object.freeze({ ...meshStates });
|
|
451
|
+
let finalization;
|
|
452
|
+
let finalizationBinding;
|
|
453
|
+
const handle = Object.freeze({
|
|
454
|
+
schemaVersion: 1,
|
|
455
|
+
winnerPeerId,
|
|
456
|
+
discoveredPeerIds,
|
|
457
|
+
offerRecipientPeerIds,
|
|
458
|
+
bidderPeerIds,
|
|
459
|
+
workContract: adaptiveRole.workContract,
|
|
460
|
+
adaptiveRole,
|
|
461
|
+
roleBinding: adaptiveRole.roleBinding,
|
|
462
|
+
authorityState,
|
|
463
|
+
...authorities,
|
|
464
|
+
planningStates: immutablePlanningStates,
|
|
465
|
+
meshStates: immutableMeshStates,
|
|
466
|
+
logicalTimeMs: checkpointedAt,
|
|
467
|
+
interactionCount: context.interactionCount,
|
|
468
|
+
async finalizeAfterCommittedEffect(finalizeInput) {
|
|
469
|
+
const attempt = validateCollectiveProtectedEffectAttemptV1(finalizeInput.effectAttempt);
|
|
470
|
+
const rawReceipt = finalizeInput.effectReceipt;
|
|
471
|
+
const receipt = validateCollectiveProtectedEffectReceiptV1(rawReceipt);
|
|
472
|
+
assertEffectBoundToWork(attempt, receipt, adaptiveRole.workContract);
|
|
473
|
+
assertCollectiveEffectReceiptProvenanceV1(handle, attempt, rawReceipt);
|
|
474
|
+
if (receipt.status !== 'committed' ||
|
|
475
|
+
receipt.outputDigest !== finalizeInput.resultDigest)
|
|
476
|
+
throw new Error('closed_loop_effect_not_committed');
|
|
477
|
+
const binding = `${attempt.attemptDigest}:${receipt.receiptDigest}:${finalizeInput.resultDigest}:${finalizeInput.resultSummary}`;
|
|
478
|
+
if (finalization) {
|
|
479
|
+
if (binding !== finalizationBinding)
|
|
480
|
+
throw new Error('closed_loop_finalization_conflict');
|
|
481
|
+
return finalization;
|
|
482
|
+
}
|
|
483
|
+
finalizationBinding = binding;
|
|
484
|
+
finalization = finalizeMeshResult({
|
|
485
|
+
context,
|
|
486
|
+
ownerPeerId: owner.peerId,
|
|
487
|
+
winnerPeerId,
|
|
488
|
+
discoveredPeerIds,
|
|
489
|
+
offerRecipientPeerIds,
|
|
490
|
+
bidderPeerIds,
|
|
491
|
+
workContract: adaptiveRole.workContract,
|
|
492
|
+
roleBinding: adaptiveRole.roleBinding,
|
|
493
|
+
checkpoint: authorities.checkpoint,
|
|
494
|
+
checkpointId,
|
|
495
|
+
awardId,
|
|
496
|
+
acceptanceId,
|
|
497
|
+
leaseExpiresAt,
|
|
498
|
+
resultDigest: finalizeInput.resultDigest,
|
|
499
|
+
resultSummary: finalizeInput.resultSummary,
|
|
500
|
+
planningStates: immutablePlanningStates,
|
|
501
|
+
meshStates: immutableMeshStates,
|
|
502
|
+
allocationInbound,
|
|
503
|
+
});
|
|
504
|
+
return finalization;
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
return handle;
|
|
508
|
+
}
|
|
509
|
+
function validateInput(input) {
|
|
510
|
+
assertExactDataRecord(input, [
|
|
511
|
+
'schemaVersion',
|
|
512
|
+
'missionIntent',
|
|
513
|
+
'selectionPolicy',
|
|
514
|
+
'mandate',
|
|
515
|
+
'peers',
|
|
516
|
+
'observations',
|
|
517
|
+
'planningProposal',
|
|
518
|
+
'planningMode',
|
|
519
|
+
'runner',
|
|
520
|
+
'seed',
|
|
521
|
+
'maximumLogicalTimeMs',
|
|
522
|
+
], 'closed_loop_runtime_input');
|
|
523
|
+
if (input.schemaVersion !== 1)
|
|
524
|
+
throw new TypeError('closed_loop_schema_version_invalid');
|
|
525
|
+
const missionIntent = validateMissionIntentV1(input.missionIntent);
|
|
526
|
+
const selectionPolicy = validatePlanSelectionPolicyV1(input.selectionPolicy);
|
|
527
|
+
const observations = input.observations.map((observation) => validateMissionObservationV1(observation));
|
|
528
|
+
const planningProposal = validatePlanFragmentProposalV1(input.planningProposal);
|
|
529
|
+
if (input.planningMode !== 'adaptive_collective' &&
|
|
530
|
+
input.planningMode !== 'centralized_planner')
|
|
531
|
+
throw new TypeError('closed_loop_planning_mode_invalid');
|
|
532
|
+
if (missionIntent.selectionPolicyDigest !== selectionPolicy.policyDigest)
|
|
533
|
+
throw new TypeError('closed_loop_selection_policy_mismatch');
|
|
534
|
+
if (input.peers.length < MINIMUM_PEERS ||
|
|
535
|
+
input.peers.length > MAXIMUM_PEERS)
|
|
536
|
+
throw new RangeError('closed_loop_peer_count_invalid');
|
|
537
|
+
if (!Number.isSafeInteger(input.seed) || input.seed < 0)
|
|
538
|
+
throw new TypeError('closed_loop_seed_invalid');
|
|
539
|
+
if (!Number.isSafeInteger(input.maximumLogicalTimeMs) ||
|
|
540
|
+
input.maximumLogicalTimeMs < 1)
|
|
541
|
+
throw new RangeError('closed_loop_logical_time_limit_invalid');
|
|
542
|
+
const peerIds = new Set();
|
|
543
|
+
for (const peer of input.peers) {
|
|
544
|
+
if (peer.schemaVersion !== 1 ||
|
|
545
|
+
peer.peerId.length === 0 ||
|
|
546
|
+
peer.peerInstanceId.length === 0 ||
|
|
547
|
+
peerIds.has(peer.peerId) ||
|
|
548
|
+
new Set(peer.capabilityKeys).size !== peer.capabilityKeys.length ||
|
|
549
|
+
new Set(peer.neighborPeerIds).size !== peer.neighborPeerIds.length ||
|
|
550
|
+
peer.neighborPeerIds.length > MAXIMUM_NEIGHBORS ||
|
|
551
|
+
peer.neighborPeerIds.includes(peer.peerId) ||
|
|
552
|
+
!input.runner.privateKeys[peer.peerId])
|
|
553
|
+
throw new TypeError('closed_loop_peer_invalid');
|
|
554
|
+
peerIds.add(peer.peerId);
|
|
555
|
+
}
|
|
556
|
+
for (const peer of input.peers)
|
|
557
|
+
if (peer.neighborPeerIds.some((peerId) => !peerIds.has(peerId)))
|
|
558
|
+
throw new TypeError('closed_loop_neighbor_invalid');
|
|
559
|
+
const owner = input.peers[0];
|
|
560
|
+
const peersById = new Map(input.peers.map((peer) => [peer.peerId, peer]));
|
|
561
|
+
for (const observation of observations) {
|
|
562
|
+
const observer = peersById.get(observation.observerPeerId);
|
|
563
|
+
if (!observer ||
|
|
564
|
+
observer.peerInstanceId !== observation.observerInstanceId ||
|
|
565
|
+
observation.missionIntentId !== missionIntent.missionIntentId ||
|
|
566
|
+
observation.intentRevision !== missionIntent.revision ||
|
|
567
|
+
observation.intentDigest !== missionIntent.intentDigest)
|
|
568
|
+
throw new TypeError('closed_loop_observation_binding_invalid');
|
|
569
|
+
}
|
|
570
|
+
const eligibleObservationDigests = new Set(observations
|
|
571
|
+
.filter((observation) => input.planningMode === 'centralized_planner' ||
|
|
572
|
+
(observation.observerPeerId === owner.peerId &&
|
|
573
|
+
observation.observerInstanceId === owner.peerInstanceId))
|
|
574
|
+
.map((observation) => observation.observationDigest));
|
|
575
|
+
if (planningProposal.proposerPeerId !== owner.peerId ||
|
|
576
|
+
planningProposal.proposerInstanceId !== owner.peerInstanceId ||
|
|
577
|
+
planningProposal.missionIntentId !== missionIntent.missionIntentId ||
|
|
578
|
+
planningProposal.intentRevision !== missionIntent.revision ||
|
|
579
|
+
planningProposal.intentDigest !== missionIntent.intentDigest ||
|
|
580
|
+
planningProposal.basisObservationDigests.length === 0 ||
|
|
581
|
+
planningProposal.basisObservationDigests.some((digest) => !eligibleObservationDigests.has(digest)))
|
|
582
|
+
throw new TypeError('closed_loop_planning_proposal_binding_invalid');
|
|
583
|
+
if (input.mandate.statement.tenantId !== missionIntent.tenantId ||
|
|
584
|
+
input.mandate.statement.policyDomainId !== missionIntent.policyDomainId ||
|
|
585
|
+
input.mandate.statement.issuerId !== owner.peerId ||
|
|
586
|
+
input.mandate.statement.objective.meshId !== missionIntent.objective.meshId ||
|
|
587
|
+
input.mandate.statement.objective.objectiveId !==
|
|
588
|
+
missionIntent.objective.objectiveId ||
|
|
589
|
+
input.mandate.statement.objective.objectiveDocumentId !==
|
|
590
|
+
missionIntent.objective.objectiveDocumentId ||
|
|
591
|
+
input.peers.some((peer) => !input.mandate.statement.subjectPeerIds.includes(peer.peerId)) ||
|
|
592
|
+
input.runner.mandateVerification.signedDigest !== input.mandate.mandateDigest ||
|
|
593
|
+
input.runner.mandateVerification.issuerId !==
|
|
594
|
+
input.mandate.statement.issuerId)
|
|
595
|
+
throw new TypeError('closed_loop_authority_binding_invalid');
|
|
596
|
+
const requiredCapability = missionIntent.permittedCapabilityKeys[0];
|
|
597
|
+
if (!requiredCapability ||
|
|
598
|
+
owner.neighborPeerIds.every((peerId) => !input.peers
|
|
599
|
+
.find((peer) => peer.peerId === peerId)
|
|
600
|
+
?.capabilityKeys.includes(requiredCapability)))
|
|
601
|
+
throw new TypeError('closed_loop_capability_topology_invalid');
|
|
602
|
+
return Object.freeze({
|
|
603
|
+
...input,
|
|
604
|
+
missionIntent,
|
|
605
|
+
selectionPolicy,
|
|
606
|
+
observations: Object.freeze(observations),
|
|
607
|
+
planningProposal,
|
|
608
|
+
peers: Object.freeze(input.peers.map((peer) => Object.freeze({ ...peer }))),
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function installMandate(input) {
|
|
612
|
+
const decision = acceptDelegationMandateV1(createCollectiveAuthorityStateV1({
|
|
613
|
+
tenantId: input.missionIntent.tenantId,
|
|
614
|
+
policyDomainId: input.missionIntent.policyDomainId,
|
|
615
|
+
}), {
|
|
616
|
+
mandate: input.mandate,
|
|
617
|
+
verification: input.runner.mandateVerification,
|
|
618
|
+
acceptedAtLogicalMs: 1,
|
|
619
|
+
});
|
|
620
|
+
if (!decision.accepted)
|
|
621
|
+
throw new Error(`closed_loop_mandate_rejected:${decision.code}`);
|
|
622
|
+
return decision.state;
|
|
623
|
+
}
|
|
624
|
+
function createPlanning(context) {
|
|
625
|
+
const { input, owner } = context;
|
|
626
|
+
const admittedSubjects = input.peers.map((peer) => ({
|
|
627
|
+
schemaVersion: 1,
|
|
628
|
+
peerId: peer.peerId,
|
|
629
|
+
peerInstanceId: peer.peerInstanceId,
|
|
630
|
+
}));
|
|
631
|
+
const proposal = input.planningProposal;
|
|
632
|
+
const states = {};
|
|
633
|
+
for (const peer of input.peers) {
|
|
634
|
+
let state = createPlanningReducerStateV1({
|
|
635
|
+
schemaVersion: 1,
|
|
636
|
+
peerId: peer.peerId,
|
|
637
|
+
peerInstanceId: peer.peerInstanceId,
|
|
638
|
+
missionIntent: input.missionIntent,
|
|
639
|
+
selectionPolicy: input.selectionPolicy,
|
|
640
|
+
admittedSubjects,
|
|
641
|
+
logicalTimeMs: 1,
|
|
642
|
+
});
|
|
643
|
+
const proposalBasis = new Set(proposal.basisObservationDigests);
|
|
644
|
+
const localObservations = input.planningMode === 'centralized_planner' &&
|
|
645
|
+
peer.peerId === owner.peerId
|
|
646
|
+
? input.observations.filter((observation) => proposalBasis.has(observation.observationDigest))
|
|
647
|
+
: input.observations.filter((observation) => observation.observerPeerId === peer.peerId &&
|
|
648
|
+
observation.observerInstanceId === peer.peerInstanceId);
|
|
649
|
+
for (const observation of localObservations)
|
|
650
|
+
state = applyPlanning(state, {
|
|
651
|
+
schemaVersion: 1,
|
|
652
|
+
kind: 'observation.record',
|
|
653
|
+
expectedStateDigest: null,
|
|
654
|
+
observation,
|
|
655
|
+
});
|
|
656
|
+
states[peer.peerId] = state;
|
|
657
|
+
}
|
|
658
|
+
let ownerState = applyPlanning(states[owner.peerId], {
|
|
659
|
+
schemaVersion: 1,
|
|
660
|
+
kind: 'proposal.record',
|
|
661
|
+
expectedStateDigest: null,
|
|
662
|
+
proposal,
|
|
663
|
+
});
|
|
664
|
+
ownerState = applyPlanning(ownerState, {
|
|
665
|
+
schemaVersion: 1,
|
|
666
|
+
kind: 'slot.evaluate',
|
|
667
|
+
expectedStateDigest: null,
|
|
668
|
+
semanticSlotKey: proposal.semanticSlotKey,
|
|
669
|
+
candidateProposalDigests: [proposal.proposalDigest],
|
|
670
|
+
decidedAtLogicalMs: 1,
|
|
671
|
+
});
|
|
672
|
+
const activeFragment = ownerState.planView.fragments.find((fragment) => fragment.status === 'active');
|
|
673
|
+
if (!activeFragment)
|
|
674
|
+
throw new Error('closed_loop_active_fragment_missing');
|
|
675
|
+
const workItemId = planningWorkItemIdV1(proposal.proposalDigest);
|
|
676
|
+
ownerState = applyPlanning(ownerState, {
|
|
677
|
+
schemaVersion: 1,
|
|
678
|
+
kind: 'fragment.project-to-work',
|
|
679
|
+
expectedStateDigest: null,
|
|
680
|
+
fragmentId: activeFragment.fragmentId,
|
|
681
|
+
previousFragmentDigest: activeFragment.fragmentDigest,
|
|
682
|
+
workTarget: {
|
|
683
|
+
schemaVersion: 1,
|
|
684
|
+
meshId: input.missionIntent.objective.meshId,
|
|
685
|
+
objectiveId: input.missionIntent.objective.objectiveId,
|
|
686
|
+
workItemId,
|
|
687
|
+
workItemRevision: 1,
|
|
688
|
+
},
|
|
689
|
+
transitionedAtLogicalMs: 1,
|
|
690
|
+
});
|
|
691
|
+
states[owner.peerId] = ownerState;
|
|
692
|
+
const offeredFragment = ownerState.planView.fragments.find((fragment) => fragment.fragmentId === activeFragment.fragmentId &&
|
|
693
|
+
fragment.status === 'offered');
|
|
694
|
+
if (!offeredFragment)
|
|
695
|
+
throw new Error('closed_loop_offered_fragment_missing');
|
|
696
|
+
return {
|
|
697
|
+
states,
|
|
698
|
+
proposal,
|
|
699
|
+
projection: createPlanningLocalWorkProjectionV1({
|
|
700
|
+
missionIntent: input.missionIntent,
|
|
701
|
+
sourcePlanView: ownerState.planView,
|
|
702
|
+
fragment: offeredFragment,
|
|
703
|
+
}),
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function applyPlanning(state, input) {
|
|
707
|
+
const decision = reducePlanningCommandV1(state, createPlanningReducerCommandV1(input));
|
|
708
|
+
if (decision.status !== 'applied')
|
|
709
|
+
throw new Error(`closed_loop_planning_rejected:${decision.error?.message}`);
|
|
710
|
+
return decision.state;
|
|
711
|
+
}
|
|
712
|
+
function createPeerMeshState(context, peer) {
|
|
713
|
+
const local = identity(context, peer.peerId);
|
|
714
|
+
const admittedPeerIds = unique([
|
|
715
|
+
peer.peerId,
|
|
716
|
+
context.owner.peerId,
|
|
717
|
+
...peer.neighborPeerIds,
|
|
718
|
+
]);
|
|
719
|
+
return createMeshAllocationInboundRuntimeState(createMeshCoordinationState({ identity: local }), createMeshDiscoveryState({
|
|
720
|
+
identity: local,
|
|
721
|
+
subscriptions: ['membership', 'capability', 'objective'],
|
|
722
|
+
admittedPeers: admittedPeerIds.map((peerId) => ({
|
|
723
|
+
peerId,
|
|
724
|
+
instanceIds: [context.peersById.get(peerId)?.peerInstanceId ?? 'missing'],
|
|
725
|
+
validUntil: wallTime(context, 86_400_000),
|
|
726
|
+
})),
|
|
727
|
+
}), createMeshObjectiveWorkState({
|
|
728
|
+
identity: local,
|
|
729
|
+
issuerAuthorities: [
|
|
730
|
+
{
|
|
731
|
+
peerId: context.owner.peerId,
|
|
732
|
+
keyIds: [keyId(context.owner.peerId)],
|
|
733
|
+
validUntil: wallTime(context, 86_400_000),
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
}), createMeshAllocationState({ identity: local }), createMeshCoordinationInboundState({ identity: local }));
|
|
737
|
+
}
|
|
738
|
+
async function discoverPeer(context, ownerState, peer) {
|
|
739
|
+
const capabilityIds = [
|
|
740
|
+
planningCapabilityId(peer.peerId),
|
|
741
|
+
...peer.capabilityKeys.map((capability) => capabilityId(peer.peerId, capability)),
|
|
742
|
+
].sort();
|
|
743
|
+
const card = await signEnvelope(context, {
|
|
744
|
+
senderPeerId: peer.peerId,
|
|
745
|
+
type: 'peer.card',
|
|
746
|
+
audience: { kind: 'mesh', topic: 'membership' },
|
|
747
|
+
payload: {
|
|
748
|
+
type: 'peer.card',
|
|
749
|
+
peerCardId: scopedId(context, `card-${peer.peerId}`, 1),
|
|
750
|
+
cardRevision: 1,
|
|
751
|
+
subjectPeerId: peer.peerId,
|
|
752
|
+
instanceId: peer.peerInstanceId,
|
|
753
|
+
protocolVersions: [WIRE_VERSION],
|
|
754
|
+
transportHints: [`https://${peer.peerId}.invalid/mesh`],
|
|
755
|
+
capabilityIds,
|
|
756
|
+
validFrom: wallTime(context, 0),
|
|
757
|
+
validUntil: wallTime(context, 120_000),
|
|
758
|
+
},
|
|
759
|
+
sentAtOffsetMs: 0,
|
|
760
|
+
expiresAtOffsetMs: 120_000,
|
|
761
|
+
});
|
|
762
|
+
ownerState = admitDiscovery(context, ownerState, card.verified, []);
|
|
763
|
+
const planning = await signEnvelope(context, {
|
|
764
|
+
senderPeerId: peer.peerId,
|
|
765
|
+
type: 'capability.advertise',
|
|
766
|
+
audience: { kind: 'mesh', topic: 'capability' },
|
|
767
|
+
payload: {
|
|
768
|
+
type: 'capability.advertise',
|
|
769
|
+
advertisementId: planningAdvertisementId(peer.peerId),
|
|
770
|
+
capabilityId: planningCapabilityId(peer.peerId),
|
|
771
|
+
capabilityRevision: 1,
|
|
772
|
+
ownerPeerId: peer.peerId,
|
|
773
|
+
capabilityKey: PLANNING_MESH_CAPABILITY_PROFILE_V1.capabilityKey,
|
|
774
|
+
version: PLANNING_MESH_CAPABILITY_PROFILE_V1.version,
|
|
775
|
+
variant: PLANNING_MESH_CAPABILITY_PROFILE_V1.variant,
|
|
776
|
+
inputMediaTypes: PLANNING_MESH_CAPABILITY_PROFILE_V1.inputMediaTypes,
|
|
777
|
+
outputMediaTypes: PLANNING_MESH_CAPABILITY_PROFILE_V1.outputMediaTypes,
|
|
778
|
+
attributes: PLANNING_MESH_CAPABILITY_PROFILE_V1.attributes,
|
|
779
|
+
validFrom: wallTime(context, 0),
|
|
780
|
+
validUntil: wallTime(context, 120_000),
|
|
781
|
+
maximumConcurrency: 1,
|
|
782
|
+
maximumPayloadBytes: 262_144,
|
|
783
|
+
},
|
|
784
|
+
sentAtOffsetMs: 0,
|
|
785
|
+
expiresAtOffsetMs: 120_000,
|
|
786
|
+
});
|
|
787
|
+
ownerState = admitDiscovery(context, ownerState, planning.verified, [
|
|
788
|
+
PLANNING_WORK_EXTENSION_KEY_V1,
|
|
789
|
+
]);
|
|
790
|
+
for (const capabilityKey of peer.capabilityKeys) {
|
|
791
|
+
const capability = await signEnvelope(context, {
|
|
792
|
+
senderPeerId: peer.peerId,
|
|
793
|
+
type: 'capability.advertise',
|
|
794
|
+
audience: { kind: 'mesh', topic: 'capability' },
|
|
795
|
+
payload: {
|
|
796
|
+
type: 'capability.advertise',
|
|
797
|
+
advertisementId: capabilityAdvertisementId(peer.peerId, capabilityKey),
|
|
798
|
+
capabilityId: capabilityId(peer.peerId, capabilityKey),
|
|
799
|
+
capabilityRevision: 1,
|
|
800
|
+
ownerPeerId: peer.peerId,
|
|
801
|
+
capabilityKey,
|
|
802
|
+
version: '1',
|
|
803
|
+
inputMediaTypes: ['application/json'],
|
|
804
|
+
outputMediaTypes: ['application/json'],
|
|
805
|
+
attributes: {},
|
|
806
|
+
validFrom: wallTime(context, 0),
|
|
807
|
+
validUntil: wallTime(context, 120_000),
|
|
808
|
+
maximumConcurrency: 1,
|
|
809
|
+
maximumPayloadBytes: 262_144,
|
|
810
|
+
},
|
|
811
|
+
sentAtOffsetMs: 0,
|
|
812
|
+
expiresAtOffsetMs: 120_000,
|
|
813
|
+
});
|
|
814
|
+
ownerState = admitDiscovery(context, ownerState, capability.verified, []);
|
|
815
|
+
}
|
|
816
|
+
return ownerState;
|
|
817
|
+
}
|
|
818
|
+
function admitDiscovery(context, state, envelope, supportedCriticalExtensions) {
|
|
819
|
+
const receivedAt = advance(context);
|
|
820
|
+
const decision = evaluateVerifiedMeshDiscoveryEnvelope(createMeshDiscoveryRuntimeState(state.coordination, state.discovery), {
|
|
821
|
+
envelope,
|
|
822
|
+
verifiedAt: wallTime(context, 1_000),
|
|
823
|
+
receivedAt,
|
|
824
|
+
...(supportedCriticalExtensions.length === 0
|
|
825
|
+
? {}
|
|
826
|
+
: { supportedCriticalExtensions }),
|
|
827
|
+
});
|
|
828
|
+
countInteraction(context, envelope, context.owner.peerId);
|
|
829
|
+
if (!decision.accepted)
|
|
830
|
+
throw new Error(`closed_loop_discovery_rejected:${decision.code}`);
|
|
831
|
+
return replaceMeshDomains(state, {
|
|
832
|
+
coordination: decision.state.coordination,
|
|
833
|
+
discovery: decision.state.discovery,
|
|
834
|
+
objectives: state.objectives,
|
|
835
|
+
}, receivedAt);
|
|
836
|
+
}
|
|
837
|
+
function objectivePayload(context, offerRecipientPeerIds) {
|
|
838
|
+
const { missionIntent, mandate } = context.input;
|
|
839
|
+
// Recovery can only award a replacement that has both a received offer and
|
|
840
|
+
// its own bid. Bind the witness pool to that exact offer cohort, rather than
|
|
841
|
+
// to unrelated topology positions. The cohort is bounded by the recipient
|
|
842
|
+
// selection limit (32), which is also the protocol witness ceiling.
|
|
843
|
+
// Three offer recipients provide a two-witness quorum even when the
|
|
844
|
+
// original assignee is one of them, while keeping recovery fanout small and
|
|
845
|
+
// comfortably inside the protocol recipient limit.
|
|
846
|
+
const witnesses = [...new Set(offerRecipientPeerIds)].sort().slice(0, 3);
|
|
847
|
+
// The public objective contract requires at least three witnesses even for
|
|
848
|
+
// the minimum three-peer reference topology, where only two are in-mesh.
|
|
849
|
+
if (witnesses.length < 3)
|
|
850
|
+
witnesses.push(`witness:${context.input.seed}:external`);
|
|
851
|
+
witnesses.sort();
|
|
852
|
+
return {
|
|
853
|
+
type: 'objective.announce',
|
|
854
|
+
objectiveDocumentId: missionIntent.objective.objectiveDocumentId,
|
|
855
|
+
objectiveId: missionIntent.objective.objectiveId,
|
|
856
|
+
objectiveRevision: missionIntent.objective.objectiveRevision,
|
|
857
|
+
issuerPeerId: context.owner.peerId,
|
|
858
|
+
successCriteria: [...missionIntent.outcomeStatements],
|
|
859
|
+
permittedCapabilityKeys: [...missionIntent.permittedCapabilityKeys],
|
|
860
|
+
maximumWorkItems: Math.min(4, mandate.statement.budget.maximumConcurrentWorkReservations),
|
|
861
|
+
maximumConcurrentAssignments: Math.min(2, mandate.statement.budget.maximumConcurrentWorkReservations),
|
|
862
|
+
maximumBudgetUnits: mandate.statement.budget.totalBudgetUnits,
|
|
863
|
+
bidWindowMs: 60_000,
|
|
864
|
+
acceptanceWindowMs: 30_000,
|
|
865
|
+
maximumLeaseDurationMs: 3_600_000,
|
|
866
|
+
recoveryGraceMs: 60_000,
|
|
867
|
+
maximumLeaseRenewals: 3,
|
|
868
|
+
recoveryWitnessPeerIds: witnesses,
|
|
869
|
+
recoveryWitnessThreshold: Math.min(2, witnesses.length - 1),
|
|
870
|
+
contentReference: `${DELEGATION_MANDATE_REFERENCE_PREFIX_V1}${mandate.mandateDigest}`,
|
|
871
|
+
validFrom: missionIntent.validFrom,
|
|
872
|
+
validUntil: earlierTimestamp(missionIntent.validUntil, mandate.statement.validUntil),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function createOfferPayload(context, projection, offerId) {
|
|
876
|
+
const objective = context.input.missionIntent.objective;
|
|
877
|
+
if (projection.work.inputReference === undefined)
|
|
878
|
+
throw new Error('closed_loop_work_input_reference_missing');
|
|
879
|
+
return {
|
|
880
|
+
type: 'work.offer',
|
|
881
|
+
offerId,
|
|
882
|
+
objectiveId: objective.objectiveId,
|
|
883
|
+
objectiveDocumentId: objective.objectiveDocumentId,
|
|
884
|
+
objectiveRevision: objective.objectiveRevision,
|
|
885
|
+
workItemId: projection.workItemId,
|
|
886
|
+
workItemRevision: projection.workItemRevision,
|
|
887
|
+
ownerPeerId: context.owner.peerId,
|
|
888
|
+
ownerEpoch: 1,
|
|
889
|
+
offerAttempt: 1,
|
|
890
|
+
requiredCapabilityKeys: projection.work.requiredCapabilityKeys,
|
|
891
|
+
matchingAttributes: projection.work.matchingAttributes ?? {},
|
|
892
|
+
inputReference: projection.work.inputReference,
|
|
893
|
+
completionCriteria: projection.work.completionCriteria,
|
|
894
|
+
budgetReservationUnits: projection.work.budgetReservationUnits,
|
|
895
|
+
bidDeadline: wallTime(context, 60_000),
|
|
896
|
+
workDeadline: projection.work.workDeadline,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function currentAuthorities(meshStates, ownerPeerId, winnerPeerId, objectiveId, workItemId, checkpointId) {
|
|
900
|
+
const objective = meshStates[ownerPeerId].objectives.objectives[objectiveId];
|
|
901
|
+
const workItem = Object.values(meshStates[ownerPeerId].objectives.workItems).find((candidate) => candidate.workItemId === workItemId);
|
|
902
|
+
const execution = Object.values(meshStates[winnerPeerId].allocation.executionHeads).find((candidate) => candidate.workItemId === workItemId && candidate.phase === 'active');
|
|
903
|
+
const fenceHead = Object.values(meshStates[winnerPeerId].allocation.assignmentFenceHeads).find((candidate) => candidate.workItemId === workItemId && candidate.phase === 'active');
|
|
904
|
+
const checkpoint = meshStates[winnerPeerId].allocation.executionRecords[checkpointId];
|
|
905
|
+
if (!objective || !workItem || !execution || !fenceHead || !checkpoint)
|
|
906
|
+
throw new Error('closed_loop_authority_head_missing');
|
|
907
|
+
return { objective, workItem, execution, fenceHead, checkpoint };
|
|
908
|
+
}
|
|
909
|
+
async function finalizeMeshResult(input) {
|
|
910
|
+
const { context, workContract } = input;
|
|
911
|
+
const resultId = scopedId(context, 'result', 1);
|
|
912
|
+
const resultEnvelope = await signEnvelope(context, {
|
|
913
|
+
senderPeerId: input.winnerPeerId,
|
|
914
|
+
type: 'work.result',
|
|
915
|
+
audience: { kind: 'peer', peerId: input.ownerPeerId },
|
|
916
|
+
objectiveId: workContract.objective.objectiveId,
|
|
917
|
+
causationId: input.checkpoint.envelope.messageId,
|
|
918
|
+
payload: {
|
|
919
|
+
type: 'work.result',
|
|
920
|
+
resultId,
|
|
921
|
+
resultDigest: meshContentDigest(input.resultDigest),
|
|
922
|
+
objectiveId: workContract.objective.objectiveId,
|
|
923
|
+
objectiveDocumentId: workContract.objective.objectiveDocumentId,
|
|
924
|
+
objectiveRevision: workContract.objective.objectiveRevision,
|
|
925
|
+
workItemId: workContract.assignment.workItemId,
|
|
926
|
+
workItemRevision: workContract.assignment.workItemRevision,
|
|
927
|
+
ownerPeerId: workContract.assignment.ownerPeerId,
|
|
928
|
+
ownerEpoch: 1,
|
|
929
|
+
assigneePeerId: input.winnerPeerId,
|
|
930
|
+
awardId: input.awardId,
|
|
931
|
+
acceptanceId: input.acceptanceId,
|
|
932
|
+
assignmentEpoch: workContract.assignment.assignmentEpoch,
|
|
933
|
+
assignmentAuthorityId: workContract.assignment.assignmentAuthorityId,
|
|
934
|
+
fencingToken: workContract.assignment.fencingToken,
|
|
935
|
+
leaseExpiresAt: input.leaseExpiresAt,
|
|
936
|
+
checkpointId: input.checkpointId,
|
|
937
|
+
resultSummary: input.resultSummary,
|
|
938
|
+
},
|
|
939
|
+
sentAtOffsetMs: 14_000,
|
|
940
|
+
expiresAtOffsetMs: 90_000,
|
|
941
|
+
});
|
|
942
|
+
const finalizedAt = advance(context);
|
|
943
|
+
const local = evaluateMeshAllocationCommand(allocationRuntime(input.meshStates[input.winnerPeerId]), {
|
|
944
|
+
kind: 'allocation.execution',
|
|
945
|
+
preparedAt: finalizedAt,
|
|
946
|
+
envelope: resultEnvelope.signed,
|
|
947
|
+
}, wallTime(context, 14_000), finalizedAt);
|
|
948
|
+
if (!local.accepted)
|
|
949
|
+
throw new Error(`closed_loop_local_result_rejected:${local.code}`);
|
|
950
|
+
const nextMeshStates = {
|
|
951
|
+
...input.meshStates,
|
|
952
|
+
[input.winnerPeerId]: retainInbound(input.meshStates[input.winnerPeerId], local.state),
|
|
953
|
+
};
|
|
954
|
+
const owner = await input.allocationInbound.process(nextMeshStates[input.ownerPeerId], {
|
|
955
|
+
envelope: resultEnvelope.signed,
|
|
956
|
+
verifiedAt: wallTime(context, 14_000),
|
|
957
|
+
receivedAt: finalizedAt,
|
|
958
|
+
});
|
|
959
|
+
countInteraction(context, resultEnvelope.signed, input.ownerPeerId);
|
|
960
|
+
if (!owner.accepted)
|
|
961
|
+
throw new Error(`closed_loop_result_rejected:${owner.code}`);
|
|
962
|
+
nextMeshStates[input.ownerPeerId] = owner.state;
|
|
963
|
+
const result = nextMeshStates[input.winnerPeerId].allocation.executionRecords[resultId];
|
|
964
|
+
if (!result || result.recordType !== 'result')
|
|
965
|
+
throw new Error('closed_loop_result_evidence_missing');
|
|
966
|
+
return Object.freeze({
|
|
967
|
+
schemaVersion: 1,
|
|
968
|
+
winnerPeerId: input.winnerPeerId,
|
|
969
|
+
discoveredPeerIds: input.discoveredPeerIds,
|
|
970
|
+
offerRecipientPeerIds: input.offerRecipientPeerIds,
|
|
971
|
+
bidderPeerIds: input.bidderPeerIds,
|
|
972
|
+
workContract,
|
|
973
|
+
roleBinding: input.roleBinding,
|
|
974
|
+
result,
|
|
975
|
+
planningStates: input.planningStates,
|
|
976
|
+
meshStates: Object.freeze(nextMeshStates),
|
|
977
|
+
logicalTimeMs: finalizedAt,
|
|
978
|
+
interactionCount: context.interactionCount,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function assertEffectBoundToWork(attempt, receipt, workContract) {
|
|
982
|
+
if (receipt.attemptDigest !== attempt.attemptDigest ||
|
|
983
|
+
receipt.idempotencyKey !== attempt.idempotencyKey ||
|
|
984
|
+
attempt.tenantId !== workContract.tenantId ||
|
|
985
|
+
attempt.peerId !== workContract.assignment.assignedPeerId ||
|
|
986
|
+
attempt.peerInstanceId !== workContract.assignment.assignedInstanceId ||
|
|
987
|
+
attempt.workItemId !== workContract.assignment.workItemId ||
|
|
988
|
+
attempt.workItemRevision !== workContract.assignment.workItemRevision ||
|
|
989
|
+
attempt.workContractId !== workContract.workContractId ||
|
|
990
|
+
attempt.workContractDigest !== workContract.workContractDigest ||
|
|
991
|
+
attempt.assignmentEpoch !== workContract.assignment.assignmentEpoch ||
|
|
992
|
+
attempt.authorityGeneration !== workContract.assignment.authorityGeneration ||
|
|
993
|
+
attempt.fencingToken !== workContract.assignment.fencingToken)
|
|
994
|
+
throw new Error('closed_loop_effect_binding_mismatch');
|
|
995
|
+
}
|
|
996
|
+
async function signEnvelope(context, input) {
|
|
997
|
+
const peer = context.peersById.get(input.senderPeerId);
|
|
998
|
+
const privateKey = context.input.runner.privateKeys[input.senderPeerId];
|
|
999
|
+
if (!peer || !privateKey)
|
|
1000
|
+
throw new Error('closed_loop_signing_identity_missing');
|
|
1001
|
+
const sequence = (context.sequences.get(peer.peerId) ?? 0) + 1;
|
|
1002
|
+
context.sequences.set(peer.peerId, sequence);
|
|
1003
|
+
const payloadHash = await payloadDigest(context.input.runner.crypto, input.payload);
|
|
1004
|
+
const envelope = {
|
|
1005
|
+
protocol: 'agentplat.mesh',
|
|
1006
|
+
wireVersion: WIRE_VERSION,
|
|
1007
|
+
messageId: deterministicMessageId(context.input.seed, context.input.peers.findIndex((candidate) => candidate.peerId === peer.peerId), sequence),
|
|
1008
|
+
tenantId: context.input.missionIntent.tenantId,
|
|
1009
|
+
meshId: context.input.missionIntent.objective.meshId,
|
|
1010
|
+
...(input.objectiveId === undefined
|
|
1011
|
+
? {}
|
|
1012
|
+
: { objectiveId: input.objectiveId }),
|
|
1013
|
+
type: input.type,
|
|
1014
|
+
sender: { peerId: peer.peerId, instanceId: peer.peerInstanceId },
|
|
1015
|
+
audience: input.audience,
|
|
1016
|
+
sequence,
|
|
1017
|
+
sentAt: wallTime(context, input.sentAtOffsetMs ?? 0),
|
|
1018
|
+
expiresAt: wallTime(context, input.expiresAtOffsetMs ?? 120_000),
|
|
1019
|
+
...(input.causationId === undefined
|
|
1020
|
+
? {}
|
|
1021
|
+
: { causationId: input.causationId }),
|
|
1022
|
+
...(input.extensions === undefined ? {} : { extensions: input.extensions }),
|
|
1023
|
+
...(input.criticalExtensions === undefined
|
|
1024
|
+
? {}
|
|
1025
|
+
: { criticalExtensions: input.criticalExtensions }),
|
|
1026
|
+
payloadHash,
|
|
1027
|
+
payload: input.payload,
|
|
1028
|
+
proof: { algorithm: 'Ed25519', keyId: keyId(peer.peerId), value: '' },
|
|
1029
|
+
};
|
|
1030
|
+
const preflight = validateSignedMeshEnvelope({
|
|
1031
|
+
...envelope,
|
|
1032
|
+
payloadHash: `sha256:${'A'.repeat(43)}`,
|
|
1033
|
+
proof: { ...envelope.proof, value: 'A'.repeat(86) },
|
|
1034
|
+
}, { acceptedWireVersions: [WIRE_VERSION] });
|
|
1035
|
+
if (!preflight.ok)
|
|
1036
|
+
throw new Error(`closed_loop_envelope_invalid:${input.type}:${preflight.issues
|
|
1037
|
+
.map((issue) => `${issue.code}:${issue.path}`)
|
|
1038
|
+
.join(',')}`);
|
|
1039
|
+
const signed = await context.input.runner.signer.sign({ envelope, privateKey });
|
|
1040
|
+
const verified = await verifyMeshEnvelope({
|
|
1041
|
+
envelope: signed,
|
|
1042
|
+
resolver: context.input.runner.resolver,
|
|
1043
|
+
policy: context.input.runner.cryptoPolicy,
|
|
1044
|
+
verifiedAt: wallTime(context, Math.max(input.sentAtOffsetMs ?? 0, 1_000)),
|
|
1045
|
+
crypto: context.input.runner.crypto,
|
|
1046
|
+
});
|
|
1047
|
+
if (!verified.verified)
|
|
1048
|
+
throw new Error(`closed_loop_signature_rejected:${verified.code}`);
|
|
1049
|
+
return Object.freeze({ signed, verified: verified.envelope });
|
|
1050
|
+
}
|
|
1051
|
+
function replaceMeshDomains(state, domains, logicalTimeMs) {
|
|
1052
|
+
return createMeshAllocationInboundRuntimeState(domains.coordination, restoreMeshDiscoveryState({
|
|
1053
|
+
...domains.discovery,
|
|
1054
|
+
lastLogicalTime: logicalTimeMs,
|
|
1055
|
+
}), restoreMeshObjectiveWorkState({
|
|
1056
|
+
...domains.objectives,
|
|
1057
|
+
lastLogicalTime: logicalTimeMs,
|
|
1058
|
+
}), restoreMeshAllocationState({
|
|
1059
|
+
...state.allocation,
|
|
1060
|
+
lastLogicalTime: logicalTimeMs,
|
|
1061
|
+
}), state.inbound);
|
|
1062
|
+
}
|
|
1063
|
+
function retainInbound(state, domains) {
|
|
1064
|
+
return createMeshAllocationInboundRuntimeState(domains.coordination, domains.discovery, domains.objectives, domains.allocation, state.inbound);
|
|
1065
|
+
}
|
|
1066
|
+
function allocationRuntime(state) {
|
|
1067
|
+
return createMeshAllocationRuntimeState(state.coordination, state.discovery, state.objectives, state.allocation);
|
|
1068
|
+
}
|
|
1069
|
+
function identity(context, peerId) {
|
|
1070
|
+
const peer = context.peersById.get(peerId);
|
|
1071
|
+
if (!peer)
|
|
1072
|
+
throw new Error('closed_loop_peer_missing');
|
|
1073
|
+
return Object.freeze({
|
|
1074
|
+
tenantId: context.input.missionIntent.tenantId,
|
|
1075
|
+
meshId: context.input.missionIntent.objective.meshId,
|
|
1076
|
+
peerId,
|
|
1077
|
+
instanceId: peer.peerInstanceId,
|
|
1078
|
+
keyId: keyId(peerId),
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
function advance(context) {
|
|
1082
|
+
context.logicalTimeMs += 1;
|
|
1083
|
+
if (context.logicalTimeMs > context.input.maximumLogicalTimeMs)
|
|
1084
|
+
throw new RangeError('closed_loop_logical_time_exceeded');
|
|
1085
|
+
return context.logicalTimeMs;
|
|
1086
|
+
}
|
|
1087
|
+
function countInteraction(context, record, peerId) {
|
|
1088
|
+
context.interactionCount += 1;
|
|
1089
|
+
if (context.interactionCount > MAXIMUM_INTERACTIONS)
|
|
1090
|
+
throw new RangeError('closed_loop_interaction_limit_exceeded');
|
|
1091
|
+
const journal = collectiveTraceJournalForOwnerV2(context.input.runner);
|
|
1092
|
+
if (!journal)
|
|
1093
|
+
return;
|
|
1094
|
+
journal.append({
|
|
1095
|
+
logicalTimeMs: context.logicalTimeMs,
|
|
1096
|
+
peerId,
|
|
1097
|
+
component: 'mesh',
|
|
1098
|
+
kind: 'mesh.message.delivered',
|
|
1099
|
+
status: 'accepted',
|
|
1100
|
+
reasonCode: null,
|
|
1101
|
+
recordDigest: digestPlanningJsonV1('environment-state-v1', {
|
|
1102
|
+
messageId: record.messageId,
|
|
1103
|
+
type: record.type,
|
|
1104
|
+
payloadHash: record.payloadHash,
|
|
1105
|
+
sequence: record.sequence,
|
|
1106
|
+
}),
|
|
1107
|
+
stateDigestBefore: null,
|
|
1108
|
+
stateDigestAfter: null,
|
|
1109
|
+
faultBinding: null,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
function assertExactDataRecord(value, expectedKeys, errorCode) {
|
|
1113
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
1114
|
+
throw new TypeError(errorCode);
|
|
1115
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1116
|
+
const actual = Object.getOwnPropertyNames(value).sort();
|
|
1117
|
+
const expected = [...expectedKeys].sort();
|
|
1118
|
+
if ((prototype !== Object.prototype && prototype !== null) ||
|
|
1119
|
+
Object.getOwnPropertySymbols(value).length !== 0 ||
|
|
1120
|
+
actual.length !== expected.length ||
|
|
1121
|
+
actual.some((key, index) => key !== expected[index]))
|
|
1122
|
+
throw new TypeError(errorCode);
|
|
1123
|
+
for (const key of actual) {
|
|
1124
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1125
|
+
if (!descriptor || !descriptor.enumerable || !('value' in descriptor))
|
|
1126
|
+
throw new TypeError(errorCode);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function wallTime(context, offsetMs) {
|
|
1130
|
+
return new Date(context.baseWallTimeMs + offsetMs).toISOString();
|
|
1131
|
+
}
|
|
1132
|
+
function earlierTimestamp(left, right) {
|
|
1133
|
+
return Date.parse(left) <= Date.parse(right) ? left : right;
|
|
1134
|
+
}
|
|
1135
|
+
function scopedId(context, kind, revision) {
|
|
1136
|
+
return `${kind}:${context.input.seed}:${revision}`;
|
|
1137
|
+
}
|
|
1138
|
+
function keyId(peerId) {
|
|
1139
|
+
return `key:${peerId}`;
|
|
1140
|
+
}
|
|
1141
|
+
function planningCapabilityId(peerId) {
|
|
1142
|
+
return `capability:planning:${peerId}`;
|
|
1143
|
+
}
|
|
1144
|
+
function planningAdvertisementId(peerId) {
|
|
1145
|
+
return `advertisement:planning:${peerId}`;
|
|
1146
|
+
}
|
|
1147
|
+
function capabilityId(peerId, capabilityKey) {
|
|
1148
|
+
return `capability:${peerId}:${capabilityKey}`;
|
|
1149
|
+
}
|
|
1150
|
+
function capabilityAdvertisementId(peerId, capabilityKey) {
|
|
1151
|
+
return `advertisement:${peerId}:${capabilityKey}`;
|
|
1152
|
+
}
|
|
1153
|
+
function unique(values) {
|
|
1154
|
+
return [...new Set(values)];
|
|
1155
|
+
}
|
|
1156
|
+
function deterministicMessageId(seed, peerOrdinal, sequence) {
|
|
1157
|
+
const bytes = new Uint8Array(16);
|
|
1158
|
+
const view = new DataView(bytes.buffer);
|
|
1159
|
+
view.setUint32(0, seed >>> 0);
|
|
1160
|
+
view.setUint32(4, peerOrdinal >>> 0);
|
|
1161
|
+
view.setUint32(8, Math.floor(seed / 0x1_0000_0000) >>> 0);
|
|
1162
|
+
view.setUint32(12, sequence >>> 0);
|
|
1163
|
+
return base64Url(bytes);
|
|
1164
|
+
}
|
|
1165
|
+
async function payloadDigest(crypto, payload) {
|
|
1166
|
+
const canonical = canonicalizeMeshPayload(payload);
|
|
1167
|
+
if (!canonical.ok)
|
|
1168
|
+
throw new TypeError('closed_loop_payload_invalid');
|
|
1169
|
+
const bytes = Uint8Array.from(canonical.value);
|
|
1170
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes.buffer);
|
|
1171
|
+
return `sha256:${base64Url(new Uint8Array(digest))}`;
|
|
1172
|
+
}
|
|
1173
|
+
async function contentDigest(crypto, value) {
|
|
1174
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value));
|
|
1175
|
+
return `sha256:${base64Url(new Uint8Array(digest))}`;
|
|
1176
|
+
}
|
|
1177
|
+
function meshContentDigest(value) {
|
|
1178
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(value))
|
|
1179
|
+
throw new TypeError('closed_loop_result_digest_invalid');
|
|
1180
|
+
const hex = value.slice('sha256:'.length);
|
|
1181
|
+
const bytes = new Uint8Array(32);
|
|
1182
|
+
for (let index = 0; index < bytes.length; index += 1)
|
|
1183
|
+
bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
|
|
1184
|
+
return `sha256:${base64Url(bytes)}`;
|
|
1185
|
+
}
|
|
1186
|
+
function base64Url(bytes) {
|
|
1187
|
+
let binary = '';
|
|
1188
|
+
for (const byte of bytes)
|
|
1189
|
+
binary += String.fromCharCode(byte);
|
|
1190
|
+
return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/u, '');
|
|
1191
|
+
}
|
|
1192
|
+
//# sourceMappingURL=collective-closed-loop-runtime.js.map
|