@origintrail-official/dkg-agent 10.0.20 → 10.0.21
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/dist/approved-private-replica.d.ts +54 -0
- package/dist/approved-private-replica.d.ts.map +1 -0
- package/dist/approved-private-replica.js +148 -0
- package/dist/approved-private-replica.js.map +1 -0
- package/dist/bounded-operation.d.ts +2 -0
- package/dist/bounded-operation.d.ts.map +1 -1
- package/dist/bounded-operation.js +5 -0
- package/dist/bounded-operation.js.map +1 -1
- package/dist/chain-finality-confirmations-v1.d.ts +7 -0
- package/dist/chain-finality-confirmations-v1.d.ts.map +1 -0
- package/dist/chain-finality-confirmations-v1.js +9 -0
- package/dist/chain-finality-confirmations-v1.js.map +1 -0
- package/dist/chain-reconciler.d.ts +23 -0
- package/dist/chain-reconciler.d.ts.map +1 -1
- package/dist/chain-reconciler.js +35 -0
- package/dist/chain-reconciler.js.map +1 -1
- package/dist/context-graph-member-proof.d.ts +13 -0
- package/dist/context-graph-member-proof.d.ts.map +1 -1
- package/dist/context-graph-member-proof.js +36 -0
- package/dist/context-graph-member-proof.js.map +1 -1
- package/dist/context-graph-membership-persist-scheduler.d.ts +7 -23
- package/dist/context-graph-membership-persist-scheduler.d.ts.map +1 -1
- package/dist/context-graph-membership-persist-scheduler.js +12 -88
- package/dist/context-graph-membership-persist-scheduler.js.map +1 -1
- package/dist/context-graph-meta-projection.d.ts +13 -2
- package/dist/context-graph-meta-projection.d.ts.map +1 -1
- package/dist/context-graph-meta-projection.js +61 -24
- package/dist/context-graph-meta-projection.js.map +1 -1
- package/dist/context-graph-private-meta-proof.d.ts +5 -0
- package/dist/context-graph-private-meta-proof.d.ts.map +1 -1
- package/dist/context-graph-private-meta-proof.js +23 -0
- package/dist/context-graph-private-meta-proof.js.map +1 -1
- package/dist/context-graph-read-authority.d.ts +13 -0
- package/dist/context-graph-read-authority.d.ts.map +1 -1
- package/dist/context-graph-read-authority.js +23 -3
- package/dist/context-graph-read-authority.js.map +1 -1
- package/dist/context-graph-storage-discovery.d.ts +11 -2
- package/dist/context-graph-storage-discovery.d.ts.map +1 -1
- package/dist/context-graph-storage-discovery.js +11 -2
- package/dist/context-graph-storage-discovery.js.map +1 -1
- package/dist/context-graph-subscription-persist-scheduler.d.ts +52 -0
- package/dist/context-graph-subscription-persist-scheduler.d.ts.map +1 -0
- package/dist/context-graph-subscription-persist-scheduler.js +78 -0
- package/dist/context-graph-subscription-persist-scheduler.js.map +1 -0
- package/dist/curator-dial-address.d.ts +16 -0
- package/dist/curator-dial-address.d.ts.map +1 -0
- package/dist/curator-dial-address.js +58 -0
- package/dist/curator-dial-address.js.map +1 -0
- package/dist/curator-meta-refresh.d.ts +2 -0
- package/dist/curator-meta-refresh.d.ts.map +1 -1
- package/dist/curator-meta-refresh.js +2 -44
- package/dist/curator-meta-refresh.js.map +1 -1
- package/dist/curator-peer-connection.d.ts +21 -0
- package/dist/curator-peer-connection.d.ts.map +1 -0
- package/dist/curator-peer-connection.js +41 -0
- package/dist/curator-peer-connection.js.map +1 -0
- package/dist/dkg-agent-base.d.ts +94 -22
- package/dist/dkg-agent-base.d.ts.map +1 -1
- package/dist/dkg-agent-base.js +97 -144
- package/dist/dkg-agent-base.js.map +1 -1
- package/dist/dkg-agent-cg-registry.d.ts +10 -0
- package/dist/dkg-agent-cg-registry.d.ts.map +1 -1
- package/dist/dkg-agent-cg-registry.js +176 -115
- package/dist/dkg-agent-cg-registry.js.map +1 -1
- package/dist/dkg-agent-cg-resolve.d.ts +6 -4
- package/dist/dkg-agent-cg-resolve.d.ts.map +1 -1
- package/dist/dkg-agent-cg-resolve.js +22 -16
- package/dist/dkg-agent-cg-resolve.js.map +1 -1
- package/dist/dkg-agent-constants.d.ts +4 -2
- package/dist/dkg-agent-constants.d.ts.map +1 -1
- package/dist/dkg-agent-constants.js +4 -2
- package/dist/dkg-agent-constants.js.map +1 -1
- package/dist/dkg-agent-crypto.d.ts +101 -21
- package/dist/dkg-agent-crypto.d.ts.map +1 -1
- package/dist/dkg-agent-crypto.js +670 -152
- package/dist/dkg-agent-crypto.js.map +1 -1
- package/dist/dkg-agent-helpers.d.ts +2 -2
- package/dist/dkg-agent-helpers.d.ts.map +1 -1
- package/dist/dkg-agent-helpers.js +3 -3
- package/dist/dkg-agent-helpers.js.map +1 -1
- package/dist/dkg-agent-join.d.ts +12 -1
- package/dist/dkg-agent-join.d.ts.map +1 -1
- package/dist/dkg-agent-join.js +80 -92
- package/dist/dkg-agent-join.js.map +1 -1
- package/dist/dkg-agent-lifecycle.d.ts +58 -15
- package/dist/dkg-agent-lifecycle.d.ts.map +1 -1
- package/dist/dkg-agent-lifecycle.js +295 -114
- package/dist/dkg-agent-lifecycle.js.map +1 -1
- package/dist/dkg-agent-publish.d.ts +33 -40
- package/dist/dkg-agent-publish.d.ts.map +1 -1
- package/dist/dkg-agent-publish.js +190 -256
- package/dist/dkg-agent-publish.js.map +1 -1
- package/dist/dkg-agent-query.d.ts +7 -0
- package/dist/dkg-agent-query.d.ts.map +1 -1
- package/dist/dkg-agent-query.js +75 -63
- package/dist/dkg-agent-query.js.map +1 -1
- package/dist/dkg-agent-registered-private-empty-vm.d.ts +70 -0
- package/dist/dkg-agent-registered-private-empty-vm.d.ts.map +1 -0
- package/dist/dkg-agent-registered-private-empty-vm.js +133 -0
- package/dist/dkg-agent-registered-private-empty-vm.js.map +1 -0
- package/dist/dkg-agent-rfc64-catalog-auto-publish.d.ts +8 -3
- package/dist/dkg-agent-rfc64-catalog-auto-publish.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-catalog-auto-publish.js +45 -51
- package/dist/dkg-agent-rfc64-catalog-auto-publish.js.map +1 -1
- package/dist/dkg-agent-rfc64-catalog.d.ts +63 -87
- package/dist/dkg-agent-rfc64-catalog.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-catalog.js +595 -551
- package/dist/dkg-agent-rfc64-catalog.js.map +1 -1
- package/dist/dkg-agent-swm-host.d.ts +43 -52
- package/dist/dkg-agent-swm-host.d.ts.map +1 -1
- package/dist/dkg-agent-swm-host.js +629 -812
- package/dist/dkg-agent-swm-host.js.map +1 -1
- package/dist/dkg-agent-swm-state.d.ts +15 -0
- package/dist/dkg-agent-swm-state.d.ts.map +1 -1
- package/dist/dkg-agent-swm-state.js +43 -0
- package/dist/dkg-agent-swm-state.js.map +1 -1
- package/dist/dkg-agent-swm-substrate.d.ts +2 -0
- package/dist/dkg-agent-swm-substrate.d.ts.map +1 -1
- package/dist/dkg-agent-swm-substrate.js +110 -53
- package/dist/dkg-agent-swm-substrate.js.map +1 -1
- package/dist/dkg-agent-types.d.ts +62 -26
- package/dist/dkg-agent-types.d.ts.map +1 -1
- package/dist/dkg-agent-types.js.map +1 -1
- package/dist/dkg-agent-vm-promotion.d.ts +2 -0
- package/dist/dkg-agent-vm-promotion.d.ts.map +1 -1
- package/dist/dkg-agent-vm-promotion.js +39 -5
- package/dist/dkg-agent-vm-promotion.js.map +1 -1
- package/dist/dkg-agent.d.ts +8 -1
- package/dist/dkg-agent.d.ts.map +1 -1
- package/dist/dkg-agent.js +75 -45
- package/dist/dkg-agent.js.map +1 -1
- package/dist/exact-graph-content-verifier.d.ts.map +1 -1
- package/dist/exact-graph-content-verifier.js +3 -2
- package/dist/exact-graph-content-verifier.js.map +1 -1
- package/dist/finalization-handler.d.ts +19 -207
- package/dist/finalization-handler.d.ts.map +1 -1
- package/dist/finalization-handler.js +79 -1646
- package/dist/finalization-handler.js.map +1 -1
- package/dist/finalization-recovery-worker.js +1 -1
- package/dist/finalization-recovery-worker.js.map +1 -1
- package/dist/finalized-authority-cold-resolution.d.ts +6 -1
- package/dist/finalized-authority-cold-resolution.d.ts.map +1 -1
- package/dist/finalized-authority-cold-resolution.js +22 -3
- package/dist/finalized-authority-cold-resolution.js.map +1 -1
- package/dist/gossip-session.d.ts +29 -0
- package/dist/gossip-session.d.ts.map +1 -0
- package/dist/gossip-session.js +48 -0
- package/dist/gossip-session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.d.ts +5 -5
- package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.d.ts.map +1 -1
- package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.js +59 -49
- package/dist/internal/context-graph-authority/context-graph-agent-gate-authority.js.map +1 -1
- package/dist/internal/context-graph-authority/finalized-target-projection.d.ts +17 -0
- package/dist/internal/context-graph-authority/finalized-target-projection.d.ts.map +1 -0
- package/dist/internal/context-graph-authority/finalized-target-projection.js +36 -0
- package/dist/internal/context-graph-authority/finalized-target-projection.js.map +1 -0
- package/dist/internal/context-graph-authority/swm-transport-authority.d.ts +43 -7
- package/dist/internal/context-graph-authority/swm-transport-authority.d.ts.map +1 -1
- package/dist/internal/context-graph-authority/swm-transport-authority.js +135 -18
- package/dist/internal/context-graph-authority/swm-transport-authority.js.map +1 -1
- package/dist/internal/context-graph-binding-abort.d.ts +3 -0
- package/dist/internal/context-graph-binding-abort.d.ts.map +1 -0
- package/dist/internal/context-graph-binding-abort.js +32 -0
- package/dist/internal/context-graph-binding-abort.js.map +1 -0
- package/dist/internal/context-graph-cache-invalidating-store.d.ts +3 -0
- package/dist/internal/context-graph-cache-invalidating-store.d.ts.map +1 -0
- package/dist/internal/context-graph-cache-invalidating-store.js +167 -0
- package/dist/internal/context-graph-cache-invalidating-store.js.map +1 -0
- package/dist/internal/context-graph-meta-record-copy.d.ts +3 -0
- package/dist/internal/context-graph-meta-record-copy.d.ts.map +1 -0
- package/dist/internal/context-graph-meta-record-copy.js +21 -0
- package/dist/internal/context-graph-meta-record-copy.js.map +1 -0
- package/dist/internal/gossip/host-mode-handler.d.ts +11 -0
- package/dist/internal/gossip/host-mode-handler.d.ts.map +1 -0
- package/dist/internal/gossip/host-mode-handler.js +41 -0
- package/dist/internal/gossip/host-mode-handler.js.map +1 -0
- package/dist/internal/join-encryption-key-bundle.d.ts +10 -0
- package/dist/internal/join-encryption-key-bundle.d.ts.map +1 -0
- package/dist/internal/join-encryption-key-bundle.js +72 -0
- package/dist/internal/join-encryption-key-bundle.js.map +1 -0
- package/dist/internal/knowledge-asset-vm-publish-request.d.ts +31 -0
- package/dist/internal/knowledge-asset-vm-publish-request.d.ts.map +1 -0
- package/dist/internal/knowledge-asset-vm-publish-request.js +91 -0
- package/dist/internal/knowledge-asset-vm-publish-request.js.map +1 -0
- package/dist/internal/lifecycle-sync-policy.d.ts +5 -0
- package/dist/internal/lifecycle-sync-policy.d.ts.map +1 -0
- package/dist/internal/lifecycle-sync-policy.js +12 -0
- package/dist/internal/lifecycle-sync-policy.js.map +1 -0
- package/dist/internal/local-private-member.d.ts +14 -0
- package/dist/internal/local-private-member.d.ts.map +1 -0
- package/dist/internal/local-private-member.js +21 -0
- package/dist/internal/local-private-member.js.map +1 -0
- package/dist/internal/promote/assertion-promote-precommit.d.ts +41 -1
- package/dist/internal/promote/assertion-promote-precommit.d.ts.map +1 -1
- package/dist/internal/promote/assertion-promote-precommit.js +74 -5
- package/dist/internal/promote/assertion-promote-precommit.js.map +1 -1
- package/dist/internal/storage-ack-owned-request.d.ts +16 -0
- package/dist/internal/storage-ack-owned-request.d.ts.map +1 -0
- package/dist/internal/storage-ack-owned-request.js +16 -0
- package/dist/internal/storage-ack-owned-request.js.map +1 -0
- package/dist/internal/vm-reconcile-local-admission-wait.d.ts +96 -0
- package/dist/internal/vm-reconcile-local-admission-wait.d.ts.map +1 -0
- package/dist/internal/vm-reconcile-local-admission-wait.js +226 -0
- package/dist/internal/vm-reconcile-local-admission-wait.js.map +1 -0
- package/dist/internal/vm-reconcile-read-authority.d.ts +27 -0
- package/dist/internal/vm-reconcile-read-authority.d.ts.map +1 -0
- package/dist/internal/vm-reconcile-read-authority.js +33 -0
- package/dist/internal/vm-reconcile-read-authority.js.map +1 -0
- package/dist/internal/vm-reconcile-sweep-admission.d.ts +2 -0
- package/dist/internal/vm-reconcile-sweep-admission.d.ts.map +1 -1
- package/dist/internal/vm-reconcile-sweep.d.ts +16 -4
- package/dist/internal/vm-reconcile-sweep.d.ts.map +1 -1
- package/dist/internal/vm-reconcile-sweep.js +91 -50
- package/dist/internal/vm-reconcile-sweep.js.map +1 -1
- package/dist/internal/workspace-projected-delegatees.d.ts +3 -0
- package/dist/internal/workspace-projected-delegatees.d.ts.map +1 -0
- package/dist/internal/workspace-projected-delegatees.js +34 -0
- package/dist/internal/workspace-projected-delegatees.js.map +1 -0
- package/dist/join-approval-restart-projection.d.ts +14 -0
- package/dist/join-approval-restart-projection.d.ts.map +1 -0
- package/dist/join-approval-restart-projection.js +32 -0
- package/dist/join-approval-restart-projection.js.map +1 -0
- package/dist/keyed-persist-scheduler.d.ts +72 -0
- package/dist/keyed-persist-scheduler.d.ts.map +1 -0
- package/dist/keyed-persist-scheduler.js +140 -0
- package/dist/keyed-persist-scheduler.js.map +1 -0
- package/dist/map-with-concurrency.d.ts +7 -0
- package/dist/map-with-concurrency.d.ts.map +1 -1
- package/dist/map-with-concurrency.js +36 -0
- package/dist/map-with-concurrency.js.map +1 -1
- package/dist/p2p/network-admission-coordinator.d.ts +15 -0
- package/dist/p2p/network-admission-coordinator.d.ts.map +1 -1
- package/dist/p2p/network-admission-coordinator.js +20 -2
- package/dist/p2p/network-admission-coordinator.js.map +1 -1
- package/dist/prepare-unscoped-context-graph-read-checks.d.ts.map +1 -1
- package/dist/prepare-unscoped-context-graph-read-checks.js +12 -0
- package/dist/prepare-unscoped-context-graph-read-checks.js.map +1 -1
- package/dist/registered-context-graph-authority.d.ts +7 -0
- package/dist/registered-context-graph-authority.d.ts.map +1 -1
- package/dist/registered-private-empty-vm-attempt-v1.d.ts +34 -0
- package/dist/registered-private-empty-vm-attempt-v1.d.ts.map +1 -0
- package/dist/registered-private-empty-vm-attempt-v1.js +120 -0
- package/dist/registered-private-empty-vm-attempt-v1.js.map +1 -0
- package/dist/reserved-ka-id-allocation.d.ts +12 -0
- package/dist/reserved-ka-id-allocation.d.ts.map +1 -0
- package/dist/reserved-ka-id-allocation.js +25 -0
- package/dist/reserved-ka-id-allocation.js.map +1 -0
- package/dist/rfc64/authority-rpc-circuit-breaker-v1.d.ts +5 -3
- package/dist/rfc64/authority-rpc-circuit-breaker-v1.d.ts.map +1 -1
- package/dist/rfc64/authority-rpc-circuit-breaker-v1.js +9 -0
- package/dist/rfc64/authority-rpc-circuit-breaker-v1.js.map +1 -1
- package/dist/rfc64/catalog-completion-evidence-v1.d.ts +38 -0
- package/dist/rfc64/catalog-completion-evidence-v1.d.ts.map +1 -0
- package/dist/rfc64/catalog-completion-evidence-v1.js +49 -0
- package/dist/rfc64/catalog-completion-evidence-v1.js.map +1 -0
- package/dist/rfc64/catalog-operational-applied-heads-v1.d.ts +6 -3
- package/dist/rfc64/catalog-operational-applied-heads-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-operational-applied-heads-v1.js +6 -3
- package/dist/rfc64/catalog-operational-applied-heads-v1.js.map +1 -1
- package/dist/rfc64/catalog-operational-reads-v1.d.ts +13 -0
- package/dist/rfc64/catalog-operational-reads-v1.d.ts.map +1 -0
- package/dist/rfc64/catalog-operational-reads-v1.js +56 -0
- package/dist/rfc64/catalog-operational-reads-v1.js.map +1 -0
- package/dist/rfc64/catalog-operational-targets-v1.d.ts +55 -0
- package/dist/rfc64/catalog-operational-targets-v1.d.ts.map +1 -0
- package/dist/rfc64/catalog-operational-targets-v1.js +379 -0
- package/dist/rfc64/catalog-operational-targets-v1.js.map +1 -0
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts +2 -0
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.js +18 -25
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.js.map +1 -1
- package/dist/rfc64/legacy-swm-boundary-codec-v1.d.ts +25 -0
- package/dist/rfc64/legacy-swm-boundary-codec-v1.d.ts.map +1 -0
- package/dist/rfc64/legacy-swm-boundary-codec-v1.js +127 -0
- package/dist/rfc64/legacy-swm-boundary-codec-v1.js.map +1 -0
- package/dist/rfc64/legacy-swm-boundary-v1.d.ts +12 -0
- package/dist/rfc64/legacy-swm-boundary-v1.d.ts.map +1 -1
- package/dist/rfc64/legacy-swm-boundary-v1.js +83 -144
- package/dist/rfc64/legacy-swm-boundary-v1.js.map +1 -1
- package/dist/rfc64/private-catalog-subscription-readiness-v1.d.ts +33 -0
- package/dist/rfc64/private-catalog-subscription-readiness-v1.d.ts.map +1 -0
- package/dist/rfc64/private-catalog-subscription-readiness-v1.js +97 -0
- package/dist/rfc64/private-catalog-subscription-readiness-v1.js.map +1 -0
- package/dist/rfc64/private-read-roster-v1.d.ts +14 -0
- package/dist/rfc64/private-read-roster-v1.d.ts.map +1 -0
- package/dist/rfc64/private-read-roster-v1.js +57 -0
- package/dist/rfc64/private-read-roster-v1.js.map +1 -0
- package/dist/rfc64/registered-private-empty-vm-proof-v1.d.ts +20 -0
- package/dist/rfc64/registered-private-empty-vm-proof-v1.d.ts.map +1 -0
- package/dist/rfc64/registered-private-empty-vm-proof-v1.js +26 -0
- package/dist/rfc64/registered-private-empty-vm-proof-v1.js.map +1 -0
- package/dist/rfc64/swm-inventory-shadow-runtime-v1.d.ts +3 -1
- package/dist/rfc64/swm-inventory-shadow-runtime-v1.d.ts.map +1 -1
- package/dist/rfc64/swm-inventory-shadow-runtime-v1.js +34 -0
- package/dist/rfc64/swm-inventory-shadow-runtime-v1.js.map +1 -1
- package/dist/sync/auth/pipe-request-tail.d.ts +5 -1
- package/dist/sync/auth/pipe-request-tail.d.ts.map +1 -1
- package/dist/sync/auth/pipe-request-tail.js +13 -3
- package/dist/sync/auth/pipe-request-tail.js.map +1 -1
- package/dist/sync/auth/request-build.d.ts +3 -0
- package/dist/sync/auth/request-build.d.ts.map +1 -1
- package/dist/sync/auth/request-build.js +11 -2
- package/dist/sync/auth/request-build.js.map +1 -1
- package/dist/sync/backpressure.d.ts +27 -16
- package/dist/sync/backpressure.d.ts.map +1 -1
- package/dist/sync/backpressure.js +66 -40
- package/dist/sync/backpressure.js.map +1 -1
- package/dist/sync/catchup-policy.d.ts +2 -0
- package/dist/sync/catchup-policy.d.ts.map +1 -1
- package/dist/sync/catchup-policy.js +2 -0
- package/dist/sync/catchup-policy.js.map +1 -1
- package/dist/sync/exact-assets.d.ts +7 -5
- package/dist/sync/exact-assets.d.ts.map +1 -1
- package/dist/sync/exact-assets.js +13 -8
- package/dist/sync/exact-assets.js.map +1 -1
- package/dist/sync/exact-batch-observation.d.ts +3 -0
- package/dist/sync/exact-batch-observation.d.ts.map +1 -0
- package/dist/sync/exact-batch-observation.js +13 -0
- package/dist/sync/exact-batch-observation.js.map +1 -0
- package/dist/sync/exact-batch-stream-capability.d.ts +24 -0
- package/dist/sync/exact-batch-stream-capability.d.ts.map +1 -0
- package/dist/sync/exact-batch-stream-capability.js +88 -0
- package/dist/sync/exact-batch-stream-capability.js.map +1 -0
- package/dist/sync/exact-batch-stream-contract.d.ts +78 -0
- package/dist/sync/exact-batch-stream-contract.d.ts.map +1 -0
- package/dist/sync/exact-batch-stream-contract.js +206 -0
- package/dist/sync/exact-batch-stream-contract.js.map +1 -0
- package/dist/sync/peer-connection.d.ts.map +1 -1
- package/dist/sync/peer-connection.js +11 -2
- package/dist/sync/peer-connection.js.map +1 -1
- package/dist/sync/priority-admission-queue.d.ts +17 -0
- package/dist/sync/priority-admission-queue.d.ts.map +1 -1
- package/dist/sync/priority-admission-queue.js +58 -37
- package/dist/sync/priority-admission-queue.js.map +1 -1
- package/dist/sync/requester/admission-boundary.d.ts +12 -0
- package/dist/sync/requester/admission-boundary.d.ts.map +1 -0
- package/dist/sync/requester/admission-boundary.js +22 -0
- package/dist/sync/requester/admission-boundary.js.map +1 -0
- package/dist/sync/requester/durable-sync.d.ts +2 -1
- package/dist/sync/requester/durable-sync.d.ts.map +1 -1
- package/dist/sync/requester/durable-sync.js +3 -188
- package/dist/sync/requester/durable-sync.js.map +1 -1
- package/dist/sync/requester/exact-batch-stream-driver.d.ts +83 -0
- package/dist/sync/requester/exact-batch-stream-driver.d.ts.map +1 -0
- package/dist/sync/requester/exact-batch-stream-driver.js +299 -0
- package/dist/sync/requester/exact-batch-stream-driver.js.map +1 -0
- package/dist/sync/requester/exact-batch-stream.d.ts +72 -0
- package/dist/sync/requester/exact-batch-stream.d.ts.map +1 -0
- package/dist/sync/requester/exact-batch-stream.js +267 -0
- package/dist/sync/requester/exact-batch-stream.js.map +1 -0
- package/dist/sync/requester/exact-recovery-transport.d.ts +19 -0
- package/dist/sync/requester/exact-recovery-transport.d.ts.map +1 -0
- package/dist/sync/requester/exact-recovery-transport.js +3 -0
- package/dist/sync/requester/exact-recovery-transport.js.map +1 -0
- package/dist/sync/requester/finalized-swm-twin-reconciliation.js +12 -3
- package/dist/sync/requester/finalized-swm-twin-reconciliation.js.map +1 -1
- package/dist/sync/requester/graph-scoped-materialization.d.ts.map +1 -1
- package/dist/sync/requester/graph-scoped-materialization.js +58 -24
- package/dist/sync/requester/graph-scoped-materialization.js.map +1 -1
- package/dist/sync/requester/page-fetch.d.ts +7 -0
- package/dist/sync/requester/page-fetch.d.ts.map +1 -1
- package/dist/sync/requester/page-fetch.js +103 -49
- package/dist/sync/requester/page-fetch.js.map +1 -1
- package/dist/sync/requester/shared-memory-sync.d.ts +10 -5
- package/dist/sync/requester/shared-memory-sync.d.ts.map +1 -1
- package/dist/sync/requester/shared-memory-sync.js +149 -150
- package/dist/sync/requester/shared-memory-sync.js.map +1 -1
- package/dist/sync/requester/swm-materialization-memo.d.ts +12 -0
- package/dist/sync/requester/swm-materialization-memo.d.ts.map +1 -0
- package/dist/sync/requester/swm-materialization-memo.js +54 -0
- package/dist/sync/requester/swm-materialization-memo.js.map +1 -0
- package/dist/sync/requester/swm-recovery.d.ts +3 -29
- package/dist/sync/requester/swm-recovery.d.ts.map +1 -1
- package/dist/sync/requester/swm-recovery.js +11 -10
- package/dist/sync/requester/swm-recovery.js.map +1 -1
- package/dist/sync/requester/swm-snapshot-materializer.d.ts.map +1 -1
- package/dist/sync/requester/swm-snapshot-materializer.js +18 -0
- package/dist/sync/requester/swm-snapshot-materializer.js.map +1 -1
- package/dist/sync/requester/swm-target-executor.d.ts +6 -0
- package/dist/sync/requester/swm-target-executor.d.ts.map +1 -1
- package/dist/sync/requester/swm-target-executor.js +24 -3
- package/dist/sync/requester/swm-target-executor.js.map +1 -1
- package/dist/sync/requester/verified-asset-preparation.d.ts +9 -0
- package/dist/sync/requester/verified-asset-preparation.d.ts.map +1 -0
- package/dist/sync/requester/verified-asset-preparation.js +190 -0
- package/dist/sync/requester/verified-asset-preparation.js.map +1 -0
- package/dist/sync/responder/asset-metadata.d.ts +18 -0
- package/dist/sync/responder/asset-metadata.d.ts.map +1 -0
- package/dist/sync/responder/asset-metadata.js +27 -0
- package/dist/sync/responder/asset-metadata.js.map +1 -0
- package/dist/sync/responder/durable-data-request-policy.d.ts +2 -16
- package/dist/sync/responder/durable-data-request-policy.d.ts.map +1 -1
- package/dist/sync/responder/durable-data-request-policy.js +1 -31
- package/dist/sync/responder/durable-data-request-policy.js.map +1 -1
- package/dist/sync/responder/exact-asset-export-cache.d.ts +89 -0
- package/dist/sync/responder/exact-asset-export-cache.d.ts.map +1 -0
- package/dist/sync/responder/exact-asset-export-cache.js +550 -0
- package/dist/sync/responder/exact-asset-export-cache.js.map +1 -0
- package/dist/sync/responder/exact-batch-stream.d.ts +47 -0
- package/dist/sync/responder/exact-batch-stream.d.ts.map +1 -0
- package/dist/sync/responder/exact-batch-stream.js +151 -0
- package/dist/sync/responder/exact-batch-stream.js.map +1 -0
- package/dist/sync/responder/exact-data-session.d.ts +67 -0
- package/dist/sync/responder/exact-data-session.d.ts.map +1 -0
- package/dist/sync/responder/exact-data-session.js +130 -0
- package/dist/sync/responder/exact-data-session.js.map +1 -0
- package/dist/sync/responder/exact-graph-cursor-filter.d.ts +9 -0
- package/dist/sync/responder/exact-graph-cursor-filter.d.ts.map +1 -0
- package/dist/sync/responder/exact-graph-cursor-filter.js +101 -0
- package/dist/sync/responder/exact-graph-cursor-filter.js.map +1 -0
- package/dist/sync/responder/exact-graph-reader.d.ts +95 -0
- package/dist/sync/responder/exact-graph-reader.d.ts.map +1 -0
- package/dist/sync/responder/exact-graph-reader.js +623 -0
- package/dist/sync/responder/exact-graph-reader.js.map +1 -0
- package/dist/sync/responder/exact-page-reader.d.ts +37 -0
- package/dist/sync/responder/exact-page-reader.d.ts.map +1 -0
- package/dist/sync/responder/exact-page-reader.js +130 -0
- package/dist/sync/responder/exact-page-reader.js.map +1 -0
- package/dist/sync/responder/graph-plan.d.ts +19 -85
- package/dist/sync/responder/graph-plan.d.ts.map +1 -1
- package/dist/sync/responder/graph-plan.js +51 -988
- package/dist/sync/responder/graph-plan.js.map +1 -1
- package/dist/sync/responder/page-framing-policy.d.ts +31 -0
- package/dist/sync/responder/page-framing-policy.d.ts.map +1 -0
- package/dist/sync/responder/page-framing-policy.js +41 -0
- package/dist/sync/responder/page-framing-policy.js.map +1 -0
- package/dist/sync/responder/responder-row-page.d.ts +85 -0
- package/dist/sync/responder/responder-row-page.d.ts.map +1 -0
- package/dist/sync/responder/responder-row-page.js +181 -0
- package/dist/sync/responder/responder-row-page.js.map +1 -0
- package/dist/sync/responder/row-serialization.d.ts +34 -0
- package/dist/sync/responder/row-serialization.d.ts.map +1 -0
- package/dist/sync/responder/row-serialization.js +69 -0
- package/dist/sync/responder/row-serialization.js.map +1 -0
- package/dist/sync/responder/session-plan-memo.d.ts +20 -0
- package/dist/sync/responder/session-plan-memo.d.ts.map +1 -0
- package/dist/sync/responder/session-plan-memo.js +135 -0
- package/dist/sync/responder/session-plan-memo.js.map +1 -0
- package/dist/sync/responder/sync-handler.d.ts +18 -1
- package/dist/sync/responder/sync-handler.d.ts.map +1 -1
- package/dist/sync/responder/sync-handler.js +101 -57
- package/dist/sync/responder/sync-handler.js.map +1 -1
- package/dist/sync/shared-memory-completion.d.ts +30 -0
- package/dist/sync/shared-memory-completion.d.ts.map +1 -1
- package/dist/sync/shared-memory-completion.js +13 -0
- package/dist/sync/shared-memory-completion.js.map +1 -1
- package/dist/sync/wire-compression.d.ts +47 -0
- package/dist/sync/wire-compression.d.ts.map +1 -0
- package/dist/sync/wire-compression.js +111 -0
- package/dist/sync/wire-compression.js.map +1 -0
- package/dist/unscoped-query-consistency.d.ts +12 -0
- package/dist/unscoped-query-consistency.d.ts.map +1 -1
- package/dist/unscoped-query-consistency.js +17 -1
- package/dist/unscoped-query-consistency.js.map +1 -1
- package/dist/vm-promotion-audit.d.ts +2 -2
- package/dist/vm-promotion-audit.d.ts.map +1 -1
- package/dist/vm-promotion-audit.js +26 -9
- package/dist/vm-promotion-audit.js.map +1 -1
- package/dist/vm-reconcile-dispatcher.d.ts +42 -1
- package/dist/vm-reconcile-dispatcher.d.ts.map +1 -1
- package/dist/vm-reconcile-dispatcher.js +103 -4
- package/dist/vm-reconcile-dispatcher.js.map +1 -1
- package/dist/vm-reconcile-peer-topology.d.ts +7 -9
- package/dist/vm-reconcile-peer-topology.d.ts.map +1 -1
- package/dist/vm-reconcile-peer-topology.js +1 -37
- package/dist/vm-reconcile-peer-topology.js.map +1 -1
- package/dist/vm-recovery-candidate-order.d.ts +16 -0
- package/dist/vm-recovery-candidate-order.d.ts.map +1 -0
- package/dist/vm-recovery-candidate-order.js +24 -0
- package/dist/vm-recovery-candidate-order.js.map +1 -0
- package/dist/vm-recovery-core-transport-preference.d.ts +69 -0
- package/dist/vm-recovery-core-transport-preference.d.ts.map +1 -0
- package/dist/vm-recovery-core-transport-preference.js +131 -0
- package/dist/vm-recovery-core-transport-preference.js.map +1 -0
- package/dist/vm-recovery-experiment-policy.d.ts +24 -0
- package/dist/vm-recovery-experiment-policy.d.ts.map +1 -0
- package/dist/vm-recovery-experiment-policy.js +18 -0
- package/dist/vm-recovery-experiment-policy.js.map +1 -0
- package/dist/vm-recovery-footprint.d.ts +54 -0
- package/dist/vm-recovery-footprint.d.ts.map +1 -1
- package/dist/vm-recovery-footprint.js +88 -24
- package/dist/vm-recovery-footprint.js.map +1 -1
- package/dist/vm-recovery-microbatch.d.ts +2 -0
- package/dist/vm-recovery-microbatch.d.ts.map +1 -1
- package/dist/vm-recovery-microbatch.js +7 -3
- package/dist/vm-recovery-microbatch.js.map +1 -1
- package/dist/vm-recovery-pass-authority.d.ts +84 -0
- package/dist/vm-recovery-pass-authority.d.ts.map +1 -0
- package/dist/vm-recovery-pass-authority.js +126 -0
- package/dist/vm-recovery-pass-authority.js.map +1 -0
- package/dist/vm-recovery-phase-timing.d.ts +75 -0
- package/dist/vm-recovery-phase-timing.d.ts.map +1 -0
- package/dist/vm-recovery-phase-timing.js +236 -0
- package/dist/vm-recovery-phase-timing.js.map +1 -0
- package/dist/vm-recovery-preparation.d.ts +143 -0
- package/dist/vm-recovery-preparation.d.ts.map +1 -0
- package/dist/vm-recovery-preparation.js +433 -0
- package/dist/vm-recovery-preparation.js.map +1 -0
- package/dist/vm-recovery-provider-policy.d.ts +13 -1
- package/dist/vm-recovery-provider-policy.d.ts.map +1 -1
- package/dist/vm-recovery-provider-policy.js +37 -5
- package/dist/vm-recovery-provider-policy.js.map +1 -1
- package/dist/vm-recovery-stream-profile.d.ts +15 -0
- package/dist/vm-recovery-stream-profile.d.ts.map +1 -0
- package/dist/vm-recovery-stream-profile.js +53 -0
- package/dist/vm-recovery-stream-profile.js.map +1 -0
- package/dist/vm-recovery-stream-setback-policy.d.ts +54 -0
- package/dist/vm-recovery-stream-setback-policy.d.ts.map +1 -0
- package/dist/vm-recovery-stream-setback-policy.js +85 -0
- package/dist/vm-recovery-stream-setback-policy.js.map +1 -0
- package/dist/vm-recovery-timing-observer.d.ts +27 -0
- package/dist/vm-recovery-timing-observer.d.ts.map +1 -0
- package/dist/vm-recovery-timing-observer.js +49 -0
- package/dist/vm-recovery-timing-observer.js.map +1 -0
- package/dist/vm-recovery-transport-budget-policy.d.ts +32 -0
- package/dist/vm-recovery-transport-budget-policy.d.ts.map +1 -0
- package/dist/vm-recovery-transport-budget-policy.js +55 -0
- package/dist/vm-recovery-transport-budget-policy.js.map +1 -0
- package/dist/vm-recovery-transport-plan.d.ts +83 -0
- package/dist/vm-recovery-transport-plan.d.ts.map +1 -0
- package/dist/vm-recovery-transport-plan.js +117 -0
- package/dist/vm-recovery-transport-plan.js.map +1 -0
- package/package.json +17 -9
- package/dist/finalization-annotations.d.ts +0 -5
- package/dist/finalization-annotations.d.ts.map +0 -1
- package/dist/finalization-annotations.js +0 -6
- package/dist/finalization-annotations.js.map +0 -1
- package/dist/swm-ka-bound.d.ts +0 -21
- package/dist/swm-ka-bound.d.ts.map +0 -1
- package/dist/swm-ka-bound.js +0 -36
- package/dist/swm-ka-bound.js.map +0 -1
|
@@ -6,11 +6,15 @@
|
|
|
6
6
|
* the catchup/enable/stats entrypoints. Bodies are a 1:1 move; methods take
|
|
7
7
|
* `this: DKGAgent` so cross-calls resolve against the composed class.
|
|
8
8
|
*/
|
|
9
|
+
import { orderVmRecoveryCandidates } from './vm-recovery-candidate-order.js';
|
|
9
10
|
import { createHash } from 'node:crypto';
|
|
11
|
+
import { createSwmHostModeHandler } from './internal/gossip/host-mode-handler.js';
|
|
10
12
|
import { Buffer } from 'node:buffer';
|
|
13
|
+
import { VmRecoveryCoreTransportPreferencePolicy } from './vm-recovery-core-transport-preference.js';
|
|
14
|
+
import { VmRecoveryPassAuthority } from './vm-recovery-pass-authority.js';
|
|
11
15
|
import { performance } from 'node:perf_hooks';
|
|
12
|
-
import { PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_SWM_HOST_CATCHUP, contextGraphWorkspaceTopic,
|
|
13
|
-
import { GraphManager, isStoreSchedulerBusyError, withDefaultStoreWorkPriority,
|
|
16
|
+
import { PROTOCOL_GET_CIPHERTEXT_CHUNK, PROTOCOL_SWM_HOST_CATCHUP, contextGraphWorkspaceTopic, contextGraphDataUri, contextGraphMetaUri, contextGraphLayerUri, MemoryLayer, GOSSIP_TYPE_WORKSPACE_PUBLISH, decodeGossipEnvelope, decodeEncryptedWorkspacePayload, ENCRYPTED_WORKSPACE_ENVELOPE_TYPE, decodeSwmSenderKeyMessage, SWM_SENDER_KEY_MESSAGE_TYPE, SYSTEM_CONTEXT_GRAPHS, createOperationContext, sparqlString, isSafeIri, assertSafeIri, TRUST_LEVEL_PREDICATE, LEGACY_TRUST_LEVEL_PREDICATE, GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED, ciphertextChunkStoreGraph, ciphertextChunkStoreSubject, CIPHERTEXT_CHUNK_PREDICATE, SUBSCRIPTION_SOURCES, } from '@origintrail-official/dkg-core';
|
|
17
|
+
import { GraphManager, isStoreSchedulerBusyError, withDefaultStoreWorkPriority, tryUpdateWithTouchedGraphs } from '@origintrail-official/dkg-storage';
|
|
14
18
|
import { readMaterializedVersion, shouldApplyMaterialization, withMaterializationLock, isKnowledgeAssetWorkspaceHeadCorruptError, readConfirmedGraphKnowledgeAssetMetadataEnvelope, resolveKnowledgeAssetWorkspaceHead, } from '@origintrail-official/dkg-publisher';
|
|
15
19
|
import { ethers } from 'ethers';
|
|
16
20
|
import { isCoreHostedPublicCgRecorded, resolveCoreHostedPublicCgLocalId, } from './core-hosted-public-cg-record-decision.js';
|
|
@@ -24,11 +28,16 @@ import { decodeCiphertextChunkCatchupRequest, encodeCiphertextChunkCatchupReques
|
|
|
24
28
|
import { orderCatchupPeers } from './p2p/peer-selection.js';
|
|
25
29
|
import { reconcileContextGraph, VmReconcileSchedulingRuntime, } from './chain-reconciler.js';
|
|
26
30
|
import { ContextGraphOnChainIdUnresolvedError, VmReconcileUnavailableError, VmReconcileQueueClosedError, } from './vm-reconcile-service.js';
|
|
31
|
+
import { isUnansweredVmReconcileReadAuthority, VmReconcileReadAuthorityUnansweredError, } from './internal/vm-reconcile-read-authority.js';
|
|
27
32
|
import { createCursorState } from './reconcile-cursor.js';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
33
|
+
import { resolveVmRecoveryExperimentPolicy } from './vm-recovery-experiment-policy.js';
|
|
34
|
+
import { VmRecoveryTimingObserver } from './vm-recovery-timing-observer.js';
|
|
35
|
+
import { noteVmReconcilePassEnd, observeVmRecoveryTiming, vmReconcilePassGapMs, } from './vm-recovery-phase-timing.js';
|
|
36
|
+
import { existingVmRecoveryPreparation, vmRecoveryPreparationFor, } from './vm-recovery-preparation.js';
|
|
37
|
+
import { planVmRecoveryTransport, VmRecoveryTransportPreparation, VM_EXACT_MICROBATCH_LIMITS } from './vm-recovery-transport-plan.js';
|
|
38
|
+
import { EXACT_BATCH_STREAM_PROTOCOL } from './sync/exact-batch-stream-contract.js';
|
|
39
|
+
import { exactBatchStreamUnsupported } from './sync/exact-batch-stream-capability.js';
|
|
30
40
|
import { VmRecoveryProviderPolicy, } from './vm-recovery-provider-policy.js';
|
|
31
|
-
import { encodeExactAssetUals, MAX_EXACT_SYNC_ASSETS, } from './sync/exact-assets.js';
|
|
32
41
|
import { MAX_CONTEXT_GRAPH_ASSET_FETCH_PEERS, ExactAssetFetchLifecycleClosedError, ExactAssetVersionBehindError, exactAssetFetchPeerWindow, runExactAssetFetch, } from './sync/exact-asset-fetch.js';
|
|
33
42
|
import { isBoundedOperationTimeoutError, runBoundedOperation } from './bounded-operation.js';
|
|
34
43
|
/** Graph-scoped KA metadata marker; its presence names the metadata graph. */
|
|
@@ -76,12 +85,13 @@ function advanceRsHealCursor(cursorMap, cursorKey, bindings, batchSize, maxEntri
|
|
|
76
85
|
cursorMap.delete(oldest);
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
|
-
import {
|
|
80
|
-
import { TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
|
|
88
|
+
import { SYNC_MIN_GRAPH_BUDGET_MS, TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
|
|
81
89
|
import { chainAuthorityReadBudgetsOf } from './chain-authority-read-budgets.js';
|
|
82
90
|
import { ContextGraphNotFoundError, } from './dkg-agent-types.js';
|
|
83
91
|
import { DKGAgentBase } from './dkg-agent-base.js';
|
|
84
|
-
import { resolveVmReconcilerEnabled } from './sync/backpressure.js';
|
|
92
|
+
import { resolveExactBatchStreamEnabled, resolveVmReconcilerEnabled } from './sync/backpressure.js';
|
|
93
|
+
import { mapWithConcurrencyDrained } from './map-with-concurrency.js';
|
|
94
|
+
import { VM_RECOVERY_SYNC_PRIORITY } from './sync/catchup-policy.js';
|
|
85
95
|
import { finalizedContextGraphSnapshotMismatchV1 } from './internal/context-graph-authority/finalized-context-graph-binding.js';
|
|
86
96
|
import { CONTEXT_GRAPH_AUTHORITY_RPC_SITES as CG_AUTH_RPC_SITES, withOwnedRpcRequestContext, withRpcUsageSite, } from '@origintrail-official/dkg-chain';
|
|
87
97
|
const DEFAULT_HOST_MODE_RECONCILE_BATCH_SIZE = 32;
|
|
@@ -108,26 +118,6 @@ function vmReconcileDeploymentMatches(value, expected) {
|
|
|
108
118
|
* amplify into a per-message `eth_call` (Branimir review #1239 follow-on).
|
|
109
119
|
*/
|
|
110
120
|
const HOST_MODE_PUBLISH_POLICY_MAX_CACHE_AGE_MS = 5_000;
|
|
111
|
-
// Exact VM data responses are page-only and capped at 64 rows per page. Keep
|
|
112
|
-
// one recovery microbatch near 64 non-empty pages so a single large graph does
|
|
113
|
-
// not monopolize the global sync admission. This is a soft scheduling/fairness
|
|
114
|
-
// cap, not a wire or correctness limit: an individually larger KA is still
|
|
115
|
-
// admitted alone and remains bounded by the exact executor's hard guards.
|
|
116
|
-
const VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES = 4096n;
|
|
117
|
-
const VM_EXACT_MICROBATCH_LIMITS = Object.freeze({
|
|
118
|
-
// Executor capability: the current exact-sync envelope accepts at most ten.
|
|
119
|
-
maxAssets: MAX_EXACT_SYNC_ASSETS,
|
|
120
|
-
// Soft recovery-window targets. A single public KA may exceed either; it is
|
|
121
|
-
// still admitted alone while the exact executor's own hard byte/quad guards
|
|
122
|
-
// remain authoritative.
|
|
123
|
-
targetBytes: 24n * 1024n * 1024n,
|
|
124
|
-
targetLeaves: VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES,
|
|
125
|
-
fixedBytesPerAsset: 64n * 1024n,
|
|
126
|
-
bytesPerLeafOverhead: 128n,
|
|
127
|
-
byteSizeMultiplierBps: 11500n,
|
|
128
|
-
// Hard exact-selector cap, evaluated with the executor's real encoder.
|
|
129
|
-
maxSelectorBytes: 16 * 1024,
|
|
130
|
-
});
|
|
131
121
|
/**
|
|
132
122
|
* Recovery-target root of an ordinal whose KA the node holds nowhere locally:
|
|
133
123
|
* the sweep queues it without reading the chain root. Exact recovery fetches
|
|
@@ -322,6 +312,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
322
312
|
* reserved, or when the node is already subscribed in member mode.
|
|
323
313
|
*/
|
|
324
314
|
async reconcileSwmHostModeSubscription(contextGraphId, source = SUBSCRIPTION_SOURCES.RECONCILER) {
|
|
315
|
+
const session = this.gossipSession;
|
|
316
|
+
const live = session.live();
|
|
317
|
+
if (live === null)
|
|
318
|
+
return;
|
|
325
319
|
if (!this.swmHostModeStore)
|
|
326
320
|
return;
|
|
327
321
|
if (Object.values(SYSTEM_CONTEXT_GRAPHS).includes(contextGraphId))
|
|
@@ -357,7 +351,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
357
351
|
await this.isCuratedForHostMode(contextGraphId)) {
|
|
358
352
|
// A manually hosted CG can become curated later. Upgrade the cached
|
|
359
353
|
// classification so the existing handler starts stripping immediately.
|
|
360
|
-
this.
|
|
354
|
+
if (!session.active || this.gossipSession !== session)
|
|
355
|
+
return;
|
|
356
|
+
session.swmHostModeCurated.set(hostKey, true);
|
|
361
357
|
}
|
|
362
358
|
await this.maybeMarkRegisteredForHostMode(contextGraphId);
|
|
363
359
|
return;
|
|
@@ -392,7 +388,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
392
388
|
// critically, the host-only-core case where there's no local
|
|
393
389
|
// `_meta` and `isPrivateContextGraph` alone returns false.
|
|
394
390
|
const curated = await this.isCuratedForHostMode(contextGraphId);
|
|
395
|
-
if (!curated)
|
|
391
|
+
if (!curated || !session.active || this.gossipSession !== session)
|
|
396
392
|
return;
|
|
397
393
|
// OT-RFC-49 WS-A — the private-ciphertext strip. With `stripCiphertext`
|
|
398
394
|
// ON (default), a core declines ALL host-mode custody for a curated CG:
|
|
@@ -512,15 +508,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
512
508
|
* so the host-mode lifecycle is in one place.
|
|
513
509
|
*/
|
|
514
510
|
wireSwmHostModeHandler(contextGraphId, source = SUBSCRIPTION_SOURCES.RECONCILER, curated = true) {
|
|
511
|
+
const session = this.gossipSession;
|
|
512
|
+
const live = session.live();
|
|
513
|
+
if (live === null)
|
|
514
|
+
return;
|
|
515
515
|
if (!this.rfc64LegacySwmGossipAllowedForContextGraph(contextGraphId)) {
|
|
516
516
|
const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
|
|
517
|
-
const hadRuntimeHostState =
|
|
518
|
-
||
|
|
519
|
-
||
|
|
517
|
+
const hadRuntimeHostState = session.swmHostModeHandlers.has(hostKey)
|
|
518
|
+
|| session.swmHostModeSubscribed.has(hostKey)
|
|
519
|
+
|| session.swmHostModeCurated.has(hostKey);
|
|
520
520
|
this.unwireSwmHostModeHandler(contextGraphId);
|
|
521
|
-
const deletedStaleHandler =
|
|
522
|
-
const deletedStaleSubscription =
|
|
523
|
-
const deletedStaleClassification =
|
|
521
|
+
const deletedStaleHandler = session.swmHostModeHandlers.delete(hostKey);
|
|
522
|
+
const deletedStaleSubscription = session.swmHostModeSubscribed.delete(hostKey);
|
|
523
|
+
const deletedStaleClassification = session.swmHostModeCurated.delete(hostKey);
|
|
524
524
|
if (deletedStaleHandler || deletedStaleSubscription || deletedStaleClassification) {
|
|
525
525
|
// Heal partially restored/stale bookkeeping even when its handler
|
|
526
526
|
// reference is absent, which makes unwireSwmHostModeHandler a no-op.
|
|
@@ -548,7 +548,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
548
548
|
// genuine no-op (instead of silently wiring a second handler on
|
|
549
549
|
// the same topic).
|
|
550
550
|
const wireCgId = this.canonicalSwmHostModeKey(contextGraphId);
|
|
551
|
-
if (
|
|
551
|
+
if (session.swmHostModeHandlers.has(wireCgId)) {
|
|
552
552
|
// Idempotent re-entry — preserve the original source. The first
|
|
553
553
|
// discovery path to wire the handler wins the provenance label;
|
|
554
554
|
// a later path covering the same CG is "also true" but the
|
|
@@ -556,44 +556,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
556
556
|
return;
|
|
557
557
|
}
|
|
558
558
|
const swmTopic = contextGraphWorkspaceTopic(wireCgId);
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const handler = (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (this.swmHostModeStripCiphertext() &&
|
|
566
|
-
this.swmHostModeCurated.get(wireCgId) !== false) {
|
|
567
|
-
this.log.debug(createOperationContext('share'), `Dropping host-mode envelope on cg=${contextGraphId} from=${from}: ` +
|
|
568
|
-
`private-ciphertext strip is ON for a curated CG (OT-RFC-49 WS-A)`);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
// OT-RFC-38 LU-11: peek envelope type and dispatch. Chunked
|
|
572
|
-
// envelopes (`type='share-write-chunked'`) take the V2 chunk
|
|
573
|
-
// persistence path; everything else flows through the legacy
|
|
574
|
-
// host-mode store unchanged. Failed decode falls through to
|
|
575
|
-
// `ingestSwmHostModeEnvelope` which is also defensive — the
|
|
576
|
-
// dispatch here is best-effort, not a security boundary.
|
|
577
|
-
let envelopeType;
|
|
578
|
-
try {
|
|
579
|
-
const peek = decodeGossipEnvelope(data);
|
|
580
|
-
envelopeType = peek?.type;
|
|
581
|
-
}
|
|
582
|
-
catch { /* drop into legacy path */ }
|
|
583
|
-
if (envelopeType === GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED) {
|
|
584
|
-
this.ingestSwmCiphertextChunkEnvelope(contextGraphId, data, from).catch((err) => {
|
|
585
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
586
|
-
this.log.warn(createOperationContext('system'), `LU-11: chunked SWM ingest failed for "${contextGraphId}": ${msg}`);
|
|
587
|
-
});
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
this.ingestSwmHostModeEnvelope(contextGraphId, data, from).catch((err) => {
|
|
591
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
592
|
-
this.log.warn(createOperationContext('system'), `Host-mode SWM ingest failed for "${contextGraphId}": ${msg}`);
|
|
593
|
-
});
|
|
594
|
-
};
|
|
595
|
-
this.swmHostModeHandlers.set(wireCgId, handler);
|
|
596
|
-
this.gossip.onMessage(swmTopic, handler);
|
|
559
|
+
session.swmHostModeSubscribed.set(wireCgId, source);
|
|
560
|
+
session.swmHostModeCurated.set(wireCgId, curated);
|
|
561
|
+
live.manager.subscribe(swmTopic);
|
|
562
|
+
const handler = createSwmHostModeHandler(this, this.log, session, contextGraphId, wireCgId);
|
|
563
|
+
session.swmHostModeHandlers.set(wireCgId, handler);
|
|
564
|
+
live.manager.onMessage(swmTopic, handler);
|
|
597
565
|
// B3: persist the host-mode designation so a restart re-engages
|
|
598
566
|
// this handler before the chain-event poller catches up.
|
|
599
567
|
// Codex PR #620 R2: chain wire/unwire writes through a per-CG
|
|
@@ -1632,7 +1600,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
1632
1600
|
}
|
|
1633
1601
|
if (!authOk) {
|
|
1634
1602
|
try {
|
|
1635
|
-
const allowedPeers = await this.
|
|
1603
|
+
const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
|
|
1636
1604
|
if (allowedPeers !== null) {
|
|
1637
1605
|
anyAuthorityFound = true;
|
|
1638
1606
|
if (allowedPeers.includes(fromPeerId))
|
|
@@ -3246,12 +3214,18 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3246
3214
|
this.log.debug(createOperationContext('system'), `VM reconcile for "${localCgId}" stopped: superseded by cleartext adoption of "${supersedingId}"`);
|
|
3247
3215
|
return;
|
|
3248
3216
|
}
|
|
3217
|
+
// Reported where the graph was parked (executeVmReconcileForCg): it
|
|
3218
|
+
// is asked again shortly and does not wait for the sweep.
|
|
3219
|
+
if (err instanceof VmReconcileReadAuthorityUnansweredError)
|
|
3220
|
+
return;
|
|
3249
3221
|
this.log.warn(createOperationContext('system'), `VM reconcile for "${localCgId}" failed; retrying on the periodic sweep: ${err instanceof Error ? err.message : String(err)}`);
|
|
3250
3222
|
}, {
|
|
3251
3223
|
concurrency: DKGAgentBase.VM_RECONCILE_CONCURRENCY,
|
|
3252
3224
|
maxPending: DKGAgentBase.VM_RECONCILE_QUEUE_MAX_PENDING,
|
|
3253
3225
|
maxForegroundBurst: DKGAgentBase.VM_RECONCILE_MAX_FOREGROUND_BURST,
|
|
3254
|
-
|
|
3226
|
+
discoveryBatchSize: DKGAgentBase.VM_RECONCILE_UNBOUND_BATCH_SIZE,
|
|
3227
|
+
periodicBoundBatchSize: DKGAgentBase.VM_RECONCILE_PERIODIC_BOUND_BATCH_SIZE,
|
|
3228
|
+
});
|
|
3255
3229
|
this.vmReconcileScheduling = scheduling;
|
|
3256
3230
|
}
|
|
3257
3231
|
return scheduling;
|
|
@@ -3276,7 +3250,37 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3276
3250
|
? work()
|
|
3277
3251
|
: withOwnedRpcRequestContext({ requestClass: 'background', ...(lifecycleSignal ? { signal: lifecycleSignal } : {}) }, () => withDefaultStoreWorkPriority('background', work));
|
|
3278
3252
|
const physicalRun = runInLane(async () => {
|
|
3279
|
-
const
|
|
3253
|
+
const passStartedAt = performance.now();
|
|
3254
|
+
const passGapMs = vmReconcilePassGapMs(this, localCgId, passStartedAt);
|
|
3255
|
+
const target = await this.resolveVmReconcileTarget(localCgId, isLifecycleCurrent, lifecycleSignal).catch((err) => {
|
|
3256
|
+
if (!(err instanceof VmReconcileReadAuthorityUnansweredError))
|
|
3257
|
+
throw err;
|
|
3258
|
+
// The chain read behind the read-authority check got no answer, which
|
|
3259
|
+
// is the node's RPC budget or its endpoint and not the graph. An
|
|
3260
|
+
// automatic pass leaves the graph waiting to be asked again; without
|
|
3261
|
+
// that its next attempt is the periodic sweep, up to a full interval
|
|
3262
|
+
// after the read would have been answered. It is asked again only
|
|
3263
|
+
// while its fetch could start, the readiness every waiter uses. An
|
|
3264
|
+
// operator's request, and a graph the wait cannot take, end as they
|
|
3265
|
+
// always did.
|
|
3266
|
+
const waiting = source === 'manual'
|
|
3267
|
+
? undefined
|
|
3268
|
+
: this.vmReconcileScheduling?.deferForReadAuthority(localCgId, {
|
|
3269
|
+
signal: lifecycleSignal,
|
|
3270
|
+
isCurrent: isLifecycleCurrent,
|
|
3271
|
+
canAdmit: () => this.vmRecoverySyncAdmissionAvailable(localCgId),
|
|
3272
|
+
});
|
|
3273
|
+
if (waiting === undefined)
|
|
3274
|
+
throw new ContextGraphNotFoundError(localCgId);
|
|
3275
|
+
const message = `VM reconcile for "${localCgId}" is waiting for read authority `
|
|
3276
|
+
+ `(${err.readAuthority}): the chain read got no answer; asking again shortly`;
|
|
3277
|
+
if (waiting === 'parked')
|
|
3278
|
+
this.log.info(createOperationContext('system'), message);
|
|
3279
|
+
else
|
|
3280
|
+
this.log.debug(createOperationContext('system'), message);
|
|
3281
|
+
throw err;
|
|
3282
|
+
});
|
|
3283
|
+
const resolveTargetMs = performance.now() - passStartedAt;
|
|
3280
3284
|
const isTargetCurrent = () => isLifecycleCurrent()
|
|
3281
3285
|
&& this.isVmReconcileTargetCurrent(localCgId, target, lifecycleGeneration);
|
|
3282
3286
|
if (!isTargetCurrent())
|
|
@@ -3296,10 +3300,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3296
3300
|
};
|
|
3297
3301
|
let pendingWatermark;
|
|
3298
3302
|
let result;
|
|
3303
|
+
let engineTimings;
|
|
3299
3304
|
try {
|
|
3300
3305
|
result = await reconcileContextGraph(this.createVmReconcileDeps(localCgId, lifecycleGeneration, target, lifecycleSignal, {
|
|
3301
3306
|
identityCursor: target.cursor,
|
|
3302
3307
|
persistWatermark: (_lcg, watermark) => { pendingWatermark = watermark; },
|
|
3308
|
+
observePassTimings: (timings) => { engineTimings = timings; },
|
|
3303
3309
|
}), workingCursor, localCgId, target.onChainCgId);
|
|
3304
3310
|
}
|
|
3305
3311
|
catch (err) {
|
|
@@ -3319,16 +3325,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3319
3325
|
}
|
|
3320
3326
|
if (!isTargetCurrent())
|
|
3321
3327
|
throw new VmReconcileQueueClosedError();
|
|
3328
|
+
let flushMs = 0;
|
|
3322
3329
|
if (result.reconciled > 0 || pendingWatermark !== undefined) {
|
|
3330
|
+
const flushStartedAt = performance.now();
|
|
3323
3331
|
await this.store.flush?.({
|
|
3324
3332
|
priority: 'background',
|
|
3325
3333
|
source: 'agent.vmReconcile.materialization.flush',
|
|
3326
3334
|
});
|
|
3335
|
+
flushMs = performance.now() - flushStartedAt;
|
|
3327
3336
|
if (!isTargetCurrent())
|
|
3328
3337
|
throw new VmReconcileQueueClosedError();
|
|
3329
3338
|
}
|
|
3339
|
+
let watermarkMs = 0;
|
|
3330
3340
|
if (pendingWatermark !== undefined) {
|
|
3341
|
+
const watermarkStartedAt = performance.now();
|
|
3331
3342
|
await this.persistVmReconcileWatermark(localCgId, pendingWatermark, target);
|
|
3343
|
+
watermarkMs = performance.now() - watermarkStartedAt;
|
|
3332
3344
|
if (!isTargetCurrent())
|
|
3333
3345
|
throw new VmReconcileQueueClosedError();
|
|
3334
3346
|
}
|
|
@@ -3337,11 +3349,40 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3337
3349
|
target.cursor.scanOrdinal = workingCursor.scanOrdinal;
|
|
3338
3350
|
const response = this.toContextGraphReconcileResult(localCgId, source, target, result);
|
|
3339
3351
|
this.emitVmReconcileTelemetry(localCgId, target, result, response.status);
|
|
3352
|
+
observeVmRecoveryTiming(() => {
|
|
3353
|
+
const timings = engineTimings;
|
|
3354
|
+
this.log.info(createOperationContext('system'), `VM reconcile pass timing for "${localCgId}": source=${source} `
|
|
3355
|
+
+ `gapSincePreviousPassMs=${passGapMs === undefined ? '-' : Math.round(passGapMs)} `
|
|
3356
|
+
+ `resolveTargetMs=${Math.round(resolveTargetMs)} `
|
|
3357
|
+
+ `headReadMs=${Math.round(timings?.headReadMs ?? 0)} blockReadMs=${Math.round(timings?.blockReadMs ?? 0)} `
|
|
3358
|
+
+ `scanMs=${Math.round(timings?.scanMs ?? 0)} recoverMs=${Math.round(timings?.recoverMs ?? 0)} `
|
|
3359
|
+
+ `engineMs=${Math.round(timings?.totalMs ?? 0)} flushMs=${Math.round(flushMs)} `
|
|
3360
|
+
+ `watermarkMs=${Math.round(watermarkMs)} totalMs=${Math.round(performance.now() - passStartedAt)} `
|
|
3361
|
+
+ `processed=${result.processed} reconciled=${result.reconciled} pending=${result.pending} `
|
|
3362
|
+
+ `continue=${result.shouldContinueImmediately ? 1 : 0}`);
|
|
3363
|
+
});
|
|
3340
3364
|
// The reconciler owns the continuation policy: productive slices, stale
|
|
3341
3365
|
// bindings, and explicit provider rotations continue immediately, while
|
|
3342
3366
|
// pending-only historical inventory yields to the periodic sweep.
|
|
3343
|
-
|
|
3344
|
-
|
|
3367
|
+
const localAdmissionWait = {
|
|
3368
|
+
signal: lifecycleSignal,
|
|
3369
|
+
isCurrent: isTargetCurrent,
|
|
3370
|
+
canAdmit: () => this.vmRecoverySyncAdmissionAvailable(localCgId),
|
|
3371
|
+
};
|
|
3372
|
+
if (isTargetCurrent() && result.localAdmissionDeferred) {
|
|
3373
|
+
this.vmReconcileScheduling?.retryLocalAdmission(localCgId, localAdmissionWait);
|
|
3374
|
+
}
|
|
3375
|
+
else if (isLifecycleCurrent() && result.shouldContinueImmediately) {
|
|
3376
|
+
// A graph that just fetched from peers takes its next turn behind the
|
|
3377
|
+
// graphs the node's sync admission refused while it did. Without that
|
|
3378
|
+
// the freed capacity goes back to whichever pass asks first, which is
|
|
3379
|
+
// this one, and the others repeat their passes to be refused again.
|
|
3380
|
+
const yielded = result.recoveryAttempted === true
|
|
3381
|
+
&& !result.staleTarget
|
|
3382
|
+
&& isTargetCurrent()
|
|
3383
|
+
&& this.vmReconcileScheduling?.yieldLocalAdmissionTurn(localCgId, localAdmissionWait) === true;
|
|
3384
|
+
if (!yielded)
|
|
3385
|
+
this.vmReconcileScheduling?.triggerLive(localCgId);
|
|
3345
3386
|
}
|
|
3346
3387
|
else if (isTargetCurrent()) {
|
|
3347
3388
|
// RS heal is bounded, best-effort maintenance. Run it only after the
|
|
@@ -3362,6 +3403,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3362
3403
|
if (!isTargetCurrent())
|
|
3363
3404
|
throw new VmReconcileQueueClosedError();
|
|
3364
3405
|
}
|
|
3406
|
+
noteVmReconcilePassEnd(this, localCgId, performance.now());
|
|
3365
3407
|
return response;
|
|
3366
3408
|
});
|
|
3367
3409
|
trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, physicalRun);
|
|
@@ -3448,8 +3490,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3448
3490
|
// Central defense for periodic, live-chain, and manual reconciliation.
|
|
3449
3491
|
// Every dispatcher entry point converges here and must independently prove
|
|
3450
3492
|
// read authority. Never let a persisted subscription authorize itself.
|
|
3493
|
+
let authorityDecision;
|
|
3451
3494
|
const authorityRead = withRpcUsageSite(CG_AUTH_RPC_SITES.vmReconcile, () => this.canReadContextGraph(localCgId, {
|
|
3452
3495
|
allowSubscriptionFallback: false,
|
|
3496
|
+
onReadAuthorityDecision: (decision) => { authorityDecision = decision; },
|
|
3453
3497
|
}));
|
|
3454
3498
|
// Cancellation releases the dispatcher worker, but an underlying store/RPC
|
|
3455
3499
|
// read may ignore it. Keep that physical dependency in the shutdown drain.
|
|
@@ -3457,8 +3501,16 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3457
3501
|
const canRead = await raceVmReconcileAbort(authorityRead, signal).catch(() => false);
|
|
3458
3502
|
if (!isCurrent())
|
|
3459
3503
|
throw new VmReconcileQueueClosedError();
|
|
3460
|
-
if (!canRead)
|
|
3504
|
+
if (!canRead) {
|
|
3505
|
+
// Still a refusal for this pass. The class only records that the chain
|
|
3506
|
+
// never answered, so scheduling can ask again instead of treating the
|
|
3507
|
+
// graph as unreadable until the next sweep.
|
|
3508
|
+
const decision = authorityDecision;
|
|
3509
|
+
if (isUnansweredVmReconcileReadAuthority(decision)) {
|
|
3510
|
+
throw new VmReconcileReadAuthorityUnansweredError(localCgId, decision);
|
|
3511
|
+
}
|
|
3461
3512
|
throw new ContextGraphNotFoundError(localCgId);
|
|
3513
|
+
}
|
|
3462
3514
|
if (this.contextGraphBindingState.currentBindingFor(localCgId, sub) === undefined
|
|
3463
3515
|
&& sub.subscribed) {
|
|
3464
3516
|
await this.selfPrimeSubscriptionOnChainId(localCgId, sub, undefined, isCurrent, signal);
|
|
@@ -3749,7 +3801,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3749
3801
|
reconcileOrdinal: (lcg, ocg, ordinal, headBlock, context) => this.reconcileChainOrdinal(lcg, ocg, ordinal, headBlock, {
|
|
3750
3802
|
isTargetCurrent,
|
|
3751
3803
|
revalidateTarget,
|
|
3752
|
-
deferActiveFetch: true,
|
|
3753
3804
|
rememberFinalizedEvidence: context === undefined
|
|
3754
3805
|
|| context.headOrdinal - ordinal <= DKGAgentBase.VM_RECONCILE_BATCH_SIZE,
|
|
3755
3806
|
}),
|
|
@@ -3784,6 +3835,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3784
3835
|
},
|
|
3785
3836
|
confirmationDepth: DKGAgentBase.VM_RECONCILE_CONFIRMATION_DEPTH,
|
|
3786
3837
|
log: (msg) => this.log.info(createOperationContext('system'), msg),
|
|
3838
|
+
...(execution?.observePassTimings ? { observePassTimings: execution.observePassTimings } : {}),
|
|
3787
3839
|
};
|
|
3788
3840
|
}
|
|
3789
3841
|
async persistVmReconcileWatermark(localCgId, watermark, target) {
|
|
@@ -4162,425 +4214,29 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4162
4214
|
return { status: 'skipped', reason: 'failed' };
|
|
4163
4215
|
}
|
|
4164
4216
|
}
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
...await this.storageAckLedgerNamespaceSubGraphs(localCgId),
|
|
4188
|
-
])];
|
|
4189
|
-
const subGraphNamespaces = subGraphs
|
|
4190
|
-
.map((sg) => ({
|
|
4191
|
-
metaGraph: graphManager.sharedMemoryMetaUri(localCgId, sg),
|
|
4192
|
-
dataGraph: graphManager.sharedMemoryUri(localCgId, sg),
|
|
4193
|
-
}))
|
|
4194
|
-
.sort((a, b) => `${a.metaGraph}\0${a.dataGraph}`.localeCompare(`${b.metaGraph}\0${b.dataGraph}`));
|
|
4195
|
-
return [
|
|
4196
|
-
...this.vmReconcileRootSwmCandidateNamespaces(localCgId),
|
|
4197
|
-
...subGraphNamespaces,
|
|
4198
|
-
];
|
|
4199
|
-
}
|
|
4200
|
-
async collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId) {
|
|
4201
|
-
try {
|
|
4202
|
-
return { namespaces: await this.collectVmReconcileSwmCandidateNamespaces(localCgId), complete: true };
|
|
4203
|
-
}
|
|
4204
|
-
catch {
|
|
4205
|
-
return { namespaces: this.vmReconcileRootSwmCandidateNamespaces(localCgId), complete: false };
|
|
4206
|
-
}
|
|
4207
|
-
}
|
|
4208
|
-
vmReconcileSwmNamespaceKey(candidateNamespaces) {
|
|
4209
|
-
return candidateNamespaces
|
|
4210
|
-
.map((namespace) => `${namespace.metaGraph}\0${namespace.dataGraph}`)
|
|
4211
|
-
.sort()
|
|
4212
|
-
.join('\n');
|
|
4213
|
-
}
|
|
4214
|
-
async vmReconcilePeerTopology(localCgId) {
|
|
4215
|
-
try {
|
|
4216
|
-
const preferredPeerId = await this.resolvePreferredSyncPeerId(localCgId);
|
|
4217
|
-
const isPrivateContextGraph = await this.isPrivateContextGraph(localCgId);
|
|
4218
|
-
const libp2p = this.node?.libp2p;
|
|
4219
|
-
const getConnections = libp2p?.getConnections;
|
|
4220
|
-
if (typeof getConnections !== 'function')
|
|
4221
|
-
return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
|
|
4222
|
-
const peerIds = [...new Map(getConnections.call(libp2p)
|
|
4223
|
-
.map((connection) => [connection.remotePeer?.toString(), connection.remotePeer])
|
|
4224
|
-
.filter((entry) => typeof entry[0] === 'string' && entry[0].length > 0 && !!entry[1])).keys()].sort();
|
|
4225
|
-
const orderedPeers = this.selectCatchupPeers(peerIds.map((peerId) => ({ toString: () => peerId })), preferredPeerId, isPrivateContextGraph);
|
|
4226
|
-
return createVmReconcilePeerTopology({
|
|
4227
|
-
preferredPeerId: preferredPeerId ?? null,
|
|
4228
|
-
privateOnly: isPrivateContextGraph,
|
|
4229
|
-
peers: orderedPeers.map((peer) => {
|
|
4230
|
-
const peerId = peer.toString();
|
|
4231
|
-
return {
|
|
4232
|
-
peerId,
|
|
4233
|
-
core: this.peerCapabilityRegistry.supportsCore(peerId),
|
|
4234
|
-
};
|
|
4235
|
-
}),
|
|
4236
|
-
});
|
|
4237
|
-
}
|
|
4238
|
-
catch {
|
|
4239
|
-
return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
async readVmReconcileSwmGen(candidateNamespaces) {
|
|
4243
|
-
if (candidateNamespaces.length === 0)
|
|
4244
|
-
return 'empty:0';
|
|
4245
|
-
try {
|
|
4246
|
-
// The changelog cursor is a durable write generation: it survives daemon
|
|
4247
|
-
// restart, advances at the store mutation choke point, and is O(1) after
|
|
4248
|
-
// its one-time seed. It lets large (> fingerprint cap) stores reuse a
|
|
4249
|
-
// proven negative without reconstructing or sampling their SWM content.
|
|
4250
|
-
const changelog = asChangelogReader(this.store);
|
|
4251
|
-
if (changelog) {
|
|
4252
|
-
const head = await changelog.changelogHead({
|
|
4253
|
-
priority: 'background',
|
|
4254
|
-
source: 'agent.vmReconcile.negativeGeneration',
|
|
4255
|
-
});
|
|
4256
|
-
return `changelog:${head.era}:${head.seq}`;
|
|
4257
|
-
}
|
|
4258
|
-
const parts = [];
|
|
4259
|
-
const digestRows = (rows) => createHash('sha256').update(rows.join('\n'), 'utf8').digest('hex');
|
|
4260
|
-
const maxRows = DKGAgentBase.VM_RECONCILE_SWM_GEN_FINGERPRINT_MAX_ROWS;
|
|
4261
|
-
const isTooLarge = (rows) => rows.length > maxRows;
|
|
4262
|
-
for (const namespace of candidateNamespaces) {
|
|
4263
|
-
const metaGraph = assertSafeIri(namespace.metaGraph);
|
|
4264
|
-
const dataGraph = assertSafeIri(namespace.dataGraph);
|
|
4265
|
-
const operationRows = await this.store.query(`SELECT ?op ?root ?ts WHERE {
|
|
4266
|
-
GRAPH <${metaGraph}> {
|
|
4267
|
-
?op <http://dkg.io/ontology/rootEntity> ?root .
|
|
4268
|
-
OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
|
|
4269
|
-
}
|
|
4270
|
-
} ORDER BY ?op ?root ?ts LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.operations' });
|
|
4271
|
-
if (operationRows.type !== 'bindings')
|
|
4272
|
-
return null;
|
|
4273
|
-
if (isTooLarge(operationRows.bindings))
|
|
4274
|
-
return null;
|
|
4275
|
-
const operations = operationRows.bindings
|
|
4276
|
-
.map((row) => [
|
|
4277
|
-
String(row['op'] ?? ''),
|
|
4278
|
-
String(row['root'] ?? ''),
|
|
4279
|
-
String(row['ts'] ?? ''),
|
|
4280
|
-
].join('\0'))
|
|
4281
|
-
.sort();
|
|
4282
|
-
const dataRows = await this.store.query(`SELECT ?s ?p ?o WHERE {
|
|
4283
|
-
GRAPH <${dataGraph}> { ?s ?p ?o . }
|
|
4284
|
-
} ORDER BY ?s ?p ?o LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.data' });
|
|
4285
|
-
if (dataRows.type !== 'bindings')
|
|
4286
|
-
return null;
|
|
4287
|
-
if (isTooLarge(dataRows.bindings))
|
|
4288
|
-
return null;
|
|
4289
|
-
const dataTriples = dataRows.bindings
|
|
4290
|
-
.map((row) => [
|
|
4291
|
-
String(row['s'] ?? ''),
|
|
4292
|
-
String(row['p'] ?? ''),
|
|
4293
|
-
String(row['o'] ?? ''),
|
|
4294
|
-
].join('\0'))
|
|
4295
|
-
.sort();
|
|
4296
|
-
const privateRootRows = await this.store.query(`SELECT ?privateEntity ?privateRoot WHERE {
|
|
4297
|
-
GRAPH <${metaGraph}> {
|
|
4298
|
-
?privateEntity <http://dkg.io/ontology/privateMerkleRoot> ?privateRoot .
|
|
4299
|
-
}
|
|
4300
|
-
} ORDER BY ?privateEntity ?privateRoot LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.privateRoots' });
|
|
4301
|
-
if (privateRootRows.type !== 'bindings')
|
|
4302
|
-
return null;
|
|
4303
|
-
if (isTooLarge(privateRootRows.bindings))
|
|
4304
|
-
return null;
|
|
4305
|
-
const privateRoots = privateRootRows.bindings
|
|
4306
|
-
.map((row) => [
|
|
4307
|
-
String(row['privateEntity'] ?? ''),
|
|
4308
|
-
String(row['privateRoot'] ?? ''),
|
|
4309
|
-
].join('\0'))
|
|
4310
|
-
.sort();
|
|
4311
|
-
parts.push([
|
|
4312
|
-
`meta:${namespace.metaGraph}`,
|
|
4313
|
-
`data:${namespace.dataGraph}`,
|
|
4314
|
-
`ops:${operations.length}`,
|
|
4315
|
-
`opHash:${digestRows(operations)}`,
|
|
4316
|
-
`dataTriples:${dataTriples.length}`,
|
|
4317
|
-
`dataHash:${digestRows(dataTriples)}`,
|
|
4318
|
-
`privateRoots:${privateRoots.length}`,
|
|
4319
|
-
`privateRootHash:${digestRows(privateRoots)}`,
|
|
4320
|
-
].join(';'));
|
|
4321
|
-
}
|
|
4322
|
-
// Catch writes into a newly-created/unregistered namespace that the
|
|
4323
|
-
// current namespace enumeration cannot yet name. This process-local term
|
|
4324
|
-
// complements (rather than replaces) the content fingerprint: after a
|
|
4325
|
-
// restart the fingerprint remains the correctness gate while the counter
|
|
4326
|
-
// restarts harmlessly.
|
|
4327
|
-
const rootDataGraph = candidateNamespaces[0]?.dataGraph ?? '';
|
|
4328
|
-
const swmSuffix = rootDataGraph.indexOf('/_shared_memory');
|
|
4329
|
-
const graphPrefix = swmSuffix >= 0 ? `${rootDataGraph.slice(0, swmSuffix)}/` : rootDataGraph;
|
|
4330
|
-
const writeRevision = asGraphWriteRevisionSource(this.store)?.getWriteRevision(graphPrefix);
|
|
4331
|
-
// A remote mutation can still commit after this fingerprint's reads.
|
|
4332
|
-
// Never turn such an in-flight/indeterminate observation into a stable
|
|
4333
|
-
// negative-cache key.
|
|
4334
|
-
if (writeRevision && !writeRevision.stable)
|
|
4335
|
-
return null;
|
|
4336
|
-
if (writeRevision)
|
|
4337
|
-
parts.push(`writeGen:${writeRevision.generation}`);
|
|
4338
|
-
return parts.join('|');
|
|
4339
|
-
}
|
|
4340
|
-
catch {
|
|
4341
|
-
// Probe failures are not a stable SWM generation. Callers must not cache
|
|
4342
|
-
// or preserve a negative-cache gate from this result.
|
|
4343
|
-
}
|
|
4344
|
-
return null;
|
|
4345
|
-
}
|
|
4346
|
-
vmReconcileSwmGenHasOperations(swmGen) {
|
|
4347
|
-
return swmGen.split('|').some((part) => {
|
|
4348
|
-
const match = /(?:^|;)ops:(\d+)(?:;|$)/.exec(part);
|
|
4349
|
-
return match ? Number(match[1]) > 0 : false;
|
|
4350
|
-
});
|
|
4351
|
-
}
|
|
4352
|
-
vmReconcileSwmGenSupportsDurableNegative(swmGen) {
|
|
4353
|
-
// A changelog cursor covers every descendant graph mutation. The fallback
|
|
4354
|
-
// fingerprint covers only the bare SWM bucket, so an operation whose data
|
|
4355
|
-
// later lands in a per-KA child graph must fail open after restart.
|
|
4356
|
-
return swmGen.startsWith('changelog:') || !this.vmReconcileSwmGenHasOperations(swmGen);
|
|
4357
|
-
}
|
|
4358
|
-
vmReconcileSwmGenContainsSnapshot(cachedSwmGen, currentSwmGen) {
|
|
4359
|
-
return cachedSwmGen === currentSwmGen || cachedSwmGen.split('|').includes(currentSwmGen);
|
|
4360
|
-
}
|
|
4361
|
-
vmReconcileWorkspaceOperationPattern(candidateMetaGraphs) {
|
|
4362
|
-
const branches = [];
|
|
4363
|
-
for (const graph of candidateMetaGraphs) {
|
|
4364
|
-
try {
|
|
4365
|
-
branches.push(`{ GRAPH <${assertSafeIri(graph)}> {
|
|
4366
|
-
?op <http://dkg.io/ontology/rootEntity> ?root .
|
|
4367
|
-
OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
|
|
4368
|
-
} }`);
|
|
4369
|
-
}
|
|
4370
|
-
catch {
|
|
4371
|
-
// Skip unsafe graph names instead of building a malformed query.
|
|
4372
|
-
}
|
|
4373
|
-
}
|
|
4374
|
-
return branches.join(' UNION ');
|
|
4375
|
-
}
|
|
4376
|
-
deleteVmReconcileNegativeCacheEntry(cacheKey) {
|
|
4377
|
-
const existing = this.vmReconcileNegativeCache.get(cacheKey);
|
|
4378
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, existing?.localCgId ?? cacheKey.slice(0, Math.max(0, cacheKey.indexOf('\0'))));
|
|
4379
|
-
if (existing) {
|
|
4380
|
-
this.vmReconcileNegativeCache.delete(cacheKey);
|
|
4381
|
-
const keys = this.vmReconcileNegativeCacheKeysByCg.get(existing.localCgId);
|
|
4382
|
-
if (keys) {
|
|
4383
|
-
keys.delete(cacheKey);
|
|
4384
|
-
if (keys.size === 0)
|
|
4385
|
-
this.vmReconcileNegativeCacheKeysByCg.delete(existing.localCgId);
|
|
4386
|
-
}
|
|
4387
|
-
}
|
|
4388
|
-
void this.config.contextGraphSubscriptionStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
|
|
4389
|
-
// The in-memory invalidation remains authoritative for this process.
|
|
4390
|
-
});
|
|
4391
|
-
}
|
|
4392
|
-
indexVmReconcileNegativeCacheEntry(localCgId, cacheKey) {
|
|
4393
|
-
let keys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
|
|
4394
|
-
if (!keys) {
|
|
4395
|
-
keys = new Set();
|
|
4396
|
-
this.vmReconcileNegativeCacheKeysByCg.set(localCgId, keys);
|
|
4397
|
-
}
|
|
4398
|
-
keys.add(cacheKey);
|
|
4399
|
-
}
|
|
4400
|
-
markVmReconcileNegativeCacheHydrated(cacheKey, localCgId) {
|
|
4401
|
-
// Access order keeps actively reused keys resident while old one-shot
|
|
4402
|
-
// misses fall out. Eviction is fail-open: it only permits another durable
|
|
4403
|
-
// lookup if the same key is encountered later.
|
|
4404
|
-
this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
|
|
4405
|
-
this.vmReconcileNegativeCacheHydrated.set(cacheKey, localCgId);
|
|
4406
|
-
while (this.vmReconcileNegativeCacheHydrated.size
|
|
4407
|
-
> DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
4408
|
-
const oldestKey = this.vmReconcileNegativeCacheHydrated.keys().next().value;
|
|
4409
|
-
if (oldestKey === undefined)
|
|
4410
|
-
break;
|
|
4411
|
-
this.vmReconcileNegativeCacheHydrated.delete(oldestKey);
|
|
4412
|
-
}
|
|
4413
|
-
}
|
|
4414
|
-
async shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId) {
|
|
4415
|
-
let cached = this.vmReconcileNegativeCache.get(cacheKey);
|
|
4416
|
-
const durableStateAlreadyConsulted = this.vmReconcileNegativeCacheHydrated.has(cacheKey);
|
|
4417
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
|
|
4418
|
-
if (!cached && !durableStateAlreadyConsulted) {
|
|
4419
|
-
try {
|
|
4420
|
-
const durable = await this.config.contextGraphSubscriptionStore
|
|
4421
|
-
?.loadVmReconcileNegative?.(cacheKey);
|
|
4422
|
-
const durablePeerTopology = durable
|
|
4423
|
-
? isVmReconcilePeerTopology(durable.peerTopology)
|
|
4424
|
-
? durable.peerTopology
|
|
4425
|
-
: parseLegacyVmReconcilePeerTopologyKey(durable.peerTopologyKey)
|
|
4426
|
-
: null;
|
|
4427
|
-
const durableCleanMissPeerIds = durablePeerTopology
|
|
4428
|
-
? parseVmReconcileCleanMissPeerIds(durable?.cleanMissPeerIds ?? [], durablePeerTopology)
|
|
4429
|
-
: null;
|
|
4430
|
-
if (durable &&
|
|
4431
|
-
durable.cacheKey === cacheKey &&
|
|
4432
|
-
durable.localCgId === localCgId &&
|
|
4433
|
-
Number.isInteger(durable.failures) && durable.failures > 0 &&
|
|
4434
|
-
Number.isFinite(durable.nextRetryAt) &&
|
|
4435
|
-
typeof durable.swmGen === 'string' &&
|
|
4436
|
-
Array.isArray(durable.candidateNamespaces) &&
|
|
4437
|
-
durable.candidateNamespaces.every((item) => typeof item?.metaGraph === 'string' && typeof item?.dataGraph === 'string') &&
|
|
4438
|
-
durablePeerTopology !== null &&
|
|
4439
|
-
durableCleanMissPeerIds !== null) {
|
|
4440
|
-
if (!this.vmReconcileSwmGenSupportsDurableNegative(durable.swmGen)) {
|
|
4441
|
-
await this.config.contextGraphSubscriptionStore
|
|
4442
|
-
?.deleteVmReconcileNegative?.(cacheKey);
|
|
4443
|
-
}
|
|
4444
|
-
else {
|
|
4445
|
-
cached = {
|
|
4446
|
-
localCgId: durable.localCgId,
|
|
4447
|
-
failures: durable.failures,
|
|
4448
|
-
nextRetryAt: durable.nextRetryAt,
|
|
4449
|
-
swmGen: durable.swmGen,
|
|
4450
|
-
candidateNamespaces: durable.candidateNamespaces,
|
|
4451
|
-
peerTopology: durablePeerTopology,
|
|
4452
|
-
cleanMissPeerIds: durableCleanMissPeerIds,
|
|
4453
|
-
};
|
|
4454
|
-
this.vmReconcileNegativeCache.set(cacheKey, cached);
|
|
4455
|
-
this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
|
|
4456
|
-
}
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4459
|
-
catch {
|
|
4460
|
-
// Persistence is an accelerator only. Fail open to an authoritative scan.
|
|
4461
|
-
}
|
|
4462
|
-
}
|
|
4463
|
-
if (!cached)
|
|
4464
|
-
return false;
|
|
4465
|
-
if (Date.now() >= cached.nextRetryAt)
|
|
4466
|
-
return false;
|
|
4467
|
-
try {
|
|
4468
|
-
try {
|
|
4469
|
-
await this.primeCatchupConnections();
|
|
4470
|
-
}
|
|
4471
|
-
catch {
|
|
4472
|
-
// Best effort only; an unchanged connection view can still honor the
|
|
4473
|
-
// cached miss until the backoff expires.
|
|
4474
|
-
}
|
|
4475
|
-
const currentPeerTopology = await this.vmReconcilePeerTopology(localCgId);
|
|
4476
|
-
if (!canReuseVmReconcilePeerTopology({
|
|
4477
|
-
topology: cached.peerTopology,
|
|
4478
|
-
cleanMissPeerIds: cached.cleanMissPeerIds,
|
|
4479
|
-
}, currentPeerTopology)) {
|
|
4480
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4481
|
-
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
4482
|
-
return false;
|
|
4483
|
-
}
|
|
4484
|
-
const currentNamespaces = await this.collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId);
|
|
4485
|
-
const currentNamespaceKey = this.vmReconcileSwmNamespaceKey(currentNamespaces.namespaces);
|
|
4486
|
-
const cachedNamespaceKey = this.vmReconcileSwmNamespaceKey(cached.candidateNamespaces);
|
|
4487
|
-
if (currentNamespaceKey !== cachedNamespaceKey) {
|
|
4488
|
-
if (!currentNamespaces.complete) {
|
|
4489
|
-
const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
|
|
4490
|
-
if (currentSwmGen === null) {
|
|
4491
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4492
|
-
return false;
|
|
4493
|
-
}
|
|
4494
|
-
if (!this.vmReconcileSwmGenContainsSnapshot(cached.swmGen, currentSwmGen)) {
|
|
4495
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4496
|
-
return false;
|
|
4497
|
-
}
|
|
4498
|
-
return true;
|
|
4499
|
-
}
|
|
4500
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4501
|
-
return false;
|
|
4502
|
-
}
|
|
4503
|
-
const pattern = this.vmReconcileWorkspaceOperationPattern(currentNamespaces.namespaces.map((namespace) => namespace.metaGraph));
|
|
4504
|
-
if (!pattern)
|
|
4505
|
-
return true;
|
|
4506
|
-
const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
|
|
4507
|
-
if (currentSwmGen === null) {
|
|
4508
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4509
|
-
return false;
|
|
4510
|
-
}
|
|
4511
|
-
if (currentSwmGen !== cached.swmGen) {
|
|
4512
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4513
|
-
return false;
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
catch {
|
|
4517
|
-
// Unexpected validation failures leave the existing backoff in place;
|
|
4518
|
-
// expected generation probe failures return null and clear the gate above.
|
|
4519
|
-
}
|
|
4520
|
-
return true;
|
|
4521
|
-
}
|
|
4522
|
-
recordVmReconcileNegativeCache(cacheKey, localCgId, state) {
|
|
4523
|
-
if (state.swmGen === null) {
|
|
4524
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4525
|
-
return;
|
|
4526
|
-
}
|
|
4527
|
-
this.pruneVmReconcileState();
|
|
4528
|
-
const previous = this.vmReconcileNegativeCache.get(cacheKey);
|
|
4529
|
-
const failures = (previous?.failures ?? 0) + 1;
|
|
4530
|
-
const exponentialBackoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_BASE_MS * 2 ** Math.max(0, failures - 1));
|
|
4531
|
-
const jitterSample = createHash('sha256')
|
|
4532
|
-
.update(`${this.node.peerId.toString()}\0${cacheKey}\0${failures}`)
|
|
4533
|
-
.digest()
|
|
4534
|
-
.readUInt32BE(0) / 0x1_0000_0000;
|
|
4535
|
-
const backoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, Math.max(1, Math.round(exponentialBackoff * (0.8 + jitterSample * 0.4))));
|
|
4536
|
-
getMetrics().storeRetryAttemptsTotal.add(1, {
|
|
4537
|
-
scope: 'vm_reconcile',
|
|
4538
|
-
reason: 'no_swm',
|
|
4539
|
-
attempt: Math.min(failures, 16),
|
|
4217
|
+
initializeVmReconcilePublicCoreTransportPreferencePolicy() {
|
|
4218
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy ??= new VmRecoveryCoreTransportPreferencePolicy({
|
|
4219
|
+
now: () => this.vmReconcileRotationNow(),
|
|
4220
|
+
connectionKey: peerId => this.getSyncReconcilerConnectionKey(peerId),
|
|
4221
|
+
supportsCore: peerId => this.peerCapabilityRegistry.supportsCore(peerId),
|
|
4222
|
+
holderReuseEnabled: resolveExactBatchStreamEnabled,
|
|
4223
|
+
captureScope: (localCgId, candidatePeerIds) => typeof this.chain.deploymentId === 'string' ? {
|
|
4224
|
+
deploymentId: this.chain.deploymentId,
|
|
4225
|
+
lifecycleGeneration: this.vmReconcileLifecycleGeneration,
|
|
4226
|
+
bindingGeneration: this.contextGraphBindingState.capture(localCgId),
|
|
4227
|
+
selectedBindingGeneration: this.selectedVmReconcileCursors.get(localCgId)?.bindingGeneration,
|
|
4228
|
+
candidatePeerIds: [...candidatePeerIds],
|
|
4229
|
+
} : undefined,
|
|
4230
|
+
scopeIsCurrent: (localCgId, scope) => this.chain.deploymentId === scope.deploymentId
|
|
4231
|
+
&& this.vmReconcileLifecycleGeneration === scope.lifecycleGeneration
|
|
4232
|
+
&& !this.vmReconcileRotationClosed
|
|
4233
|
+
&& this.contextGraphBindingState.capture(localCgId) === scope.bindingGeneration
|
|
4234
|
+
&& this.selectedVmReconcileCursors.get(localCgId)?.bindingGeneration === scope.selectedBindingGeneration
|
|
4235
|
+
&& this.vmReconcilePeerMembershipMatches(new Set(scope.candidatePeerIds), this.vmReconcileObservedCandidatePeerIds(localCgId)),
|
|
4236
|
+
}, {
|
|
4237
|
+
ttlMs: DKGAgentBase.VM_RECONCILE_PUBLIC_CORE_TRANSPORT_TTL_MS,
|
|
4238
|
+
maxEntries: DKGAgentBase.VM_RECONCILE_CG_STATE_MAX_ENTRIES,
|
|
4540
4239
|
});
|
|
4541
|
-
if (previous) {
|
|
4542
|
-
// Replace in place without racing an asynchronous durable DELETE against
|
|
4543
|
-
// the SAVE below. The record key/local CG are unchanged here.
|
|
4544
|
-
this.vmReconcileNegativeCache.delete(cacheKey);
|
|
4545
|
-
const keys = this.vmReconcileNegativeCacheKeysByCg.get(previous.localCgId);
|
|
4546
|
-
keys?.delete(cacheKey);
|
|
4547
|
-
if (keys?.size === 0)
|
|
4548
|
-
this.vmReconcileNegativeCacheKeysByCg.delete(previous.localCgId);
|
|
4549
|
-
}
|
|
4550
|
-
const record = {
|
|
4551
|
-
localCgId,
|
|
4552
|
-
failures,
|
|
4553
|
-
nextRetryAt: Date.now() + backoff,
|
|
4554
|
-
swmGen: state.swmGen,
|
|
4555
|
-
candidateNamespaces: state.candidateNamespaces,
|
|
4556
|
-
peerTopology: state.peerTopology,
|
|
4557
|
-
cleanMissPeerIds: state.cleanMissPeerIds,
|
|
4558
|
-
};
|
|
4559
|
-
this.vmReconcileNegativeCache.set(cacheKey, record);
|
|
4560
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
|
|
4561
|
-
this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
|
|
4562
|
-
const durableStore = this.config.contextGraphSubscriptionStore;
|
|
4563
|
-
if (this.vmReconcileSwmGenSupportsDurableNegative(record.swmGen)) {
|
|
4564
|
-
void durableStore?.saveVmReconcileNegative?.({
|
|
4565
|
-
cacheKey,
|
|
4566
|
-
localCgId: record.localCgId,
|
|
4567
|
-
failures: record.failures,
|
|
4568
|
-
nextRetryAt: record.nextRetryAt,
|
|
4569
|
-
swmGen: record.swmGen,
|
|
4570
|
-
candidateNamespaces: record.candidateNamespaces,
|
|
4571
|
-
peerTopologyKey: encodeLegacyVmReconcilePeerTopologyKey(record.peerTopology),
|
|
4572
|
-
peerTopology: record.peerTopology,
|
|
4573
|
-
cleanMissPeerIds: record.cleanMissPeerIds,
|
|
4574
|
-
}).catch(() => {
|
|
4575
|
-
// Persistence is an accelerator only; the process-local gate still works.
|
|
4576
|
-
});
|
|
4577
|
-
}
|
|
4578
|
-
else {
|
|
4579
|
-
void durableStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
|
|
4580
|
-
// Fail open after restart even if best-effort cleanup cannot complete.
|
|
4581
|
-
});
|
|
4582
|
-
}
|
|
4583
|
-
this.pruneVmReconcileState();
|
|
4584
4240
|
}
|
|
4585
4241
|
vmReconcileRotationNow() {
|
|
4586
4242
|
return performance.now();
|
|
@@ -4861,11 +4517,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4861
4517
|
* mutable transition so partial responses revoke affinity consistently.
|
|
4862
4518
|
* Unavailable peers and the global considered-peer cap remain authoritative.
|
|
4863
4519
|
*/
|
|
4864
|
-
selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy) {
|
|
4520
|
+
selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy, binding) {
|
|
4865
4521
|
const uncreditedCandidateOrder = record
|
|
4866
4522
|
? this.vmReconcileUncreditedCandidateOrder(record)
|
|
4867
4523
|
: fallbackCandidatePeerIds;
|
|
4868
|
-
|
|
4524
|
+
const preferredPeerId = binding
|
|
4525
|
+
? this.vmReconcilePublicCoreTransportPreferencePolicy.preferredPeer(binding.localCgId, binding.onChainCgId, uncreditedCandidateOrder)
|
|
4526
|
+
: undefined;
|
|
4527
|
+
const now = this.vmReconcileRotationNow();
|
|
4528
|
+
const { order, heldOffPeerIds } = orderVmRecoveryCandidates({
|
|
4529
|
+
candidatePeerIds: uncreditedCandidateOrder,
|
|
4530
|
+
preferredPeerId,
|
|
4531
|
+
streamPeerIds: binding?.experimentalStreamPeerIds,
|
|
4532
|
+
lastAttemptedPeerId: record?.lastAttemptedPeerId,
|
|
4533
|
+
hasSetback: (peerId) => binding !== undefined
|
|
4534
|
+
&& record?.streamSetbackPeerIds?.has(peerId) === true
|
|
4535
|
+
&& this.vmReconcileStreamSetbackPolicy.inSetbackStreak(binding.localCgId, peerId, now),
|
|
4536
|
+
isHeldOff: (peerId) => binding !== undefined
|
|
4537
|
+
&& this.vmReconcileStreamSetbackPolicy.heldOff(binding.localCgId, peerId, now),
|
|
4538
|
+
});
|
|
4539
|
+
return policy.selectNextCandidate(order, DKGAgentBase.VM_RECONCILE_EXACT_PEER_MAX, heldOffPeerIds);
|
|
4869
4540
|
}
|
|
4870
4541
|
findVmReconcileRotationReplacement(requestingCgId) {
|
|
4871
4542
|
if (this.vmReconcileRotationState.size < DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
@@ -4936,7 +4607,11 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4936
4607
|
if (key.startsWith(prefix))
|
|
4937
4608
|
this.vmReconcileRotationState.delete(key);
|
|
4938
4609
|
}
|
|
4610
|
+
this.vmReconcileTransportBudgetPolicy.forgetContextGraph(localCgId);
|
|
4611
|
+
this.vmReconcileStreamSetbackPolicy.forgetContextGraph(localCgId);
|
|
4939
4612
|
this.vmReconcileRotationAdmissionCursorByCg.delete(localCgId);
|
|
4613
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy?.forgetContextGraph(localCgId);
|
|
4614
|
+
existingVmRecoveryPreparation(this)?.discard(localCgId);
|
|
4940
4615
|
}
|
|
4941
4616
|
closeVmReconcileRotationState() {
|
|
4942
4617
|
this.vmReconcileLifecycleController?.abort();
|
|
@@ -4947,11 +4622,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4947
4622
|
// without running the base constructor. Shutdown must remain best-effort
|
|
4948
4623
|
// for that supported test seam and never mask later teardown failures.
|
|
4949
4624
|
this.vmReconcileRotationState?.clear();
|
|
4625
|
+
this.vmReconcileTransportBudgetPolicy?.clear();
|
|
4626
|
+
this.vmReconcileStreamSetbackPolicy?.clear();
|
|
4950
4627
|
this.vmReconcileRotationAdmissionCursorByCg?.clear();
|
|
4951
4628
|
this.vmReconcileCuratorPeersByCg?.clear();
|
|
4952
4629
|
this.vmReconcileCuratorPageCursorByCg?.clear();
|
|
4953
4630
|
this.vmReconcileExactPeerCapabilities?.clear();
|
|
4631
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy?.clear();
|
|
4954
4632
|
this.vmRefreshQueue?.clear();
|
|
4633
|
+
// Cancel speculative sizing reads and let shutdown wait for them to settle.
|
|
4634
|
+
const preparation = existingVmRecoveryPreparation(this);
|
|
4635
|
+
if (preparation && this.vmReconcilePhysicalRuns) {
|
|
4636
|
+
trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, preparation.close());
|
|
4637
|
+
}
|
|
4638
|
+
else {
|
|
4639
|
+
void preparation?.close();
|
|
4640
|
+
}
|
|
4955
4641
|
}
|
|
4956
4642
|
openVmReconcileRotationState() {
|
|
4957
4643
|
if (!this.vmReconcileLifecycleController || this.vmReconcileLifecycleController.signal.aborted) {
|
|
@@ -4978,6 +4664,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4978
4664
|
return;
|
|
4979
4665
|
if (peerId !== undefined) {
|
|
4980
4666
|
capturedRecord.attemptedPeerIds.add(peerId);
|
|
4667
|
+
// This attempt gave a verdict: an earlier setback with the peer is history.
|
|
4668
|
+
capturedRecord.streamSetbackPeerIds?.delete(peerId);
|
|
4981
4669
|
if (disposition === 'clean-absent')
|
|
4982
4670
|
capturedRecord.cleanAbsentPeerIds.add(peerId);
|
|
4983
4671
|
// Preserve fairly accumulated proof progress while other targets share
|
|
@@ -5002,6 +4690,23 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5002
4690
|
}
|
|
5003
4691
|
this.touchVmReconcileRotationRecord(slotKey, capturedRecord);
|
|
5004
4692
|
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Hand a target its turn at `peerId` back after an attempt that ended
|
|
4695
|
+
* without a verdict on the peer's data. The peer is uncredited again, so the
|
|
4696
|
+
* cycle cannot complete, and so cannot back off, without asking it. Removing
|
|
4697
|
+
* a mark is safe against any roster, which is why membership is not checked.
|
|
4698
|
+
* The setback is noted on the target, for candidate selection to order by.
|
|
4699
|
+
*/
|
|
4700
|
+
releaseVmReconcileRotationAttempt(target, peerId, capturedRecord) {
|
|
4701
|
+
if (this.vmReconcileRotationClosed)
|
|
4702
|
+
return;
|
|
4703
|
+
const slotKey = this.vmReconcileRotationSlotKey(target);
|
|
4704
|
+
if (this.vmReconcileRotationState.get(slotKey) !== capturedRecord)
|
|
4705
|
+
return;
|
|
4706
|
+
capturedRecord.attemptedPeerIds.delete(peerId);
|
|
4707
|
+
(capturedRecord.streamSetbackPeerIds ??= new Set()).add(peerId);
|
|
4708
|
+
this.touchVmReconcileRotationRecord(slotKey, capturedRecord);
|
|
4709
|
+
}
|
|
5005
4710
|
creditVmReconcileCleanAbsence(target, peerId, expectedCandidatePeerIds, capturedRecord) {
|
|
5006
4711
|
this.settleVmReconcileRotationAttempt(target, peerId, 'clean-absent', expectedCandidatePeerIds, capturedRecord);
|
|
5007
4712
|
}
|
|
@@ -5032,26 +4737,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5032
4737
|
this.installVmReconcileActiveFetchCooldown(localCgId, now);
|
|
5033
4738
|
return true;
|
|
5034
4739
|
}
|
|
5035
|
-
vmReconcileActiveFetchHadUsableResponse(result) {
|
|
5036
|
-
if ((result.peersSucceeded ?? 0) > 0)
|
|
5037
|
-
return true;
|
|
5038
|
-
if ((result.sharedMemorySynced ?? 0) > 0)
|
|
5039
|
-
return true;
|
|
5040
|
-
const shared = result.diagnostics?.sharedMemory;
|
|
5041
|
-
if (!shared)
|
|
5042
|
-
return false;
|
|
5043
|
-
return (shared.insertedDataTriples ?? 0) > 0
|
|
5044
|
-
|| (shared.insertedMetaTriples ?? 0) > 0
|
|
5045
|
-
|| (shared.checkpointAdvances ?? 0) > 0
|
|
5046
|
-
|| ((shared.completedPhases ?? 0) > 0 && (shared.resumedPhases ?? 0) > 0);
|
|
5047
|
-
}
|
|
5048
4740
|
pruneVmReconcileState(now = Date.now()) {
|
|
5049
|
-
while (this.vmReconcileNegativeCache.size > DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
5050
|
-
const oldestKey = this.vmReconcileNegativeCache.keys().next().value;
|
|
5051
|
-
if (oldestKey === undefined)
|
|
5052
|
-
break;
|
|
5053
|
-
this.deleteVmReconcileNegativeCacheEntry(oldestKey);
|
|
5054
|
-
}
|
|
5055
4741
|
for (const [localCgId, cooldown] of this.vmReconcileFetchCooldowns) {
|
|
5056
4742
|
if (now - cooldown.startedAt >= DKGAgentBase.VM_RECONCILE_SWEEP_INTERVAL_MS) {
|
|
5057
4743
|
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
@@ -5110,22 +4796,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5110
4796
|
});
|
|
5111
4797
|
}
|
|
5112
4798
|
forceClearVmReconcileStateForContextGraph(localCgId, options = {}) {
|
|
5113
|
-
const negativeCacheKeys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
|
|
5114
|
-
if (negativeCacheKeys) {
|
|
5115
|
-
for (const cacheKey of Array.from(negativeCacheKeys)) {
|
|
5116
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5119
|
-
for (const [cacheKey, hydratedLocalCgId] of this.vmReconcileNegativeCacheHydrated) {
|
|
5120
|
-
if (hydratedLocalCgId === localCgId) {
|
|
5121
|
-
this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
|
|
5122
|
-
}
|
|
5123
|
-
}
|
|
5124
|
-
void this.config.contextGraphSubscriptionStore
|
|
5125
|
-
?.deleteVmReconcileNegativesForContextGraph?.(localCgId)
|
|
5126
|
-
.catch(() => {
|
|
5127
|
-
// Best-effort durable cleanup; generation checks still reject stale rows.
|
|
5128
|
-
});
|
|
5129
4799
|
this.reconcileCursors.delete(localCgId);
|
|
5130
4800
|
if (options.includeSelectedCursor !== false
|
|
5131
4801
|
&& this.selectedVmReconcileCursors.delete(localCgId)) {
|
|
@@ -5214,97 +4884,120 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5214
4884
|
}
|
|
5215
4885
|
pruneVmReconcileCacheKeySiblings(cacheKey) {
|
|
5216
4886
|
const prefix = this.vmReconcileCacheKeyPrefix(cacheKey);
|
|
5217
|
-
for (const key of this.vmReconcileNegativeCache.keys()) {
|
|
5218
|
-
if (key !== cacheKey && key.startsWith(prefix)) {
|
|
5219
|
-
this.deleteVmReconcileNegativeCacheEntry(key);
|
|
5220
|
-
}
|
|
5221
|
-
}
|
|
5222
4887
|
this.recentReconciledUals.deleteByPrefix(prefix, cacheKey);
|
|
5223
4888
|
}
|
|
5224
|
-
vmReconcileConnectedPeerCount() {
|
|
5225
|
-
try {
|
|
5226
|
-
const libp2p = this.node?.libp2p;
|
|
5227
|
-
const getConnections = libp2p?.getConnections;
|
|
5228
|
-
if (typeof getConnections !== 'function')
|
|
5229
|
-
return 0;
|
|
5230
|
-
const uniquePeers = new Set(getConnections.call(libp2p)
|
|
5231
|
-
.map((connection) => connection.remotePeer?.toString())
|
|
5232
|
-
.filter((peerId) => typeof peerId === 'string' && peerId.length > 0));
|
|
5233
|
-
return uniquePeers.size;
|
|
5234
|
-
}
|
|
5235
|
-
catch {
|
|
5236
|
-
return 0;
|
|
5237
|
-
}
|
|
5238
|
-
}
|
|
5239
4889
|
/**
|
|
5240
|
-
* Execute one
|
|
5241
|
-
* roster
|
|
5242
|
-
*
|
|
5243
|
-
*
|
|
4890
|
+
* Execute one provider after connection/network readiness. The caller owns
|
|
4891
|
+
* roster, packing, and provider affinity; local sync admission starts the
|
|
4892
|
+
* physical attempt before rotation marks are installed. This executor owns
|
|
4893
|
+
* exact or cached legacy work, per-UAL chain revalidation, and settlement.
|
|
5244
4894
|
* Returns immutable evidence for the caller to merge. A stale lifecycle is
|
|
5245
4895
|
* discriminated by whether the physical attempt had already been admitted;
|
|
5246
4896
|
* only the pre-admission variant guarantees zero attempt side effects.
|
|
5247
4897
|
*/
|
|
5248
4898
|
async executeVmRecoveryBatch(input) {
|
|
5249
|
-
const { localCgId, onChainCgId, peerId, attempts, unavailablePeerIds, headBlock, signal, isRecoveryCurrent, revalidateTarget, ctx, } = input;
|
|
4899
|
+
const { localCgId, onChainCgId, peerId, attempts, unavailablePeerIds, headBlock, signal, isRecoveryCurrent, revalidateTarget, ctx, exactRecoveryTransportMode = 'stream-preferred', legacyAttemptTimeoutMs, registeredPublicEvidence, phases, } = input;
|
|
5250
4900
|
const unavailablePeerIdSet = new Set(unavailablePeerIds);
|
|
5251
4901
|
// The caller's last guard can race an unsubscribe/rebind before this
|
|
5252
4902
|
// executor begins. Do not retain attempt evidence or emit a fetch event
|
|
5253
4903
|
// until this exact lifecycle has been re-proved at the ownership boundary.
|
|
5254
4904
|
if (!isRecoveryCurrent())
|
|
5255
4905
|
return { kind: 'not-started-stale' };
|
|
5256
|
-
const useCachedLegacyFallback =
|
|
4906
|
+
const useCachedLegacyFallback = exactRecoveryTransportMode !== 'stream-required'
|
|
4907
|
+
&& this.vmReconcileExactFilterUnsupported(peerId);
|
|
5257
4908
|
const handledOrdinals = [];
|
|
5258
4909
|
const attemptedOrdinals = [];
|
|
5259
4910
|
const outcomes = [];
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
attempt.
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
4911
|
+
let workStarted = false;
|
|
4912
|
+
const onWorkStarted = () => {
|
|
4913
|
+
if (workStarted || !isRecoveryCurrent())
|
|
4914
|
+
return;
|
|
4915
|
+
workStarted = true;
|
|
4916
|
+
for (const attempt of attempts) {
|
|
4917
|
+
const batchTarget = attempt.entry.target;
|
|
4918
|
+
this.vmReconcileTransportBudgetPolicy.recordAdmitted(batchTarget, peerId);
|
|
4919
|
+
handledOrdinals.push(batchTarget.ordinal);
|
|
4920
|
+
attemptedOrdinals.push(batchTarget.ordinal);
|
|
4921
|
+
const record = attempt.installedRecord;
|
|
4922
|
+
const slotKey = this.vmReconcileRotationSlotKey(batchTarget);
|
|
4923
|
+
if (record && this.vmReconcileRotationState.get(slotKey) === record
|
|
4924
|
+
&& record.fingerprint === this.vmReconcileRotationFingerprint(batchTarget)) {
|
|
4925
|
+
record.lastAttemptedPeerId = peerId;
|
|
4926
|
+
record.attemptedPeerIds.add(peerId);
|
|
4927
|
+
record.collectionDeadlineAt = this.vmReconcileRotationNow()
|
|
4928
|
+
+ DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
|
|
4929
|
+
this.touchVmReconcileRotationRecord(slotKey, record);
|
|
4930
|
+
}
|
|
4931
|
+
this.emitReplication({
|
|
4932
|
+
contextGraphId: localCgId,
|
|
4933
|
+
onChainCgId: onChainCgId.toString(),
|
|
4934
|
+
action: 'fetch',
|
|
4935
|
+
ordinal: batchTarget.ordinal,
|
|
4936
|
+
kaId: batchTarget.kaId,
|
|
4937
|
+
ual: batchTarget.ual,
|
|
4938
|
+
detail: useCachedLegacyFallback
|
|
4939
|
+
? 'legacy-sync'
|
|
4940
|
+
: attempts.length > 1 ? 'exact-asset-batch' : 'exact-asset',
|
|
4941
|
+
});
|
|
5270
4942
|
}
|
|
5271
|
-
|
|
5272
|
-
contextGraphId: localCgId,
|
|
5273
|
-
onChainCgId: onChainCgId.toString(),
|
|
5274
|
-
action: 'fetch',
|
|
5275
|
-
ordinal: batchTarget.ordinal,
|
|
5276
|
-
kaId: batchTarget.kaId,
|
|
5277
|
-
ual: batchTarget.ual,
|
|
5278
|
-
detail: useCachedLegacyFallback
|
|
5279
|
-
? 'legacy-sync'
|
|
5280
|
-
: attempts.length > 1 ? 'exact-asset-batch' : 'exact-asset',
|
|
5281
|
-
});
|
|
5282
|
-
}
|
|
4943
|
+
};
|
|
5283
4944
|
let disposition = 'incomplete';
|
|
4945
|
+
let streamOutcome;
|
|
4946
|
+
let localAdmissionDeferred = false;
|
|
4947
|
+
const legacyAttemptStartedAt = Date.now();
|
|
4948
|
+
const remainingLegacyAttemptMs = () => legacyAttemptTimeoutMs === undefined
|
|
4949
|
+
? undefined
|
|
4950
|
+
: Math.max(0, legacyAttemptTimeoutMs - (Date.now() - legacyAttemptStartedAt));
|
|
5284
4951
|
const runLegacyFallback = async () => {
|
|
4952
|
+
const remainingMs = remainingLegacyAttemptMs();
|
|
4953
|
+
// Both the exact-filter probe and a full-scan fallback belong to one
|
|
4954
|
+
// provider turn. Do not grant a fresh long deadline after the probe has
|
|
4955
|
+
// already spent the bounded legacy window.
|
|
4956
|
+
if (remainingMs !== undefined && remainingMs < SYNC_MIN_GRAPH_BUDGET_MS) {
|
|
4957
|
+
this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)} skipped: attempt budget exhausted`);
|
|
4958
|
+
return;
|
|
4959
|
+
}
|
|
5285
4960
|
try {
|
|
5286
4961
|
const fallback = await this.runLegacyDurableSyncDetailed(ctx, peerId, [localCgId], undefined, undefined, undefined, {
|
|
5287
4962
|
stopOnBackoffWorthyFailure: true,
|
|
5288
|
-
priority:
|
|
4963
|
+
priority: VM_RECOVERY_SYNC_PRIORITY,
|
|
5289
4964
|
source: 'vm-recovery',
|
|
4965
|
+
onWorkStarted,
|
|
5290
4966
|
signal,
|
|
5291
4967
|
isCurrent: isRecoveryCurrent,
|
|
4968
|
+
...(remainingMs === undefined ? {} : { totalTimeoutMs: remainingMs }),
|
|
5292
4969
|
});
|
|
4970
|
+
if (fallback.admission === 'work-started')
|
|
4971
|
+
onWorkStarted();
|
|
4972
|
+
// An admitted exact request retains its attempt through a refused
|
|
4973
|
+
// fallback. Only the original never-started operation can defer.
|
|
4974
|
+
localAdmissionDeferred = !workStarted && fallback.admission === 'local-admission-deferred';
|
|
5293
4975
|
this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)}: fetched=${fallback.result.fetchedDataTriples + fallback.result.fetchedMetaTriples} inserted=${fallback.result.insertedTriples} failed=${fallback.result.failedPeers + fallback.result.failedPhases}`);
|
|
5294
4976
|
}
|
|
5295
4977
|
catch (fallbackError) {
|
|
5296
4978
|
this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)} failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
5297
4979
|
}
|
|
5298
4980
|
};
|
|
4981
|
+
const exchangeInterval = phases?.begin();
|
|
5299
4982
|
try {
|
|
5300
4983
|
if (useCachedLegacyFallback) {
|
|
5301
4984
|
await runLegacyFallback();
|
|
5302
4985
|
}
|
|
5303
4986
|
else {
|
|
5304
|
-
const detailed = await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, attempts.map(({ entry }) => entry.target.ual), {
|
|
4987
|
+
const detailed = await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, attempts.map(({ entry }) => entry.target.ual), {
|
|
4988
|
+
signal, isCurrent: isRecoveryCurrent, onWorkStarted, exactRecoveryTransportMode,
|
|
4989
|
+
...(legacyAttemptTimeoutMs === undefined ? {} : { totalTimeoutMs: legacyAttemptTimeoutMs }),
|
|
4990
|
+
...(registeredPublicEvidence ? { registeredPublicEvidence } : {}),
|
|
4991
|
+
});
|
|
5305
4992
|
const { result } = detailed;
|
|
5306
4993
|
disposition = detailed.disposition;
|
|
5307
|
-
|
|
4994
|
+
streamOutcome = detailed.streamOutcome;
|
|
4995
|
+
// A fresh capability response is physical peer evidence even when
|
|
4996
|
+
// its later ordinary fallback cannot acquire local capacity.
|
|
4997
|
+
if (detailed.admission === 'work-started' || detailed.responderCapability !== undefined)
|
|
4998
|
+
onWorkStarted();
|
|
4999
|
+
localAdmissionDeferred = !workStarted && detailed.admission === 'local-admission-deferred';
|
|
5000
|
+
if (exactRecoveryTransportMode !== 'stream-required' && detailed.responderCapability === 'legacy-filter-unsupported') {
|
|
5308
5001
|
this.rememberVmReconcileExactFilterUnsupported(peerId);
|
|
5309
5002
|
if (isRecoveryCurrent() && disposition === 'incomplete') {
|
|
5310
5003
|
await runLegacyFallback();
|
|
@@ -5316,18 +5009,44 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5316
5009
|
catch (error) {
|
|
5317
5010
|
this.log.info(ctx, `VM exact fetch for "${localCgId}" from ${peerId.slice(-8)} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5318
5011
|
}
|
|
5012
|
+
if (phases && exchangeInterval)
|
|
5013
|
+
phases.chargeSince('exchange', exchangeInterval.startedAt, exchangeInterval.before);
|
|
5319
5014
|
if (!isRecoveryCurrent())
|
|
5320
|
-
return { kind: 'stale-after-attempt' };
|
|
5015
|
+
return { kind: workStarted ? 'stale-after-attempt' : 'not-started-stale' };
|
|
5016
|
+
if (localAdmissionDeferred)
|
|
5017
|
+
return { kind: 'local-admission-deferred' };
|
|
5018
|
+
// A stream that answered busy or broke says nothing about the peer's data.
|
|
5019
|
+
// The policy decides whether the targets still pending keep their turn at it.
|
|
5020
|
+
let providerTurnKept = false;
|
|
5021
|
+
if (workStarted && streamOutcome !== undefined) {
|
|
5022
|
+
if (streamOutcome === 'complete') {
|
|
5023
|
+
this.vmReconcileStreamSetbackPolicy.recordServed(localCgId, peerId, this.vmReconcileRotationNow());
|
|
5024
|
+
}
|
|
5025
|
+
else {
|
|
5026
|
+
const setback = this.vmReconcileStreamSetbackPolicy.recordSetback(localCgId, peerId, streamOutcome, this.vmReconcileRotationNow());
|
|
5027
|
+
providerTurnKept = setback.keepsTurn;
|
|
5028
|
+
observeVmRecoveryTiming(() => this.log.info(ctx, `VM exact recovery stream setback for "${localCgId}" from ${peerId.slice(-8)}: `
|
|
5029
|
+
+ `kind=${streamOutcome} assets=${attempts.length} keepsTurn=${setback.keepsTurn ? 1 : 0} `
|
|
5030
|
+
+ `holdOffMs=${setback.holdOffMs}`));
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5321
5033
|
const perUalDispositions = new Map();
|
|
5322
|
-
|
|
5034
|
+
// The batch's targets are re-verified side by side, with the scan's own
|
|
5035
|
+
// bound, so their chain reads can leave in one request. Like the scan, a
|
|
5036
|
+
// target that throws stops the ones not yet started and the batch rejects
|
|
5037
|
+
// only once the ones in flight have finished: nothing here outlives the
|
|
5038
|
+
// pass. They are settled in order below: each step still sees the rotation
|
|
5039
|
+
// state its predecessor left.
|
|
5040
|
+
const revalidateBatch = () => mapWithConcurrencyDrained(attempts, DKGAgentBase.VM_RECONCILE_ORDINAL_CONCURRENCY, (attempt) => this.reconcileChainOrdinal(localCgId, onChainCgId, attempt.entry.target.ordinal, headBlock, {
|
|
5041
|
+
isTargetCurrent: isRecoveryCurrent,
|
|
5042
|
+
revalidateTarget,
|
|
5043
|
+
}));
|
|
5044
|
+
const revalidated = await (phases ? phases.measure('post-fetch', revalidateBatch) : revalidateBatch());
|
|
5045
|
+
if (!isRecoveryCurrent())
|
|
5046
|
+
return { kind: 'stale-after-attempt' };
|
|
5047
|
+
for (const [attemptIndex, attempt] of attempts.entries()) {
|
|
5323
5048
|
const batchTarget = attempt.entry.target;
|
|
5324
|
-
const outcome =
|
|
5325
|
-
isTargetCurrent: isRecoveryCurrent,
|
|
5326
|
-
revalidateTarget,
|
|
5327
|
-
deferActiveFetch: true,
|
|
5328
|
-
});
|
|
5329
|
-
if (!isRecoveryCurrent())
|
|
5330
|
-
return { kind: 'stale-after-attempt' };
|
|
5049
|
+
const outcome = revalidated[attemptIndex];
|
|
5331
5050
|
outcomes.push([batchTarget.ordinal, outcome]);
|
|
5332
5051
|
const perTargetDisposition = (outcome.status === 'reconciled' || outcome.status === 'already')
|
|
5333
5052
|
? 'found'
|
|
@@ -5336,6 +5055,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5336
5055
|
: 'incomplete';
|
|
5337
5056
|
perUalDispositions.set(batchTarget.ual, perTargetDisposition);
|
|
5338
5057
|
if (outcome.status === 'pending' && outcome.recovery) {
|
|
5058
|
+
if (!workStarted)
|
|
5059
|
+
continue;
|
|
5339
5060
|
const batchRecord = attempt.installedRecord;
|
|
5340
5061
|
if (!batchRecord || this.vmReconcileRotationClosed)
|
|
5341
5062
|
continue;
|
|
@@ -5343,10 +5064,14 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5343
5064
|
!== batchRecord)
|
|
5344
5065
|
continue;
|
|
5345
5066
|
const candidateMembershipAfter = this.vmReconcileObservedCandidatePeerIds(localCgId);
|
|
5067
|
+
if (providerTurnKept) {
|
|
5068
|
+
this.releaseVmReconcileRotationAttempt(batchTarget, peerId, batchRecord);
|
|
5069
|
+
}
|
|
5346
5070
|
if (!this.vmReconcilePeerMembershipMatches(batchRecord.candidatePeerIds, candidateMembershipAfter)) {
|
|
5347
5071
|
this.prepareVmReconcileRotationTarget(outcome.recovery, candidateMembershipAfter, this.vmReconcileRotationNow());
|
|
5348
5072
|
}
|
|
5349
|
-
else if (
|
|
5073
|
+
else if (!providerTurnKept
|
|
5074
|
+
&& this.vmReconcileRecoveryTargetMatches(batchTarget, outcome.recovery)) {
|
|
5350
5075
|
this.settleVmReconcileRotationAttempt(batchTarget, peerId, perTargetDisposition, attempt.candidatePeerIds, batchRecord, unavailablePeerIdSet);
|
|
5351
5076
|
}
|
|
5352
5077
|
}
|
|
@@ -5361,6 +5086,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5361
5086
|
attemptedOrdinals,
|
|
5362
5087
|
providerDisposition: disposition,
|
|
5363
5088
|
perUalDispositions: [...perUalDispositions],
|
|
5089
|
+
providerTurnKept,
|
|
5364
5090
|
};
|
|
5365
5091
|
}
|
|
5366
5092
|
/**
|
|
@@ -5376,6 +5102,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5376
5102
|
&& this.vmReconcileLifecycleGeneration === rotationGeneration
|
|
5377
5103
|
&& isTargetCurrent();
|
|
5378
5104
|
const ctx = createOperationContext('system');
|
|
5105
|
+
// Advisory sizing preparation (opt-in, default off). Created lazily and
|
|
5106
|
+
// owned by this host; every hint is bound to this exact recovery operation.
|
|
5107
|
+
const readUpdateContextForPreparation = this.chain.getKnowledgeAssetUpdateContext;
|
|
5108
|
+
const experimentPolicy = resolveVmRecoveryExperimentPolicy(this.config.vmRecoveryPrefetchEnabled, existingVmRecoveryPreparation(this)?.limits);
|
|
5109
|
+
const preparation = experimentPolicy.prefetchEnabled
|
|
5110
|
+
? vmRecoveryPreparationFor(this, typeof readUpdateContextForPreparation === 'function'
|
|
5111
|
+
? { readUpdateContext: (id, readOptions) => readUpdateContextForPreparation.call(this.chain, id, readOptions) }
|
|
5112
|
+
: null)
|
|
5113
|
+
: undefined;
|
|
5114
|
+
const preparationScope = {
|
|
5115
|
+
localCgId,
|
|
5116
|
+
onChainCgId,
|
|
5117
|
+
generation: rotationGeneration,
|
|
5118
|
+
...(signal ? { signal } : {}),
|
|
5119
|
+
isCurrent: isRecoveryCurrent,
|
|
5120
|
+
};
|
|
5121
|
+
const transportPreparation = preparation
|
|
5122
|
+
? new VmRecoveryTransportPreparation(preparation, preparationScope) : undefined;
|
|
5123
|
+
const timing = new VmRecoveryTimingObserver(localCgId, (message) => this.log.info(ctx, message));
|
|
5124
|
+
const phases = timing.phases;
|
|
5379
5125
|
const noRecovery = (continuationOrdinal, cooldownOnly = false) => ({
|
|
5380
5126
|
outcomes: new Map(),
|
|
5381
5127
|
attemptedOrdinals: [],
|
|
@@ -5508,7 +5254,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5508
5254
|
...(this.vmReconcileCuratorPeersByCg.get(localCgId) ?? []),
|
|
5509
5255
|
];
|
|
5510
5256
|
const curatorPageCursor = this.vmReconcileCuratorPageCursorByCg.get(localCgId);
|
|
5511
|
-
const curatorResolution = await this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
5257
|
+
const curatorResolution = await phases.measure('roster', () => this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
5512
5258
|
maxPeerIds: DKGAgentBase.VM_RECONCILE_EXACT_ROSTER_MAX,
|
|
5513
5259
|
// Once overflow is proven, expose exactly one new ordered peer per pass.
|
|
5514
5260
|
// One target can spend only one peer attempt, so advancing by more would
|
|
@@ -5523,7 +5269,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5523
5269
|
curatorIsLocal: false,
|
|
5524
5270
|
legacyTripleResolved: false,
|
|
5525
5271
|
lookupFailed: true,
|
|
5526
|
-
}));
|
|
5272
|
+
})));
|
|
5527
5273
|
if (!isRecoveryCurrent())
|
|
5528
5274
|
return staleRecovery();
|
|
5529
5275
|
const allResolvedCuratorPeerIds = [...new Set(curatorResolution.peerIds
|
|
@@ -5569,7 +5315,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5569
5315
|
let legacyPreferredPeerId;
|
|
5570
5316
|
if (resolutionSucceeded && !curatorResolution.curatorIsLocal
|
|
5571
5317
|
&& resolvedCuratorPeerIds.length === 0) {
|
|
5572
|
-
legacyPreferredPeerId = await this.resolvePreferredSyncPeerId(localCgId);
|
|
5318
|
+
legacyPreferredPeerId = await phases.measure('roster', () => this.resolvePreferredSyncPeerId(localCgId));
|
|
5573
5319
|
}
|
|
5574
5320
|
if (!isRecoveryCurrent())
|
|
5575
5321
|
return staleRecovery();
|
|
@@ -5625,6 +5371,79 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5625
5371
|
// Curator resolution may connect a missing peer, but it must not substitute
|
|
5626
5372
|
// a different ranking algorithm and invalidate an otherwise stable roster.
|
|
5627
5373
|
const orderedPeerIds = this.vmReconcileObservedCandidatePeerIds(localCgId);
|
|
5374
|
+
// Only an explicit experimental run may reorder these existing capped
|
|
5375
|
+
// transport candidates. The proof roster and every target's independent
|
|
5376
|
+
// attempted/absence credits stay unchanged. Identify does not prove data.
|
|
5377
|
+
const experimentalStreamPeerIds = new Set();
|
|
5378
|
+
// Observation only: why this pass did or did not choose the stream wire.
|
|
5379
|
+
let streamAdvertisedCount = 0;
|
|
5380
|
+
const advertisedStreamPeers = new Set();
|
|
5381
|
+
// The registered-public answer that gates the stream wire. Retry eligibility, the wire
|
|
5382
|
+
// choice and the log label all derive from this one typed observation.
|
|
5383
|
+
const passAuthority = new VmRecoveryPassAuthority(undefined, undefined, experimentPolicy.authorityRetry);
|
|
5384
|
+
const readRegisteredPublicAuthority = async () => {
|
|
5385
|
+
await phases.measure('authority', () => passAuthority.read(() => this.resolveRegisteredContextGraphAuthority(localCgId, {
|
|
5386
|
+
authorityReadMode: 'finalized-index-or-live', signal,
|
|
5387
|
+
})));
|
|
5388
|
+
if (passAuthority.isPublic && isRecoveryCurrent()) {
|
|
5389
|
+
for (const advertisedPeerId of advertisedStreamPeers)
|
|
5390
|
+
experimentalStreamPeerIds.add(advertisedPeerId);
|
|
5391
|
+
}
|
|
5392
|
+
};
|
|
5393
|
+
const prepareThisPassTargets = () => {
|
|
5394
|
+
if (!experimentPolicy.prefetchEnabled || orderedPeerIds.length === 0)
|
|
5395
|
+
return;
|
|
5396
|
+
// The planner sizes this pass's own targets as soon as a provider is chosen.
|
|
5397
|
+
// Start that early, but never ahead of the registered-authority read that gates
|
|
5398
|
+
// the wire: a miss there costs far more than the sizing it would overlap. The
|
|
5399
|
+
// first target keeps its own live read (a probe sizes its single asset itself),
|
|
5400
|
+
// so preparing never adds a read the unprepared path would not make. Pure
|
|
5401
|
+
// metadata: nothing is marked handled or attempted.
|
|
5402
|
+
transportPreparation?.preparePass(currentTargets
|
|
5403
|
+
.slice(1)
|
|
5404
|
+
.map((candidate) => ({ kaId: candidate.kaId })));
|
|
5405
|
+
};
|
|
5406
|
+
if (resolveExactBatchStreamEnabled()) {
|
|
5407
|
+
const transportInterval = phases.begin();
|
|
5408
|
+
const advertised = await Promise.all(orderedPeerIds
|
|
5409
|
+
.filter((peerId) => connectedByPeerId.has(peerId)
|
|
5410
|
+
&& this.peerCapabilityRegistry.supportsCore(peerId)
|
|
5411
|
+
&& !exactBatchStreamUnsupported(this, peerId, this.getSyncReconcilerConnectionKey(peerId), Date.now(), this.captureExperimentalExactBatchRefusalScope(localCgId)))
|
|
5412
|
+
.map(async (peerId) => {
|
|
5413
|
+
try {
|
|
5414
|
+
return (await this.getPeerProtocols(peerId)).includes(EXACT_BATCH_STREAM_PROTOCOL) ? peerId : undefined;
|
|
5415
|
+
}
|
|
5416
|
+
catch {
|
|
5417
|
+
return undefined;
|
|
5418
|
+
}
|
|
5419
|
+
}));
|
|
5420
|
+
phases.chargeSince('transport', transportInterval.startedAt, transportInterval.before);
|
|
5421
|
+
streamAdvertisedCount = advertised.filter((peerId) => peerId !== undefined).length;
|
|
5422
|
+
if (streamAdvertisedCount > 0) {
|
|
5423
|
+
for (const advertisedPeerId of advertised) {
|
|
5424
|
+
if (advertisedPeerId !== undefined)
|
|
5425
|
+
advertisedStreamPeers.add(advertisedPeerId);
|
|
5426
|
+
}
|
|
5427
|
+
await readRegisteredPublicAuthority();
|
|
5428
|
+
if (!isRecoveryCurrent())
|
|
5429
|
+
return staleRecovery();
|
|
5430
|
+
prepareThisPassTargets();
|
|
5431
|
+
if (experimentPolicy.authorityRetry.kind === 'spaced' && passAuthority.missed) {
|
|
5432
|
+
// A miss here is usually the shared local request budget, not the graph: the
|
|
5433
|
+
// read's detached resolution keeps warming the projection cache after its
|
|
5434
|
+
// deadline. Give it a short, bounded moment and ask once more before the
|
|
5435
|
+
// pass commits to the legacy singleton wire for every provider it tries.
|
|
5436
|
+
const retry = await phases.measure('authority', () => passAuthority.waitForMissRetry(signal));
|
|
5437
|
+
if (!isRecoveryCurrent())
|
|
5438
|
+
return staleRecovery();
|
|
5439
|
+
if (retry)
|
|
5440
|
+
await readRegisteredPublicAuthority();
|
|
5441
|
+
if (!isRecoveryCurrent())
|
|
5442
|
+
return staleRecovery();
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
prepareThisPassTargets();
|
|
5628
5447
|
// Curator preparation may have grown or shrunk the connected candidate
|
|
5629
5448
|
// set. Re-evaluate every target against that observed change. Any roster
|
|
5630
5449
|
// change breaks backoff and starts a fresh proof cycle.
|
|
@@ -5690,6 +5509,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5690
5509
|
const providerPolicy = new VmRecoveryProviderPolicy();
|
|
5691
5510
|
const handledBatchOrdinals = new Set();
|
|
5692
5511
|
let recoveryWorkRan = false;
|
|
5512
|
+
let localAdmissionDeferred = false;
|
|
5693
5513
|
for (let eligibleIndex = 0; eligibleIndex < eligible.length; eligibleIndex += 1) {
|
|
5694
5514
|
if (!isRecoveryCurrent())
|
|
5695
5515
|
break;
|
|
@@ -5697,6 +5517,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5697
5517
|
const { target } = entry;
|
|
5698
5518
|
if (handledBatchOrdinals.has(target.ordinal))
|
|
5699
5519
|
continue;
|
|
5520
|
+
const batchTiming = timing.beginBatch();
|
|
5700
5521
|
const record = entry.prepared.record;
|
|
5701
5522
|
const installedRecord = record
|
|
5702
5523
|
&& this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(target)) === record
|
|
@@ -5711,21 +5532,27 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5711
5532
|
// uncredited, but consume this target's turn so another missing KA gets
|
|
5712
5533
|
// the next physical peer slot in the same bounded pass.
|
|
5713
5534
|
let peerId;
|
|
5714
|
-
|
|
5535
|
+
if (advertisedStreamPeers.size > 0
|
|
5536
|
+
&& passAuthority.retryDue()) {
|
|
5537
|
+
// One pass can try several providers over minutes, and every wire choice below
|
|
5538
|
+
// follows this single registered-public observation. A transient miss at pass
|
|
5539
|
+
// start (the read shares a saturated local request budget; its detached
|
|
5540
|
+
// resolution keeps warming the projection cache) would otherwise pin every
|
|
5541
|
+
// later provider of the pass to the legacy singleton wire. Read it again, at
|
|
5542
|
+
// most once per interval, before choosing the next provider.
|
|
5543
|
+
await readRegisteredPublicAuthority();
|
|
5544
|
+
if (!isRecoveryCurrent())
|
|
5545
|
+
return staleRecovery();
|
|
5546
|
+
}
|
|
5547
|
+
const candidatePeerId = this.selectVmReconcileExactCandidate(installedRecord, orderedPeerIds, providerPolicy, { localCgId, onChainCgId: expectedOnChainCgId, experimentalStreamPeerIds });
|
|
5548
|
+
// One policy capture owns every later transition and token fence.
|
|
5549
|
+
const preferenceAttempt = this.vmReconcilePublicCoreTransportPreferencePolicy.capture(localCgId, expectedOnChainCgId, orderedPeerIds);
|
|
5715
5550
|
if (candidatePeerId) {
|
|
5716
|
-
attemptedOrdinals.add(target.ordinal);
|
|
5717
|
-
if (installedRecord) {
|
|
5718
|
-
installedRecord.lastAttemptedPeerId = candidatePeerId;
|
|
5719
|
-
installedRecord.attemptedPeerIds.add(candidatePeerId);
|
|
5720
|
-
installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
|
|
5721
|
-
+ DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
|
|
5722
|
-
this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(target), installedRecord);
|
|
5723
|
-
}
|
|
5724
5551
|
let connectedPeer = connectedByPeerId.get(candidatePeerId);
|
|
5725
5552
|
if (!connectedPeer) {
|
|
5726
|
-
await this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
|
|
5553
|
+
await phases.measure('peer-ready', () => this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
|
|
5727
5554
|
this.log.info(ctx, `VM exact fetch could not connect candidate peer ${candidatePeerId.slice(-8)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5728
|
-
});
|
|
5555
|
+
}));
|
|
5729
5556
|
if (!isRecoveryCurrent())
|
|
5730
5557
|
return staleRecovery();
|
|
5731
5558
|
const connection = this.node.libp2p.getConnections()
|
|
@@ -5736,7 +5563,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5736
5563
|
}
|
|
5737
5564
|
recoveryWorkRan = true;
|
|
5738
5565
|
const protocolReady = connectedPeer
|
|
5739
|
-
? await this.waitForSyncProtocol(connectedPeer, signal)
|
|
5566
|
+
? await phases.measure('peer-ready', () => this.waitForSyncProtocol(connectedPeer, signal))
|
|
5740
5567
|
: false;
|
|
5741
5568
|
if (!isRecoveryCurrent())
|
|
5742
5569
|
return staleRecovery();
|
|
@@ -5747,7 +5574,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5747
5574
|
// Network boundary: a merely-connected peer is not necessarily
|
|
5748
5575
|
// admitted to this DKG network. Never send an authenticated exact
|
|
5749
5576
|
// request to an unverified or rejected peer.
|
|
5750
|
-
const peerAdmitted = await this.ensurePeerAdmittedForRecovery(candidatePeerId, ctx, 'VM exact fetch', signal);
|
|
5577
|
+
const peerAdmitted = await phases.measure('peer-ready', () => this.ensurePeerAdmittedForRecovery(candidatePeerId, ctx, 'VM exact fetch', signal));
|
|
5751
5578
|
if (!isRecoveryCurrent())
|
|
5752
5579
|
return staleRecovery();
|
|
5753
5580
|
if (!peerAdmitted)
|
|
@@ -5757,25 +5584,41 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5757
5584
|
}
|
|
5758
5585
|
}
|
|
5759
5586
|
if (!peerId) {
|
|
5587
|
+
if (candidatePeerId) {
|
|
5588
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy.revoke(preferenceAttempt, candidatePeerId);
|
|
5589
|
+
}
|
|
5590
|
+
// A failed connection/protocol/network-admission probe still consumes
|
|
5591
|
+
// its bounded provider turn. A ready peer is marked only by the exact
|
|
5592
|
+
// executor after local sync capacity admits its physical work.
|
|
5593
|
+
if (candidatePeerId) {
|
|
5594
|
+
attemptedOrdinals.add(target.ordinal);
|
|
5595
|
+
if (installedRecord) {
|
|
5596
|
+
installedRecord.lastAttemptedPeerId = candidatePeerId;
|
|
5597
|
+
installedRecord.attemptedPeerIds.add(candidatePeerId);
|
|
5598
|
+
installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
|
|
5599
|
+
+ DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
|
|
5600
|
+
this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(target), installedRecord);
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5760
5603
|
if (installedRecord) {
|
|
5761
5604
|
this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
|
|
5762
5605
|
}
|
|
5763
5606
|
continue;
|
|
5764
5607
|
}
|
|
5608
|
+
const carriedHolder = this.vmReconcilePublicCoreTransportPreferencePolicy.canReuse(preferenceAttempt, peerId, candidatePeerIds);
|
|
5609
|
+
if (carriedHolder)
|
|
5610
|
+
providerPolicy.seedProvenHolder(peerId);
|
|
5765
5611
|
const providerAttempt = providerPolicy.beginAttempt(peerId);
|
|
5766
5612
|
if (!providerAttempt)
|
|
5767
5613
|
continue;
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
// exact-sync protocol's ten-UAL cap remains the hard upper bound.
|
|
5777
|
-
if (providerAttempt.kind === 'proven-holder-reuse') {
|
|
5778
|
-
const compatible = [];
|
|
5614
|
+
const admittedConnectionKey = this.getSyncReconcilerConnectionKey(peerId);
|
|
5615
|
+
// Choose the admitted singleton probe or rotation-compatible holder
|
|
5616
|
+
// prefix before sizing; one pipeline owns its selection and wire mode.
|
|
5617
|
+
const candidateAttempts = [];
|
|
5618
|
+
if (providerAttempt.kind === 'probe') {
|
|
5619
|
+
candidateAttempts.push({ entry, installedRecord, candidatePeerIds });
|
|
5620
|
+
}
|
|
5621
|
+
else {
|
|
5779
5622
|
for (let candidateIndex = eligibleIndex; candidateIndex < eligible.length; candidateIndex += 1) {
|
|
5780
5623
|
const candidateEntry = eligible[candidateIndex];
|
|
5781
5624
|
const candidateTarget = candidateEntry.target;
|
|
@@ -5786,8 +5629,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5786
5629
|
&& this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(candidateTarget)) === candidateRecord
|
|
5787
5630
|
? candidateRecord
|
|
5788
5631
|
: undefined;
|
|
5789
|
-
// The current target
|
|
5790
|
-
//
|
|
5632
|
+
// The current target already passed connection/network admission.
|
|
5633
|
+
// Later candidates must still prove
|
|
5791
5634
|
// this peer remains uncredited in their independent rotation record.
|
|
5792
5635
|
const peerEligible = candidateIndex === eligibleIndex
|
|
5793
5636
|
|| (candidateInstalledRecord
|
|
@@ -5795,7 +5638,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5795
5638
|
: orderedPeerIds.includes(peerId));
|
|
5796
5639
|
if (!peerEligible)
|
|
5797
5640
|
break;
|
|
5798
|
-
|
|
5641
|
+
candidateAttempts.push({
|
|
5799
5642
|
entry: candidateEntry,
|
|
5800
5643
|
installedRecord: candidateInstalledRecord,
|
|
5801
5644
|
candidatePeerIds: candidateInstalledRecord
|
|
@@ -5803,53 +5646,85 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5803
5646
|
: orderedPeerIds,
|
|
5804
5647
|
});
|
|
5805
5648
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
attempt,
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5649
|
+
}
|
|
5650
|
+
const streamEligibleProvider = passAuthority.isPublic && experimentalStreamPeerIds.has(peerId)
|
|
5651
|
+
&& !exactBatchStreamUnsupported(this, peerId, admittedConnectionKey, Date.now(), this.captureExperimentalExactBatchRefusalScope(localCgId));
|
|
5652
|
+
let transportPlan;
|
|
5653
|
+
transportPlan = await phases.measure('sizing', () => planVmRecoveryTransport({
|
|
5654
|
+
candidates: candidateAttempts.map(attempt => ({
|
|
5655
|
+
attempt, kaId: attempt.entry.target.kaId, assetUal: attempt.entry.target.ual,
|
|
5656
|
+
})),
|
|
5657
|
+
providerAttemptKind: providerAttempt.kind, onChainCgId,
|
|
5658
|
+
streamEligible: streamEligibleProvider, registeredPublicAccess: passAuthority.isPublic,
|
|
5659
|
+
legacyAttemptTimeoutMs: this.vmReconcileTransportBudgetPolicy.timeoutFor({
|
|
5660
|
+
target, peerId, providerAttemptKind: providerAttempt.kind,
|
|
5661
|
+
registeredPublicAccess: passAuthority.isPublic,
|
|
5662
|
+
competingStreamAvailable: experimentalStreamPeerIds.size > 0
|
|
5663
|
+
&& !experimentalStreamPeerIds.has(peerId),
|
|
5664
|
+
streamEligible: streamEligibleProvider,
|
|
5665
|
+
}),
|
|
5666
|
+
signal, isCurrent: isRecoveryCurrent,
|
|
5667
|
+
observeSizing: batchTiming.observeSizing,
|
|
5668
|
+
// Start reads in candidate order with a small bound so each read's
|
|
5669
|
+
// deadline measures its own round trip, not its wait in the local
|
|
5670
|
+
// RPC governor behind earlier candidates.
|
|
5671
|
+
...experimentPolicy.sizing,
|
|
5672
|
+
...(experimentPolicy.prefetchEnabled ? {
|
|
5673
|
+
probeRemainder: eligible.slice(eligibleIndex + 1)
|
|
5674
|
+
.filter(candidate => !handledBatchOrdinals.has(candidate.target.ordinal))
|
|
5675
|
+
.map(candidate => ({ kaId: candidate.target.kaId })),
|
|
5676
|
+
} : {}),
|
|
5677
|
+
}, {
|
|
5678
|
+
createSizingReader: () => {
|
|
5679
|
+
const readContext = this.chain.getKnowledgeAssetUpdateContext;
|
|
5680
|
+
return typeof readContext === 'function'
|
|
5681
|
+
? { readUpdateContext: (kaId, readOptions) => readContext.call(this.chain, kaId, readOptions) }
|
|
5682
|
+
: null;
|
|
5683
|
+
},
|
|
5684
|
+
resolvePublicAccess: async (contextGraphId) => (await withRpcUsageSite(CG_AUTH_RPC_SITES.vmSizing,
|
|
5685
|
+
// This bounded observation controls soft sizing only. Canonical
|
|
5686
|
+
// root/version/binding authority is still checked per asset.
|
|
5687
|
+
() => this.readLiveOnChainAccessPolicy(contextGraphId.toString(), ctx, { freshness: 'bounded' }))) === 0,
|
|
5688
|
+
preparation: transportPreparation,
|
|
5689
|
+
}));
|
|
5690
|
+
if (!isRecoveryCurrent())
|
|
5691
|
+
return providerAttempt.kind === 'probe' ? staleRecovery() : noRecovery();
|
|
5692
|
+
const { attempts: batchAttempts, transportMode: exactRecoveryTransportMode, publicAccessEvidence: publicRecoveryAccessVerified, packing, legacyAttemptTimeoutMs, } = transportPlan;
|
|
5693
|
+
if (batchAttempts.length === 0) {
|
|
5694
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy.revoke(preferenceAttempt, peerId);
|
|
5695
|
+
this.log.warn(ctx, `VM exact recovery selector for "${localCgId}" exceeds the executor cap; `
|
|
5696
|
+
+ `ordinal=${target.ordinal} selectorCap=${VM_EXACT_MICROBATCH_LIMITS.maxSelectorBytes}`);
|
|
5697
|
+
if (installedRecord) {
|
|
5698
|
+
this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
|
|
5846
5699
|
}
|
|
5847
|
-
|
|
5700
|
+
providerPolicy.finishAttempt(providerAttempt, 'incomplete', new Map([[target.ual, 'incomplete']]));
|
|
5701
|
+
continue;
|
|
5702
|
+
}
|
|
5703
|
+
observeVmRecoveryTiming(() => this.log.info(ctx, `VM exact recovery transport for "${localCgId}" from ${peerId.slice(-8)}: kind=${providerAttempt.kind} `
|
|
5704
|
+
+ `transport=${exactRecoveryTransportMode} assets=${batchAttempts.length} `
|
|
5705
|
+
+ `streamAdvertised=${streamAdvertisedCount} streamPeers=${experimentalStreamPeerIds.size} `
|
|
5706
|
+
+ `registeredAuthority=${passAuthority.label}`));
|
|
5707
|
+
if (packing !== undefined) {
|
|
5848
5708
|
this.log.info(ctx, `VM exact recovery plan for "${localCgId}" from ${peerId.slice(-8)}: `
|
|
5849
|
-
+ `assets=${batchAttempts.length} estimatedBytes=${
|
|
5850
|
-
+ `estimatedLeaves=${
|
|
5851
|
-
+ `completeFootprints=${
|
|
5709
|
+
+ `assets=${batchAttempts.length} estimatedBytes=${packing.estimatedBytes} `
|
|
5710
|
+
+ `estimatedLeaves=${packing.estimatedLeaves} `
|
|
5711
|
+
+ `completeFootprints=${packing.completeFootprints} transport=${exactRecoveryTransportMode}`);
|
|
5712
|
+
}
|
|
5713
|
+
if (carriedHolder && !this.vmReconcilePublicCoreTransportPreferencePolicy.revalidateReuse(preferenceAttempt, peerId, admittedConnectionKey)) {
|
|
5714
|
+
// Sizing outlived the captured holder proof; the next slice must probe.
|
|
5715
|
+
if (activeFetchCooldownOwner !== undefined) {
|
|
5716
|
+
this.clearVmReconcileActiveFetchCooldown(localCgId, activeFetchCooldownOwner);
|
|
5717
|
+
}
|
|
5718
|
+
return {
|
|
5719
|
+
outcomes, attemptedOrdinals: [...attemptedOrdinals],
|
|
5720
|
+
continuationOrdinal: target.ordinal, hasImmediateRecoveryWork: true, cooldownOnly: false,
|
|
5721
|
+
};
|
|
5852
5722
|
}
|
|
5723
|
+
// Only while preparation is on: the exchange may rely on this pass's own fresh positive
|
|
5724
|
+
// answer instead of reading it again. The handle dies with the exchange.
|
|
5725
|
+
const registeredPublicEvidence = experimentPolicy.sharePassAuthorityEvidence
|
|
5726
|
+
? passAuthority.evidence({ contextGraphId: localCgId, signal, isCurrent: isRecoveryCurrent })
|
|
5727
|
+
: undefined;
|
|
5853
5728
|
const execution = await this.executeVmRecoveryBatch({
|
|
5854
5729
|
localCgId,
|
|
5855
5730
|
onChainCgId,
|
|
@@ -5861,16 +5736,52 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5861
5736
|
isRecoveryCurrent,
|
|
5862
5737
|
revalidateTarget,
|
|
5863
5738
|
ctx,
|
|
5864
|
-
|
|
5865
|
-
|
|
5739
|
+
exactRecoveryTransportMode,
|
|
5740
|
+
...(legacyAttemptTimeoutMs === undefined ? {} : { legacyAttemptTimeoutMs }),
|
|
5741
|
+
...(registeredPublicEvidence ? { registeredPublicEvidence } : {}),
|
|
5742
|
+
phases,
|
|
5743
|
+
}).finally(() => registeredPublicEvidence?.revoke());
|
|
5744
|
+
if (execution.kind === 'local-admission-deferred') {
|
|
5745
|
+
// Capacity is node-local, so trying other providers would burn their
|
|
5746
|
+
// turns under the same refusal. Retain all peer evidence and yield the
|
|
5747
|
+
// whole slice for the scheduler's bounded local retry.
|
|
5748
|
+
localAdmissionDeferred = true;
|
|
5749
|
+
break;
|
|
5750
|
+
}
|
|
5751
|
+
if (execution.kind !== 'completed' || !isRecoveryCurrent())
|
|
5866
5752
|
return staleRecovery();
|
|
5753
|
+
batchTiming.complete({ peerId, assets: batchAttempts.length,
|
|
5754
|
+
candidates: eligible.length - eligibleIndex, kind: providerAttempt.kind,
|
|
5755
|
+
transport: exactRecoveryTransportMode, streamAdvertised: streamAdvertisedCount,
|
|
5756
|
+
streamPeers: experimentalStreamPeerIds.size, registeredAuthority: passAuthority.label });
|
|
5867
5757
|
for (const [ordinal, outcome] of execution.outcomes)
|
|
5868
5758
|
outcomes.set(ordinal, outcome);
|
|
5869
5759
|
for (const ordinal of execution.handledOrdinals)
|
|
5870
5760
|
handledBatchOrdinals.add(ordinal);
|
|
5871
5761
|
for (const ordinal of execution.attemptedOrdinals)
|
|
5872
5762
|
attemptedOrdinals.add(ordinal);
|
|
5873
|
-
|
|
5763
|
+
if (execution.providerTurnKept) {
|
|
5764
|
+
// Busy or a broken stream: the peer is not spent for this slice. It is
|
|
5765
|
+
// asked again, with a fresh probe, once its hold-off has passed.
|
|
5766
|
+
providerPolicy.releaseAttempt(providerAttempt);
|
|
5767
|
+
}
|
|
5768
|
+
else {
|
|
5769
|
+
providerPolicy.finishAttempt(providerAttempt, execution.providerDisposition, new Map(execution.perUalDispositions));
|
|
5770
|
+
}
|
|
5771
|
+
const completelyVerified = execution.providerDisposition === 'found'
|
|
5772
|
+
&& execution.perUalDispositions.length === batchAttempts.length
|
|
5773
|
+
&& execution.perUalDispositions.every(([, disposition]) => disposition === 'found');
|
|
5774
|
+
const preferenceSettlement = this.vmReconcilePublicCoreTransportPreferencePolicy.settle(preferenceAttempt, {
|
|
5775
|
+
peerId, connectionKey: admittedConnectionKey, completelyVerified,
|
|
5776
|
+
publicAccessVerified: publicRecoveryAccessVerified,
|
|
5777
|
+
reusedHolder: providerAttempt.kind === 'proven-holder-reuse', carriedHolder,
|
|
5778
|
+
});
|
|
5779
|
+
if (preferenceSettlement.yield) {
|
|
5780
|
+
this.log.info(ctx, `VM exact recovery yield: reason=verified-core-turn assets=${batchAttempts.length} `
|
|
5781
|
+
+ `publicSizingAccess=${publicRecoveryAccessVerified === undefined ? 'unknown' : publicRecoveryAccessVerified} `
|
|
5782
|
+
+ `holderCreditPresent=${preferenceSettlement.holderCreditPresent}`);
|
|
5783
|
+
break;
|
|
5784
|
+
}
|
|
5874
5785
|
}
|
|
5875
5786
|
const eligibleOrdinals = new Set(eligible.map(({ target }) => target.ordinal));
|
|
5876
5787
|
const unattemptedContinuationOrdinal = currentTargets.find((target) => eligibleOrdinals.has(target.ordinal) && !attemptedOrdinals.has(target.ordinal))?.ordinal;
|
|
@@ -5901,6 +5812,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5901
5812
|
// negative backoff applies exactly as before.
|
|
5902
5813
|
if (!isRecoveryCurrent())
|
|
5903
5814
|
return staleRecovery();
|
|
5815
|
+
if (localAdmissionDeferred) {
|
|
5816
|
+
if (activeFetchCooldownOwner !== undefined) {
|
|
5817
|
+
this.clearVmReconcileActiveFetchCooldown(localCgId, activeFetchCooldownOwner);
|
|
5818
|
+
}
|
|
5819
|
+
return {
|
|
5820
|
+
outcomes,
|
|
5821
|
+
attemptedOrdinals: [...attemptedOrdinals],
|
|
5822
|
+
continuationOrdinal: unattemptedContinuationOrdinal,
|
|
5823
|
+
hasImmediateRecoveryWork: false,
|
|
5824
|
+
cooldownOnly: false,
|
|
5825
|
+
localAdmissionDeferred: true,
|
|
5826
|
+
};
|
|
5827
|
+
}
|
|
5904
5828
|
const recoveredAny = [...outcomes.values()]
|
|
5905
5829
|
.some((outcome) => outcome.status === 'reconciled' || outcome.status === 'already');
|
|
5906
5830
|
if (!recoveryWorkRan
|
|
@@ -5917,6 +5841,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5917
5841
|
this.installVmReconcileActiveFetchCooldown(localCgId, Date.now());
|
|
5918
5842
|
}
|
|
5919
5843
|
}
|
|
5844
|
+
timing.finish(eligible.length, preparation ? () => preparation.stats() : undefined);
|
|
5920
5845
|
return {
|
|
5921
5846
|
outcomes,
|
|
5922
5847
|
attemptedOrdinals: [...attemptedOrdinals],
|
|
@@ -5934,7 +5859,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5934
5859
|
* latest on-chain merkle root + publisher, build the UAL, and ask the
|
|
5935
5860
|
* finalization handler to promote the matching local SWM snapshot to VM
|
|
5936
5861
|
* (verifying the CG binding from chain). When no local SWM matches, run an
|
|
5937
|
-
*
|
|
5862
|
+
* queue the exact target for bounded batch recovery. A successful result is
|
|
5938
5863
|
* validated against one coherent pinned version snapshot before it earns a
|
|
5939
5864
|
* same-finalized-block shortcut; a new block always reads again. `headBlock`
|
|
5940
5865
|
* remains the independent cursor observation for the reorg-depth gate. See
|
|
@@ -5942,7 +5867,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5942
5867
|
*
|
|
5943
5868
|
* Local first: before any root, publisher or version read, the handler
|
|
5944
5869
|
* classifies what the store holds for the KA. A confirmed VM copy with
|
|
5945
|
-
* nothing else to promote settles as `already`, and
|
|
5870
|
+
* nothing else to promote settles as `already`, and an
|
|
5946
5871
|
* asset held nowhere locally goes straight to the exact-recovery queue, both
|
|
5947
5872
|
* without chain reads. Only local state whose outcome depends on the root
|
|
5948
5873
|
* takes the chain-backed path below.
|
|
@@ -6016,11 +5941,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6016
5941
|
});
|
|
6017
5942
|
return { status: 'already', blockNumber: headBlock ?? 0 };
|
|
6018
5943
|
}
|
|
6019
|
-
if (localCandidate.kind === 'none'
|
|
5944
|
+
if (localCandidate.kind === 'none') {
|
|
6020
5945
|
// Nothing local can match any root, so the chain-backed path could only
|
|
6021
5946
|
// answer `no-swm`. Queue the same exact-recovery target it would; the
|
|
6022
5947
|
// recovery batch keeps its rotation backoff, cooldown and peer gating.
|
|
6023
|
-
// The inline mode keys its negative cache by the root and keeps reading it.
|
|
6024
5948
|
if (!(await targetMayMaterialize()))
|
|
6025
5949
|
return { status: 'skip' };
|
|
6026
5950
|
this.emitReplication({
|
|
@@ -6082,18 +6006,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6082
6006
|
}
|
|
6083
6007
|
merkleRoot = await this.chain.getLatestMerkleRoot(kaId);
|
|
6084
6008
|
cacheKey = this.vmReconcileCacheKey(localCgId, ual, merkleRoot);
|
|
6085
|
-
if (!options.deferActiveFetch && await this.shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId)) {
|
|
6086
|
-
this.emitReplication({
|
|
6087
|
-
contextGraphId: localCgId,
|
|
6088
|
-
onChainCgId: onChainCgId.toString(),
|
|
6089
|
-
action: 'defer',
|
|
6090
|
-
ordinal,
|
|
6091
|
-
kaId: kaId.toString(),
|
|
6092
|
-
ual,
|
|
6093
|
-
detail: 'negative-cache',
|
|
6094
|
-
});
|
|
6095
|
-
return { status: 'pending' };
|
|
6096
|
-
}
|
|
6097
6009
|
publisherAddress = (this.chain.getLatestMerkleRootPublisher
|
|
6098
6010
|
? await this.chain.getLatestMerkleRootPublisher(kaId)
|
|
6099
6011
|
: '') ?? '';
|
|
@@ -6140,119 +6052,33 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6140
6052
|
return undefined;
|
|
6141
6053
|
}
|
|
6142
6054
|
};
|
|
6143
|
-
let swmState;
|
|
6144
|
-
let activeFetchRan = false;
|
|
6145
|
-
let activeFetchHadUsableResponse = false;
|
|
6146
|
-
const cleanMissPeerIds = new Set();
|
|
6147
6055
|
if (!(await targetMayMaterialize()))
|
|
6148
6056
|
return { status: 'skip' };
|
|
6149
|
-
|
|
6057
|
+
const outcome = await reconcileKnowledgeAsset();
|
|
6150
6058
|
if (outcome === undefined)
|
|
6151
6059
|
return { status: 'pending' };
|
|
6152
6060
|
if (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending') {
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6061
|
+
this.emitReplication({
|
|
6062
|
+
contextGraphId: localCgId,
|
|
6063
|
+
onChainCgId: onChainCgId.toString(),
|
|
6064
|
+
action: 'defer',
|
|
6065
|
+
ordinal,
|
|
6066
|
+
kaId: kaId.toString(),
|
|
6067
|
+
ual,
|
|
6068
|
+
detail: outcome,
|
|
6069
|
+
});
|
|
6070
|
+
return {
|
|
6071
|
+
status: 'pending',
|
|
6072
|
+
recovery: {
|
|
6073
|
+
localCgId,
|
|
6156
6074
|
onChainCgId: onChainCgId.toString(),
|
|
6157
|
-
action: 'defer',
|
|
6158
6075
|
ordinal,
|
|
6159
|
-
kaId: kaId.toString(),
|
|
6160
6076
|
ual,
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
localCgId,
|
|
6167
|
-
onChainCgId: onChainCgId.toString(),
|
|
6168
|
-
ordinal,
|
|
6169
|
-
ual,
|
|
6170
|
-
merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
|
|
6171
|
-
kaId: kaId.toString(),
|
|
6172
|
-
reason: outcome,
|
|
6173
|
-
},
|
|
6174
|
-
};
|
|
6175
|
-
}
|
|
6176
|
-
if (outcome === 'no-swm') {
|
|
6177
|
-
swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
|
|
6178
|
-
}
|
|
6179
|
-
// Active fetch: pull the missing snapshot core-first (selectCatchupPeers
|
|
6180
|
-
// already prioritises known cores + the preferred sync peer), then retry.
|
|
6181
|
-
// Metadata-pending exact VM content needs the same recovery: a durable
|
|
6182
|
-
// sync can supply the missing provenance-bearing assertion metadata even
|
|
6183
|
-
// when no content triples need to move.
|
|
6184
|
-
const batchAllowsFetch = options.acquireActiveFetchPermit?.() ?? true;
|
|
6185
|
-
const cooldownAllowsFetch = batchAllowsFetch
|
|
6186
|
-
&& this.shouldRunVmReconcileActiveFetch(localCgId);
|
|
6187
|
-
if (batchAllowsFetch && cooldownAllowsFetch) {
|
|
6188
|
-
activeFetchRan = true;
|
|
6189
|
-
this.emitReplication({
|
|
6190
|
-
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
6191
|
-
action: 'fetch', ordinal, kaId: kaId.toString(), ual,
|
|
6192
|
-
});
|
|
6193
|
-
let maxAttempts = 1;
|
|
6194
|
-
const fixedMaxAttempts = options.maxPeerAttempts === undefined
|
|
6195
|
-
? undefined
|
|
6196
|
-
: Math.max(1, Math.floor(options.maxPeerAttempts));
|
|
6197
|
-
if (fixedMaxAttempts !== undefined)
|
|
6198
|
-
maxAttempts = fixedMaxAttempts;
|
|
6199
|
-
for (let attempt = 0; attempt < maxAttempts
|
|
6200
|
-
&& (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending'); attempt += 1) {
|
|
6201
|
-
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
6202
|
-
break;
|
|
6203
|
-
}
|
|
6204
|
-
try {
|
|
6205
|
-
const recovery = await this.syncVmRecoveryFromConnectedPeers(localCgId, {
|
|
6206
|
-
includeSharedMemory: true,
|
|
6207
|
-
maxPeers: 1,
|
|
6208
|
-
peerRotationKey: localCgId,
|
|
6209
|
-
// This is recovery, not routine background catch-up. Without the
|
|
6210
|
-
// override it would enter through the default-background path and
|
|
6211
|
-
// be reported as `catchup-background`, merging repair traffic into
|
|
6212
|
-
// the background lane. Attribution only — mode, priority, peer
|
|
6213
|
-
// selection and the coalescing key are all unchanged.
|
|
6214
|
-
sourceOverride: 'vm-recovery',
|
|
6215
|
-
});
|
|
6216
|
-
const fetchResult = recovery.catchup;
|
|
6217
|
-
if (fixedMaxAttempts === undefined) {
|
|
6218
|
-
maxAttempts = Math.max(maxAttempts, fetchResult.totalPeers ?? fetchResult.connectedPeers ?? 0, this.vmReconcileConnectedPeerCount());
|
|
6219
|
-
}
|
|
6220
|
-
if ((fetchResult.peersTried ?? 0) === 0 && (fetchResult.syncCapablePeers ?? 0) === 0) {
|
|
6221
|
-
continue;
|
|
6222
|
-
}
|
|
6223
|
-
if (!this.vmReconcileActiveFetchHadUsableResponse(fetchResult)) {
|
|
6224
|
-
continue;
|
|
6225
|
-
}
|
|
6226
|
-
activeFetchHadUsableResponse = true;
|
|
6227
|
-
for (const peerId of recovery.cleanMissPeerIds) {
|
|
6228
|
-
cleanMissPeerIds.add(peerId);
|
|
6229
|
-
}
|
|
6230
|
-
}
|
|
6231
|
-
catch (err) {
|
|
6232
|
-
this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6233
|
-
if (fixedMaxAttempts === undefined) {
|
|
6234
|
-
maxAttempts = Math.max(maxAttempts, this.vmReconcileConnectedPeerCount());
|
|
6235
|
-
}
|
|
6236
|
-
continue;
|
|
6237
|
-
}
|
|
6238
|
-
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
6239
|
-
break;
|
|
6240
|
-
}
|
|
6241
|
-
if (!(await targetMayMaterialize()))
|
|
6242
|
-
return { status: 'skip' };
|
|
6243
|
-
outcome = await reconcileKnowledgeAsset();
|
|
6244
|
-
if (outcome === undefined)
|
|
6245
|
-
return { status: 'pending' };
|
|
6246
|
-
}
|
|
6247
|
-
if (outcome === 'no-swm') {
|
|
6248
|
-
swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
|
|
6249
|
-
swmState.cleanMissPeerIds = createVmReconcileCleanMissPeerIds(swmState.peerTopology, [...cleanMissPeerIds]);
|
|
6250
|
-
}
|
|
6251
|
-
}
|
|
6252
|
-
else {
|
|
6253
|
-
const reason = batchAllowsFetch ? 'per-CG cooldown' : 'per-batch fetch budget';
|
|
6254
|
-
this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) skipped by ${reason}`);
|
|
6255
|
-
}
|
|
6077
|
+
merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
|
|
6078
|
+
kaId: kaId.toString(),
|
|
6079
|
+
reason: outcome,
|
|
6080
|
+
},
|
|
6081
|
+
};
|
|
6256
6082
|
}
|
|
6257
6083
|
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
6258
6084
|
return { status: 'skip' };
|
|
@@ -6268,7 +6094,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6268
6094
|
case 'promoted':
|
|
6269
6095
|
this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
|
|
6270
6096
|
this.pruneVmReconcileCacheKeySiblings(cacheKey);
|
|
6271
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
6272
6097
|
this.recentReconciledUals.add(cacheKey);
|
|
6273
6098
|
await this.confirmAndRememberVmReconcileFinalizedSlot(localCgId, onChainCgId, ordinal, rememberSlotBlock, kaId, merkleRoot, publisherAddress);
|
|
6274
6099
|
this.emitReplication({
|
|
@@ -6285,7 +6110,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6285
6110
|
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
6286
6111
|
action: 'already', ordinal, kaId: kaId.toString(), ual,
|
|
6287
6112
|
});
|
|
6288
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
6289
6113
|
return { status: 'already', blockNumber: completionBlock };
|
|
6290
6114
|
case 'stale-target':
|
|
6291
6115
|
this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
|
|
@@ -6296,20 +6120,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6296
6120
|
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
6297
6121
|
action: 'already', ordinal, kaId: kaId.toString(), ual,
|
|
6298
6122
|
});
|
|
6299
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
6300
6123
|
return { status: 'already', blockNumber: completionBlock };
|
|
6301
|
-
case 'no-swm':
|
|
6302
|
-
if (activeFetchRan && !activeFetchHadUsableResponse) {
|
|
6303
|
-
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
6304
|
-
}
|
|
6305
|
-
else {
|
|
6306
|
-
this.recordVmReconcileNegativeCache(cacheKey, localCgId, swmState ?? await this.collectVmReconcileSwmCandidateState(localCgId));
|
|
6307
|
-
}
|
|
6308
|
-
this.emitReplication({
|
|
6309
|
-
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
6310
|
-
action: 'defer', ordinal, kaId: kaId.toString(), ual, detail: activeFetchRan && !activeFetchHadUsableResponse ? 'network-unavailable' : outcome,
|
|
6311
|
-
});
|
|
6312
|
-
return { status: 'pending' };
|
|
6313
6124
|
case 'receipt-revalidation-pending':
|
|
6314
6125
|
case 'unverified':
|
|
6315
6126
|
default:
|
|
@@ -6529,7 +6340,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6529
6340
|
// on nodes that have persisted the CG's `allowedPeers`; host-only
|
|
6530
6341
|
// cores never see it.
|
|
6531
6342
|
try {
|
|
6532
|
-
const allowedPeers = await this.
|
|
6343
|
+
const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
|
|
6533
6344
|
if (allowedPeers !== null) {
|
|
6534
6345
|
anyAuthoritySourceFound = true;
|
|
6535
6346
|
if (allowedPeers.includes(fromPeerId)) {
|
|
@@ -6859,6 +6670,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6859
6670
|
* two would race / duplicate every apply (Codex PR #610 R4).
|
|
6860
6671
|
*/
|
|
6861
6672
|
async enableSwmHostModeFor(contextGraphId) {
|
|
6673
|
+
const session = this.gossipSession;
|
|
6674
|
+
if (!session.active)
|
|
6675
|
+
return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
|
|
6862
6676
|
if (!this.swmHostModeStore) {
|
|
6863
6677
|
return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
|
|
6864
6678
|
}
|
|
@@ -6882,6 +6696,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6882
6696
|
}
|
|
6883
6697
|
const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
|
|
6884
6698
|
const curated = await this.isCuratedForHostMode(contextGraphId);
|
|
6699
|
+
if (!session.active || this.gossipSession !== session) {
|
|
6700
|
+
return { subscribed: false, alreadySubscribed: false, hostingEnabled: true };
|
|
6701
|
+
}
|
|
6885
6702
|
if (this.swmHostModeSubscribed.has(hostKey)) {
|
|
6886
6703
|
// Idempotent re-entry: even when the subscription is already
|
|
6887
6704
|
// active, re-probe registration state. This handles the
|