@origintrail-official/dkg-agent 10.0.19 → 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/confirmed-graph-scoped-vm-resolver.d.ts +7 -1
- package/dist/confirmed-graph-scoped-vm-resolver.d.ts.map +1 -1
- package/dist/confirmed-graph-scoped-vm-resolver.js +5 -3
- package/dist/confirmed-graph-scoped-vm-resolver.js.map +1 -1
- package/dist/context-graph-authority-dependency.d.ts +11 -0
- package/dist/context-graph-authority-dependency.d.ts.map +1 -0
- package/dist/context-graph-authority-dependency.js +50 -0
- package/dist/context-graph-authority-dependency.js.map +1 -0
- package/dist/context-graph-binding-state.d.ts +6 -0
- package/dist/context-graph-binding-state.d.ts.map +1 -1
- package/dist/context-graph-binding-state.js +17 -10
- package/dist/context-graph-binding-state.js.map +1 -1
- package/dist/context-graph-claim-proof.d.ts +74 -0
- package/dist/context-graph-claim-proof.d.ts.map +1 -0
- package/dist/context-graph-claim-proof.js +137 -0
- package/dist/context-graph-claim-proof.js.map +1 -0
- package/dist/context-graph-member-proof.d.ts +33 -0
- package/dist/context-graph-member-proof.d.ts.map +1 -0
- package/dist/context-graph-member-proof.js +142 -0
- package/dist/context-graph-member-proof.js.map +1 -0
- 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-confirmation.d.ts +22 -2
- package/dist/context-graph-meta-confirmation.d.ts.map +1 -1
- package/dist/context-graph-meta-confirmation.js +54 -23
- package/dist/context-graph-meta-confirmation.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-metadata-relocation.d.ts +18 -30
- package/dist/context-graph-metadata-relocation.d.ts.map +1 -1
- package/dist/context-graph-metadata-relocation.js +32 -2
- package/dist/context-graph-metadata-relocation.js.map +1 -1
- package/dist/context-graph-private-meta-proof.d.ts +8 -9
- package/dist/context-graph-private-meta-proof.d.ts.map +1 -1
- package/dist/context-graph-private-meta-proof.js +25 -89
- package/dist/context-graph-private-meta-proof.js.map +1 -1
- package/dist/context-graph-public-meta-proof.d.ts +11 -1
- package/dist/context-graph-public-meta-proof.d.ts.map +1 -1
- package/dist/context-graph-public-meta-proof.js +23 -3
- package/dist/context-graph-public-meta-proof.js.map +1 -1
- package/dist/context-graph-read-authority.d.ts +30 -3
- package/dist/context-graph-read-authority.d.ts.map +1 -1
- package/dist/context-graph-read-authority.js +62 -18
- package/dist/context-graph-read-authority.js.map +1 -1
- package/dist/context-graph-storage-discovery.d.ts +14 -4
- package/dist/context-graph-storage-discovery.d.ts.map +1 -1
- package/dist/context-graph-storage-discovery.js +27 -7
- package/dist/context-graph-storage-discovery.js.map +1 -1
- package/dist/context-graph-subscription-authority-recovery.d.ts +7 -0
- package/dist/context-graph-subscription-authority-recovery.d.ts.map +1 -1
- package/dist/context-graph-subscription-authority-recovery.js +1 -0
- package/dist/context-graph-subscription-authority-recovery.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 +18 -4
- package/dist/curator-meta-refresh.d.ts.map +1 -1
- package/dist/curator-meta-refresh.js +61 -58
- 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-agents-phonebook.d.ts +41 -0
- package/dist/dkg-agent-agents-phonebook.d.ts.map +1 -0
- package/dist/dkg-agent-agents-phonebook.js +155 -0
- package/dist/dkg-agent-agents-phonebook.js.map +1 -0
- package/dist/dkg-agent-base.d.ts +162 -24
- package/dist/dkg-agent-base.d.ts.map +1 -1
- package/dist/dkg-agent-base.js +175 -147
- package/dist/dkg-agent-base.js.map +1 -1
- package/dist/dkg-agent-cg-chain-observation.d.ts +6 -1
- package/dist/dkg-agent-cg-chain-observation.d.ts.map +1 -1
- package/dist/dkg-agent-cg-chain-observation.js +20 -5
- package/dist/dkg-agent-cg-chain-observation.js.map +1 -1
- package/dist/dkg-agent-cg-name-resolution.d.ts +38 -2
- package/dist/dkg-agent-cg-name-resolution.d.ts.map +1 -1
- package/dist/dkg-agent-cg-name-resolution.js +100 -8
- package/dist/dkg-agent-cg-name-resolution.js.map +1 -1
- package/dist/dkg-agent-cg-registry.d.ts +43 -3
- package/dist/dkg-agent-cg-registry.d.ts.map +1 -1
- package/dist/dkg-agent-cg-registry.js +267 -129
- 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 +40 -18
- package/dist/dkg-agent-cg-resolve.js.map +1 -1
- package/dist/dkg-agent-constants.d.ts +10 -2
- package/dist/dkg-agent-constants.d.ts.map +1 -1
- package/dist/dkg-agent-constants.js +10 -2
- package/dist/dkg-agent-constants.js.map +1 -1
- package/dist/dkg-agent-context-graph.d.ts.map +1 -1
- package/dist/dkg-agent-context-graph.js +1 -5
- package/dist/dkg-agent-context-graph.js.map +1 -1
- package/dist/dkg-agent-crypto.d.ts +128 -21
- package/dist/dkg-agent-crypto.d.ts.map +1 -1
- package/dist/dkg-agent-crypto.js +824 -176
- package/dist/dkg-agent-crypto.js.map +1 -1
- package/dist/dkg-agent-diagnostics.d.ts.map +1 -1
- package/dist/dkg-agent-diagnostics.js +4 -3
- package/dist/dkg-agent-diagnostics.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 +129 -22
- package/dist/dkg-agent-lifecycle.d.ts.map +1 -1
- package/dist/dkg-agent-lifecycle.js +690 -211
- 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 +194 -281
- 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 +102 -86
- 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 +87 -80
- package/dist/dkg-agent-rfc64-catalog.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-catalog.js +726 -476
- package/dist/dkg-agent-rfc64-catalog.js.map +1 -1
- package/dist/dkg-agent-rfc64-meta-bootstrap.d.ts +6 -1
- package/dist/dkg-agent-rfc64-meta-bootstrap.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-meta-bootstrap.js +29 -13
- package/dist/dkg-agent-rfc64-meta-bootstrap.js.map +1 -1
- package/dist/dkg-agent-rfc64-swm-catalog-projection-supervisor.d.ts +8 -0
- package/dist/dkg-agent-rfc64-swm-catalog-projection-supervisor.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-swm-catalog-projection-supervisor.js +249 -40
- package/dist/dkg-agent-rfc64-swm-catalog-projection-supervisor.js.map +1 -1
- package/dist/dkg-agent-rfc64-swm-catalog-projection.d.ts.map +1 -1
- package/dist/dkg-agent-rfc64-swm-catalog-projection.js +3 -2
- package/dist/dkg-agent-rfc64-swm-catalog-projection.js.map +1 -1
- package/dist/dkg-agent-swm-host.d.ts +217 -52
- package/dist/dkg-agent-swm-host.d.ts.map +1 -1
- package/dist/dkg-agent-swm-host.js +1305 -858
- 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 +49 -8
- package/dist/dkg-agent-swm-substrate.d.ts.map +1 -1
- package/dist/dkg-agent-swm-substrate.js +219 -46
- package/dist/dkg-agent-swm-substrate.js.map +1 -1
- package/dist/dkg-agent-types.d.ts +86 -27
- 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 +4 -0
- package/dist/dkg-agent-vm-promotion.d.ts.map +1 -1
- package/dist/dkg-agent-vm-promotion.js +40 -5
- package/dist/dkg-agent-vm-promotion.js.map +1 -1
- package/dist/dkg-agent-vm-reconcile-scheduling.d.ts +7 -0
- package/dist/dkg-agent-vm-reconcile-scheduling.d.ts.map +1 -1
- package/dist/dkg-agent-vm-reconcile-scheduling.js +18 -0
- package/dist/dkg-agent-vm-reconcile-scheduling.js.map +1 -1
- package/dist/dkg-agent.d.ts +31 -2
- package/dist/dkg-agent.d.ts.map +1 -1
- package/dist/dkg-agent.js +188 -72
- 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 +26 -211
- package/dist/finalization-handler.d.ts.map +1 -1
- package/dist/finalization-handler.js +94 -1652
- package/dist/finalization-handler.js.map +1 -1
- package/dist/finalization-recovery-sqlite-displacement.d.ts +37 -0
- package/dist/finalization-recovery-sqlite-displacement.d.ts.map +1 -0
- package/dist/finalization-recovery-sqlite-displacement.js +102 -0
- package/dist/finalization-recovery-sqlite-displacement.js.map +1 -0
- package/dist/finalization-recovery-sqlite-policy.d.ts +20 -4
- package/dist/finalization-recovery-sqlite-policy.d.ts.map +1 -1
- package/dist/finalization-recovery-sqlite-policy.js +21 -5
- package/dist/finalization-recovery-sqlite-policy.js.map +1 -1
- package/dist/finalization-recovery-sqlite-rows.d.ts +53 -0
- package/dist/finalization-recovery-sqlite-rows.d.ts.map +1 -0
- package/dist/finalization-recovery-sqlite-rows.js +77 -0
- package/dist/finalization-recovery-sqlite-rows.js.map +1 -0
- package/dist/finalization-recovery-sqlite-store.d.ts +10 -4
- package/dist/finalization-recovery-sqlite-store.d.ts.map +1 -1
- package/dist/finalization-recovery-sqlite-store.js +49 -50
- package/dist/finalization-recovery-sqlite-store.js.map +1 -1
- package/dist/finalization-recovery-store.d.ts +18 -0
- package/dist/finalization-recovery-store.d.ts.map +1 -1
- package/dist/finalization-recovery-store.js +24 -0
- package/dist/finalization-recovery-store.js.map +1 -1
- package/dist/finalization-recovery-worker.js +1 -1
- package/dist/finalization-recovery-worker.js.map +1 -1
- package/dist/finalization-recovery.d.ts +2 -3
- package/dist/finalization-recovery.d.ts.map +1 -1
- package/dist/finalization-recovery.js +12 -9
- package/dist/finalization-recovery.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-publish-handler.d.ts.map +1 -1
- package/dist/gossip-publish-handler.js +4 -2
- package/dist/gossip-publish-handler.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 +4 -1
- 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/approved-member-acceptance.d.ts +61 -0
- package/dist/internal/context-graph-authority/approved-member-acceptance.d.ts.map +1 -0
- package/dist/internal/context-graph-authority/approved-member-acceptance.js +75 -0
- package/dist/internal/context-graph-authority/approved-member-acceptance.js.map +1 -0
- 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/context-graph-authority.d.ts +1 -0
- package/dist/internal/context-graph-authority/context-graph-authority.d.ts.map +1 -1
- package/dist/internal/context-graph-authority/context-graph-authority.js +25 -0
- package/dist/internal/context-graph-authority/context-graph-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 +84 -0
- package/dist/internal/context-graph-authority/swm-transport-authority.d.ts.map +1 -0
- package/dist/internal/context-graph-authority/swm-transport-authority.js +175 -0
- package/dist/internal/context-graph-authority/swm-transport-authority.js.map +1 -0
- 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 +79 -22
- 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/p2p/peer-connect.d.ts +10 -1
- package/dist/p2p/peer-connect.d.ts.map +1 -1
- package/dist/p2p/peer-connect.js +11 -2
- package/dist/p2p/peer-connect.js.map +1 -1
- package/dist/p2p/peer-selection.d.ts +8 -0
- package/dist/p2p/peer-selection.d.ts.map +1 -1
- package/dist/p2p/peer-selection.js +21 -12
- package/dist/p2p/peer-selection.js.map +1 -1
- package/dist/p2p/protocol-readiness.d.ts +3 -3
- package/dist/p2p/protocol-readiness.d.ts.map +1 -1
- package/dist/p2p/protocol-readiness.js +4 -3
- package/dist/p2p/protocol-readiness.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 +19 -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/applied-catalog-authority-transition-v1.d.ts.map +1 -1
- package/dist/rfc64/applied-catalog-authority-transition-v1.js +15 -17
- package/dist/rfc64/applied-catalog-authority-transition-v1.js.map +1 -1
- 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 +12 -13
- package/dist/rfc64/catalog-operational-applied-heads-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-operational-applied-heads-v1.js +17 -52
- 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-repair-diagnostics-v1.d.ts +28 -0
- package/dist/rfc64/catalog-repair-diagnostics-v1.d.ts.map +1 -0
- package/dist/rfc64/catalog-repair-diagnostics-v1.js +93 -0
- package/dist/rfc64/catalog-repair-diagnostics-v1.js.map +1 -0
- package/dist/rfc64/catalog-repair-retry-v1.d.ts +13 -0
- package/dist/rfc64/catalog-repair-retry-v1.d.ts.map +1 -0
- package/dist/rfc64/catalog-repair-retry-v1.js +39 -0
- package/dist/rfc64/catalog-repair-retry-v1.js.map +1 -0
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts +52 -0
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.js +188 -32
- package/dist/rfc64/catalog-replay-recovery-runtime-v1.js.map +1 -1
- package/dist/rfc64/catalog-replay-snapshot-runtime-v1.d.ts +2 -8
- package/dist/rfc64/catalog-replay-snapshot-runtime-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-replay-snapshot-runtime-v1.js +10 -37
- package/dist/rfc64/catalog-replay-snapshot-runtime-v1.js.map +1 -1
- package/dist/rfc64/catalog-rollout-authority-v1.d.ts +13 -0
- package/dist/rfc64/catalog-rollout-authority-v1.d.ts.map +1 -1
- package/dist/rfc64/catalog-rollout-authority-v1.js +11 -0
- package/dist/rfc64/catalog-rollout-authority-v1.js.map +1 -1
- package/dist/rfc64/catalog-row-authorship.d.ts +12 -0
- package/dist/rfc64/catalog-row-authorship.d.ts.map +1 -1
- package/dist/rfc64/catalog-row-authorship.js +110 -57
- package/dist/rfc64/catalog-row-authorship.js.map +1 -1
- package/dist/rfc64/inventory-v1/candidate.d.ts +31 -10
- package/dist/rfc64/inventory-v1/candidate.d.ts.map +1 -1
- package/dist/rfc64/inventory-v1/candidate.js +51 -16
- package/dist/rfc64/inventory-v1/candidate.js.map +1 -1
- package/dist/rfc64/inventory-v1/index.d.ts +1 -1
- package/dist/rfc64/inventory-v1/index.d.ts.map +1 -1
- package/dist/rfc64/inventory-v1/index.js +1 -1
- package/dist/rfc64/inventory-v1/index.js.map +1 -1
- package/dist/rfc64/inventory-v1/open.d.ts.map +1 -1
- package/dist/rfc64/inventory-v1/open.js +7 -4
- package/dist/rfc64/inventory-v1/open.js.map +1 -1
- package/dist/rfc64/inventory-v1/swm-author-inventory-persistence.d.ts +3 -1
- package/dist/rfc64/inventory-v1/swm-author-inventory-persistence.d.ts.map +1 -1
- package/dist/rfc64/inventory-v1/swm-author-inventory-persistence.js +15 -0
- package/dist/rfc64/inventory-v1/swm-author-inventory-persistence.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/public-catalog-native-receiver-v1.d.ts.map +1 -1
- package/dist/rfc64/public-catalog-native-receiver-v1.js +26 -21
- package/dist/rfc64/public-catalog-native-receiver-v1.js.map +1 -1
- package/dist/rfc64/public-catalog-native-reconciler-v1.d.ts +17 -11
- package/dist/rfc64/public-catalog-native-reconciler-v1.d.ts.map +1 -1
- package/dist/rfc64/public-catalog-native-reconciler-v1.js +10 -8
- package/dist/rfc64/public-catalog-native-reconciler-v1.js.map +1 -1
- package/dist/rfc64/public-catalog-receiver-v1.d.ts +9 -0
- package/dist/rfc64/public-catalog-receiver-v1.d.ts.map +1 -1
- package/dist/rfc64/public-catalog-receiver-v1.js +11 -2
- package/dist/rfc64/public-catalog-receiver-v1.js.map +1 -1
- package/dist/rfc64/public-catalog-service-v1.d.ts.map +1 -1
- package/dist/rfc64/public-catalog-service-v1.js +21 -22
- package/dist/rfc64/public-catalog-service-v1.js.map +1 -1
- package/dist/rfc64/public-catalog-successor-producer-v1.d.ts.map +1 -1
- package/dist/rfc64/public-catalog-successor-producer-v1.js +5 -4
- package/dist/rfc64/public-catalog-successor-producer-v1.js.map +1 -1
- 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-catalog-durable-asset-resolver-v1.d.ts.map +1 -1
- package/dist/rfc64/swm-catalog-durable-asset-resolver-v1.js +60 -53
- package/dist/rfc64/swm-catalog-durable-asset-resolver-v1.js.map +1 -1
- package/dist/rfc64/swm-inventory-catalog-reconciler-v1.d.ts +2 -1
- package/dist/rfc64/swm-inventory-catalog-reconciler-v1.d.ts.map +1 -1
- package/dist/rfc64/swm-inventory-catalog-reconciler-v1.js +44 -4
- package/dist/rfc64/swm-inventory-catalog-reconciler-v1.js.map +1 -1
- 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-asset-fetch.d.ts +22 -0
- package/dist/sync/exact-asset-fetch.d.ts.map +1 -1
- package/dist/sync/exact-asset-fetch.js +30 -3
- package/dist/sync/exact-asset-fetch.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/on-connect/sync-on-connect.d.ts +1 -1
- package/dist/sync/on-connect/sync-on-connect.d.ts.map +1 -1
- package/dist/sync/on-connect/sync-on-connect.js +3 -3
- package/dist/sync/on-connect/sync-on-connect.js.map +1 -1
- package/dist/sync/on-demand-agents-phonebook.d.ts +162 -0
- package/dist/sync/on-demand-agents-phonebook.d.ts.map +1 -0
- package/dist/sync/on-demand-agents-phonebook.js +571 -0
- package/dist/sync/on-demand-agents-phonebook.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 +61 -27
- 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 +26 -1
- package/dist/sync/responder/sync-handler.d.ts.map +1 -1
- package/dist/sync/responder/sync-handler.js +107 -58
- 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/system-context-graph-policy.d.ts +10 -0
- package/dist/sync/system-context-graph-policy.d.ts.map +1 -1
- package/dist/sync/system-context-graph-policy.js +11 -0
- package/dist/sync/system-context-graph-policy.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-publish-snapshot-preflight.d.ts +13 -0
- package/dist/vm-publish-snapshot-preflight.d.ts.map +1 -0
- package/dist/vm-publish-snapshot-preflight.js +94 -0
- package/dist/vm-publish-snapshot-preflight.js.map +1 -0
- 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/dist/vm-refresh.d.ts +138 -0
- package/dist/vm-refresh.d.ts.map +1 -0
- package/dist/vm-refresh.js +201 -0
- package/dist/vm-refresh.js.map +1 -0
- package/package.json +19 -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,12 +6,16 @@
|
|
|
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,
|
|
14
|
-
import { readMaterializedVersion, shouldApplyMaterialization, withMaterializationLock, isKnowledgeAssetWorkspaceHeadCorruptError, } from '@origintrail-official/dkg-publisher';
|
|
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';
|
|
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';
|
|
17
21
|
import { buildReconciledKnowledgeAssetUal, } from './ka-identity.js';
|
|
@@ -24,12 +28,20 @@ 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 {
|
|
32
|
-
import {
|
|
41
|
+
import { MAX_CONTEXT_GRAPH_ASSET_FETCH_PEERS, ExactAssetFetchLifecycleClosedError, ExactAssetVersionBehindError, exactAssetFetchPeerWindow, runExactAssetFetch, } from './sync/exact-asset-fetch.js';
|
|
42
|
+
import { isBoundedOperationTimeoutError, runBoundedOperation } from './bounded-operation.js';
|
|
43
|
+
/** Graph-scoped KA metadata marker; its presence names the metadata graph. */
|
|
44
|
+
const GRAPH_KA_CONTENT_SCOPE_VERSION_PREDICATE = 'http://dkg.io/ontology/contentScopeVersion';
|
|
33
45
|
function rsHealStoreOptions(operation, signal) {
|
|
34
46
|
return {
|
|
35
47
|
priority: 'background',
|
|
@@ -73,12 +85,13 @@ function advanceRsHealCursor(cursorMap, cursorKey, bindings, batchSize, maxEntri
|
|
|
73
85
|
cursorMap.delete(oldest);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
|
-
import {
|
|
77
|
-
import { TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
|
|
88
|
+
import { SYNC_MIN_GRAPH_BUDGET_MS, TIMEOUT_SENTINEL, } from './dkg-agent-constants.js';
|
|
78
89
|
import { chainAuthorityReadBudgetsOf } from './chain-authority-read-budgets.js';
|
|
79
90
|
import { ContextGraphNotFoundError, } from './dkg-agent-types.js';
|
|
80
91
|
import { DKGAgentBase } from './dkg-agent-base.js';
|
|
81
|
-
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';
|
|
82
95
|
import { finalizedContextGraphSnapshotMismatchV1 } from './internal/context-graph-authority/finalized-context-graph-binding.js';
|
|
83
96
|
import { CONTEXT_GRAPH_AUTHORITY_RPC_SITES as CG_AUTH_RPC_SITES, withOwnedRpcRequestContext, withRpcUsageSite, } from '@origintrail-official/dkg-chain';
|
|
84
97
|
const DEFAULT_HOST_MODE_RECONCILE_BATCH_SIZE = 32;
|
|
@@ -105,26 +118,6 @@ function vmReconcileDeploymentMatches(value, expected) {
|
|
|
105
118
|
* amplify into a per-message `eth_call` (Branimir review #1239 follow-on).
|
|
106
119
|
*/
|
|
107
120
|
const HOST_MODE_PUBLISH_POLICY_MAX_CACHE_AGE_MS = 5_000;
|
|
108
|
-
// Exact VM data responses are page-only and capped at 64 rows per page. Keep
|
|
109
|
-
// one recovery microbatch near 64 non-empty pages so a single large graph does
|
|
110
|
-
// not monopolize the global sync admission. This is a soft scheduling/fairness
|
|
111
|
-
// cap, not a wire or correctness limit: an individually larger KA is still
|
|
112
|
-
// admitted alone and remains bounded by the exact executor's hard guards.
|
|
113
|
-
const VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES = 4096n;
|
|
114
|
-
const VM_EXACT_MICROBATCH_LIMITS = Object.freeze({
|
|
115
|
-
// Executor capability: the current exact-sync envelope accepts at most ten.
|
|
116
|
-
maxAssets: MAX_EXACT_SYNC_ASSETS,
|
|
117
|
-
// Soft recovery-window targets. A single public KA may exceed either; it is
|
|
118
|
-
// still admitted alone while the exact executor's own hard byte/quad guards
|
|
119
|
-
// remain authoritative.
|
|
120
|
-
targetBytes: 24n * 1024n * 1024n,
|
|
121
|
-
targetLeaves: VM_EXACT_MICROBATCH_PAGE_FAIRNESS_LEAVES,
|
|
122
|
-
fixedBytesPerAsset: 64n * 1024n,
|
|
123
|
-
bytesPerLeafOverhead: 128n,
|
|
124
|
-
byteSizeMultiplierBps: 11500n,
|
|
125
|
-
// Hard exact-selector cap, evaluated with the executor's real encoder.
|
|
126
|
-
maxSelectorBytes: 16 * 1024,
|
|
127
|
-
});
|
|
128
121
|
/**
|
|
129
122
|
* Recovery-target root of an ordinal whose KA the node holds nowhere locally:
|
|
130
123
|
* the sweep queues it without reading the chain root. Exact recovery fetches
|
|
@@ -174,6 +167,13 @@ async function raceVmReconcileAbort(work, signal) {
|
|
|
174
167
|
signal.removeEventListener('abort', onAbort);
|
|
175
168
|
}
|
|
176
169
|
}
|
|
170
|
+
/** `peerIds` started `offset` places in, wrapping. */
|
|
171
|
+
function rotatePeerIds(peerIds, offset) {
|
|
172
|
+
if (peerIds.length === 0)
|
|
173
|
+
return [];
|
|
174
|
+
const start = offset % peerIds.length;
|
|
175
|
+
return [...peerIds.slice(start), ...peerIds.slice(0, start)];
|
|
176
|
+
}
|
|
177
177
|
/** Track the raw dependency even if an abort race releases its caller first. */
|
|
178
178
|
function trackVmReconcilePhysicalRun(runs, run) {
|
|
179
179
|
runs.add(run);
|
|
@@ -312,6 +312,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
312
312
|
* reserved, or when the node is already subscribed in member mode.
|
|
313
313
|
*/
|
|
314
314
|
async reconcileSwmHostModeSubscription(contextGraphId, source = SUBSCRIPTION_SOURCES.RECONCILER) {
|
|
315
|
+
const session = this.gossipSession;
|
|
316
|
+
const live = session.live();
|
|
317
|
+
if (live === null)
|
|
318
|
+
return;
|
|
315
319
|
if (!this.swmHostModeStore)
|
|
316
320
|
return;
|
|
317
321
|
if (Object.values(SYSTEM_CONTEXT_GRAPHS).includes(contextGraphId))
|
|
@@ -347,7 +351,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
347
351
|
await this.isCuratedForHostMode(contextGraphId)) {
|
|
348
352
|
// A manually hosted CG can become curated later. Upgrade the cached
|
|
349
353
|
// classification so the existing handler starts stripping immediately.
|
|
350
|
-
this.
|
|
354
|
+
if (!session.active || this.gossipSession !== session)
|
|
355
|
+
return;
|
|
356
|
+
session.swmHostModeCurated.set(hostKey, true);
|
|
351
357
|
}
|
|
352
358
|
await this.maybeMarkRegisteredForHostMode(contextGraphId);
|
|
353
359
|
return;
|
|
@@ -382,7 +388,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
382
388
|
// critically, the host-only-core case where there's no local
|
|
383
389
|
// `_meta` and `isPrivateContextGraph` alone returns false.
|
|
384
390
|
const curated = await this.isCuratedForHostMode(contextGraphId);
|
|
385
|
-
if (!curated)
|
|
391
|
+
if (!curated || !session.active || this.gossipSession !== session)
|
|
386
392
|
return;
|
|
387
393
|
// OT-RFC-49 WS-A — the private-ciphertext strip. With `stripCiphertext`
|
|
388
394
|
// ON (default), a core declines ALL host-mode custody for a curated CG:
|
|
@@ -502,15 +508,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
502
508
|
* so the host-mode lifecycle is in one place.
|
|
503
509
|
*/
|
|
504
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;
|
|
505
515
|
if (!this.rfc64LegacySwmGossipAllowedForContextGraph(contextGraphId)) {
|
|
506
516
|
const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
|
|
507
|
-
const hadRuntimeHostState =
|
|
508
|
-
||
|
|
509
|
-
||
|
|
517
|
+
const hadRuntimeHostState = session.swmHostModeHandlers.has(hostKey)
|
|
518
|
+
|| session.swmHostModeSubscribed.has(hostKey)
|
|
519
|
+
|| session.swmHostModeCurated.has(hostKey);
|
|
510
520
|
this.unwireSwmHostModeHandler(contextGraphId);
|
|
511
|
-
const deletedStaleHandler =
|
|
512
|
-
const deletedStaleSubscription =
|
|
513
|
-
const deletedStaleClassification =
|
|
521
|
+
const deletedStaleHandler = session.swmHostModeHandlers.delete(hostKey);
|
|
522
|
+
const deletedStaleSubscription = session.swmHostModeSubscribed.delete(hostKey);
|
|
523
|
+
const deletedStaleClassification = session.swmHostModeCurated.delete(hostKey);
|
|
514
524
|
if (deletedStaleHandler || deletedStaleSubscription || deletedStaleClassification) {
|
|
515
525
|
// Heal partially restored/stale bookkeeping even when its handler
|
|
516
526
|
// reference is absent, which makes unwireSwmHostModeHandler a no-op.
|
|
@@ -538,7 +548,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
538
548
|
// genuine no-op (instead of silently wiring a second handler on
|
|
539
549
|
// the same topic).
|
|
540
550
|
const wireCgId = this.canonicalSwmHostModeKey(contextGraphId);
|
|
541
|
-
if (
|
|
551
|
+
if (session.swmHostModeHandlers.has(wireCgId)) {
|
|
542
552
|
// Idempotent re-entry — preserve the original source. The first
|
|
543
553
|
// discovery path to wire the handler wins the provenance label;
|
|
544
554
|
// a later path covering the same CG is "also true" but the
|
|
@@ -546,44 +556,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
546
556
|
return;
|
|
547
557
|
}
|
|
548
558
|
const swmTopic = contextGraphWorkspaceTopic(wireCgId);
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const handler = (
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (this.swmHostModeStripCiphertext() &&
|
|
556
|
-
this.swmHostModeCurated.get(wireCgId) !== false) {
|
|
557
|
-
this.log.debug(createOperationContext('share'), `Dropping host-mode envelope on cg=${contextGraphId} from=${from}: ` +
|
|
558
|
-
`private-ciphertext strip is ON for a curated CG (OT-RFC-49 WS-A)`);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
// OT-RFC-38 LU-11: peek envelope type and dispatch. Chunked
|
|
562
|
-
// envelopes (`type='share-write-chunked'`) take the V2 chunk
|
|
563
|
-
// persistence path; everything else flows through the legacy
|
|
564
|
-
// host-mode store unchanged. Failed decode falls through to
|
|
565
|
-
// `ingestSwmHostModeEnvelope` which is also defensive — the
|
|
566
|
-
// dispatch here is best-effort, not a security boundary.
|
|
567
|
-
let envelopeType;
|
|
568
|
-
try {
|
|
569
|
-
const peek = decodeGossipEnvelope(data);
|
|
570
|
-
envelopeType = peek?.type;
|
|
571
|
-
}
|
|
572
|
-
catch { /* drop into legacy path */ }
|
|
573
|
-
if (envelopeType === GOSSIP_TYPE_WORKSPACE_PUBLISH_CHUNKED) {
|
|
574
|
-
this.ingestSwmCiphertextChunkEnvelope(contextGraphId, data, from).catch((err) => {
|
|
575
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
576
|
-
this.log.warn(createOperationContext('system'), `LU-11: chunked SWM ingest failed for "${contextGraphId}": ${msg}`);
|
|
577
|
-
});
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
this.ingestSwmHostModeEnvelope(contextGraphId, data, from).catch((err) => {
|
|
581
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
582
|
-
this.log.warn(createOperationContext('system'), `Host-mode SWM ingest failed for "${contextGraphId}": ${msg}`);
|
|
583
|
-
});
|
|
584
|
-
};
|
|
585
|
-
this.swmHostModeHandlers.set(wireCgId, handler);
|
|
586
|
-
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);
|
|
587
565
|
// B3: persist the host-mode designation so a restart re-engages
|
|
588
566
|
// this handler before the chain-event poller catches up.
|
|
589
567
|
// Codex PR #620 R2: chain wire/unwire writes through a per-CG
|
|
@@ -1622,7 +1600,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
1622
1600
|
}
|
|
1623
1601
|
if (!authOk) {
|
|
1624
1602
|
try {
|
|
1625
|
-
const allowedPeers = await this.
|
|
1603
|
+
const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
|
|
1626
1604
|
if (allowedPeers !== null) {
|
|
1627
1605
|
anyAuthorityFound = true;
|
|
1628
1606
|
if (allowedPeers.includes(fromPeerId))
|
|
@@ -1931,6 +1909,20 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
1931
1909
|
`${transition.previous?.onChainId}->${newOnChainId}; reset reconcile watermark + cursor to 0`);
|
|
1932
1910
|
}
|
|
1933
1911
|
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Inverse of {@link bindSubscriptionOnChainId}: drop a local CG's on-chain
|
|
1914
|
+
* id together with every piece of reconcile progress made against it. The
|
|
1915
|
+
* persisted watermark and the in-memory cursor count ordinals of the graph
|
|
1916
|
+
* that id named, so neither may survive the id. The canonical setter
|
|
1917
|
+
* persists the unbound row.
|
|
1918
|
+
*/
|
|
1919
|
+
unbindSubscriptionOnChainId(localCgId) {
|
|
1920
|
+
const sub = this.subscribedContextGraphs.get(localCgId);
|
|
1921
|
+
if (sub === undefined)
|
|
1922
|
+
return;
|
|
1923
|
+
this.forceClearVmReconcileStateForContextGraph(localCgId);
|
|
1924
|
+
this.setContextGraphSubscription(localCgId, { ...sub, onChainId: undefined, lastReconciledOrdinal: 0 });
|
|
1925
|
+
}
|
|
1934
1926
|
/**
|
|
1935
1927
|
* Install a reverse-derived VM candidate without promoting it to the shared
|
|
1936
1928
|
* authoritative `onChainId` field. The candidate is process-local and every
|
|
@@ -2370,7 +2362,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2370
2362
|
return null;
|
|
2371
2363
|
if (resolved.provenance !== 'reverse-name-hash') {
|
|
2372
2364
|
try {
|
|
2373
|
-
|
|
2365
|
+
// An on-demand subscription binds in memory only; durable rows are
|
|
2366
|
+
// saved before the binding becomes visible.
|
|
2367
|
+
await this.persistContextGraphSyncStateStrict(localCgId, { ...sub, onChainId: resolved.onChainId }, 'on-chain id binding', isSubscriptionCurrent);
|
|
2374
2368
|
}
|
|
2375
2369
|
catch {
|
|
2376
2370
|
return null;
|
|
@@ -2464,6 +2458,534 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2464
2458
|
}
|
|
2465
2459
|
return localCgId;
|
|
2466
2460
|
}
|
|
2461
|
+
/**
|
|
2462
|
+
* #2858 — body of the live `KnowledgeAssetUpdated` nudge.
|
|
2463
|
+
*
|
|
2464
|
+
* An update keeps the KA id and moves its latest root. The ordinal sweep
|
|
2465
|
+
* never revisits a settled ordinal, and a node whose only copy is a
|
|
2466
|
+
* confirmed VM copy (a member of a catalog graph, a core outside the update's
|
|
2467
|
+
* ACK set) has no other lane that brings the new version. This decides from
|
|
2468
|
+
* local state alone, with no chain read, for every VM-reconcile target graph
|
|
2469
|
+
* that holds a confirmed copy of the KA; a queued target gets a reconcile
|
|
2470
|
+
* pass, whose refresh worker reads the chain and fetches the current version
|
|
2471
|
+
* ({@link startVmRefreshWorker}).
|
|
2472
|
+
*
|
|
2473
|
+
* - A copy at another root is queued for a refresh.
|
|
2474
|
+
* - A copy already at the event's root is skipped when it reflects the
|
|
2475
|
+
* update: the event's own transaction confirmed it (the publisher), or it
|
|
2476
|
+
* was materialized from a chain view at or after the event's block (a
|
|
2477
|
+
* replayed event). Otherwise it may be an older version with the same
|
|
2478
|
+
* content, so a version check is queued.
|
|
2479
|
+
* - When the workspace already stages a newer version than the confirmed
|
|
2480
|
+
* copy (the publisher mid-update, a core holding the update's ACK copy, a
|
|
2481
|
+
* member that recovered its curator's shared memory), the target waits
|
|
2482
|
+
* `VM_REFRESH_STAGED_GRACE_MS` for the lane that staged it; on a member no
|
|
2483
|
+
* lane promotes it, and the refresh does.
|
|
2484
|
+
*
|
|
2485
|
+
* The event's root is only a trigger: an older or superseded event is
|
|
2486
|
+
* settled by the worker's chain read, never materialized. Every no-op is
|
|
2487
|
+
* logged at debug. A store read that fails, or a full target set, throws, so
|
|
2488
|
+
* the event lane holds the event and dispatches it again after its failure
|
|
2489
|
+
* backoff instead of losing it.
|
|
2490
|
+
*
|
|
2491
|
+
* Returns the refresh targets newly queued.
|
|
2492
|
+
*/
|
|
2493
|
+
async handleKAUpdatedNudge(kaId, merkleRoot, ctx, options = {}) {
|
|
2494
|
+
const { signal } = options;
|
|
2495
|
+
const blockNumber = options.blockNumber !== undefined
|
|
2496
|
+
&& Number.isSafeInteger(options.blockNumber)
|
|
2497
|
+
&& options.blockNumber >= 0
|
|
2498
|
+
? options.blockNumber
|
|
2499
|
+
: undefined;
|
|
2500
|
+
const eventTxHash = typeof options.txHash === 'string' && options.txHash.length > 0
|
|
2501
|
+
? options.txHash.toLowerCase()
|
|
2502
|
+
: undefined;
|
|
2503
|
+
const logIndex = options.logIndex !== undefined
|
|
2504
|
+
&& Number.isSafeInteger(options.logIndex)
|
|
2505
|
+
&& options.logIndex >= 0
|
|
2506
|
+
? options.logIndex
|
|
2507
|
+
: undefined;
|
|
2508
|
+
const blockHash = typeof options.blockHash === 'string' && options.blockHash.length > 0
|
|
2509
|
+
? options.blockHash.toLowerCase()
|
|
2510
|
+
: undefined;
|
|
2511
|
+
signal?.throwIfAborted();
|
|
2512
|
+
const lifecycleGeneration = this.vmReconcileLifecycleGeneration;
|
|
2513
|
+
const lifecycleSignal = this.vmReconcileLifecycleController?.signal;
|
|
2514
|
+
const isLifecycleCurrent = () => !this.vmReconcileRotationClosed
|
|
2515
|
+
&& !signal?.aborted
|
|
2516
|
+
&& !lifecycleSignal?.aborted
|
|
2517
|
+
&& this.vmReconcileLifecycleGeneration === lifecycleGeneration;
|
|
2518
|
+
if (!isLifecycleCurrent() || kaId <= 0n || merkleRoot.length !== 32)
|
|
2519
|
+
return [];
|
|
2520
|
+
this.expireVmRefreshTargets(ctx);
|
|
2521
|
+
const update = `update${blockNumber === undefined ? '' : ` at block ${blockNumber}`}`;
|
|
2522
|
+
let ual;
|
|
2523
|
+
let holders;
|
|
2524
|
+
try {
|
|
2525
|
+
const storageAddr = this.chain.getDKGKnowledgeAssetsAddress
|
|
2526
|
+
? await this.chain.getDKGKnowledgeAssetsAddress()
|
|
2527
|
+
: undefined;
|
|
2528
|
+
if (!storageAddr)
|
|
2529
|
+
return [];
|
|
2530
|
+
ual = buildReconciledKnowledgeAssetUal(this.chain.chainId, storageAddr, kaId);
|
|
2531
|
+
holders = await this.localGraphsDescribingKnowledgeAsset(ual);
|
|
2532
|
+
}
|
|
2533
|
+
catch (err) {
|
|
2534
|
+
throw new Error(`VM refresh: could not read which local graphs hold KA ${kaId} for its ${update}: `
|
|
2535
|
+
+ `${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
2536
|
+
}
|
|
2537
|
+
const eventRoot = ethers.hexlify(merkleRoot).toLowerCase();
|
|
2538
|
+
const queued = [];
|
|
2539
|
+
if (holders.length === 0) {
|
|
2540
|
+
this.log.debug(ctx, `VM refresh: no local graph holds ${ual}; nothing to refresh`);
|
|
2541
|
+
}
|
|
2542
|
+
for (const localCgId of holders) {
|
|
2543
|
+
if (!isLifecycleCurrent())
|
|
2544
|
+
break;
|
|
2545
|
+
if (!this.isVmReconcileTargetSelected(localCgId)) {
|
|
2546
|
+
this.log.debug(ctx, `VM refresh: "${localCgId}" holds ${ual} but is not a VM reconcile target; not queued`);
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
let local;
|
|
2550
|
+
let coversUpdate = false;
|
|
2551
|
+
try {
|
|
2552
|
+
local = await this.readVmRefreshLocalState(localCgId, ual);
|
|
2553
|
+
if (local.kind === 'confirmed' && local.merkleRoot === eventRoot) {
|
|
2554
|
+
coversUpdate = await this.vmRefreshCopyCoversUpdate(localCgId, ual, local.transactionHash, eventTxHash, blockNumber, blockHash);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (err) {
|
|
2558
|
+
throw new Error(`VM refresh: could not read the copy of ${ual} in "${localCgId}" for its ${update}: `
|
|
2559
|
+
+ `${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
2560
|
+
}
|
|
2561
|
+
if (local.kind !== 'confirmed') {
|
|
2562
|
+
this.log.debug(ctx, `VM refresh: ${ual} in "${localCgId}" not queued: no confirmed copy`);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
const sameRoot = local.merkleRoot === eventRoot;
|
|
2566
|
+
if (coversUpdate) {
|
|
2567
|
+
this.log.debug(ctx, `VM refresh: ${ual} in "${localCgId}" not queued: the copy holds this update`);
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
if (!isLifecycleCurrent())
|
|
2571
|
+
break;
|
|
2572
|
+
const target = {
|
|
2573
|
+
localCgId,
|
|
2574
|
+
ual,
|
|
2575
|
+
kaId,
|
|
2576
|
+
merkleRoot: eventRoot,
|
|
2577
|
+
...(blockNumber === undefined ? {} : { blockNumber }),
|
|
2578
|
+
...(logIndex === undefined ? {} : { logIndex }),
|
|
2579
|
+
...(blockHash === undefined ? {} : { blockHash }),
|
|
2580
|
+
...(eventTxHash === undefined ? {} : { txHash: eventTxHash }),
|
|
2581
|
+
...(sameRoot ? { checkVersion: true } : {}),
|
|
2582
|
+
};
|
|
2583
|
+
const delayMs = local.staged ? DKGAgentBase.VM_REFRESH_STAGED_GRACE_MS : 0;
|
|
2584
|
+
// A replayed event (a lane re-scan) leaves the held target and its
|
|
2585
|
+
// backoff alone.
|
|
2586
|
+
const offered = this.vmRefreshQueue.offer(target, delayMs);
|
|
2587
|
+
if (offered === 'held') {
|
|
2588
|
+
this.log.debug(ctx, `VM refresh: ${ual} in "${localCgId}" already has this or a newer update queued`);
|
|
2589
|
+
continue;
|
|
2590
|
+
}
|
|
2591
|
+
if (offered === 'full') {
|
|
2592
|
+
throw new Error(`VM refresh: ${this.vmRefreshQueue.size} targets are held, the most allowed, so the `
|
|
2593
|
+
+ `${update} of ${ual} in "${localCgId}" waits for some to settle`);
|
|
2594
|
+
}
|
|
2595
|
+
queued.push(target);
|
|
2596
|
+
if (sameRoot) {
|
|
2597
|
+
this.log.debug(ctx, `VM refresh: ${ual} in "${localCgId}" holds its update's root; version check queued`);
|
|
2598
|
+
}
|
|
2599
|
+
else if (local.staged) {
|
|
2600
|
+
this.log.info(ctx, `VM refresh: ${ual} in "${localCgId}" does not hold its update's root and stages a newer `
|
|
2601
|
+
+ `version; refresh queued in ${Math.round(delayMs / 1000)}s unless that version is promoted first`);
|
|
2602
|
+
}
|
|
2603
|
+
else {
|
|
2604
|
+
this.log.info(ctx, `VM refresh: ${ual} in "${localCgId}" does not hold its update's root; refresh queued`);
|
|
2605
|
+
}
|
|
2606
|
+
// A delayed target is not due yet: the graph's next reconcile pass
|
|
2607
|
+
// starts the worker.
|
|
2608
|
+
if (delayMs === 0 && this.vmReconcileScheduling && isLifecycleCurrent()) {
|
|
2609
|
+
this.vmReconcileScheduling.triggerLive(localCgId);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return queued;
|
|
2613
|
+
}
|
|
2614
|
+
/**
|
|
2615
|
+
* #2858 — whether a copy at an update's root already reflects that update:
|
|
2616
|
+
* the update's own transaction confirmed it, or it was materialized from a
|
|
2617
|
+
* chain view at or after the update's block, which already held the update.
|
|
2618
|
+
* Neither fact proves fork lineage when the event carries a block hash,
|
|
2619
|
+
* because the same transaction can be included on a replacement fork.
|
|
2620
|
+
*/
|
|
2621
|
+
async vmRefreshCopyCoversUpdate(localCgId, ual, copyTxHash, eventTxHash, eventBlock, eventBlockHash) {
|
|
2622
|
+
if (eventBlockHash !== undefined)
|
|
2623
|
+
return false;
|
|
2624
|
+
if (eventTxHash !== undefined && copyTxHash === eventTxHash)
|
|
2625
|
+
return true;
|
|
2626
|
+
if (eventBlock === undefined)
|
|
2627
|
+
return false;
|
|
2628
|
+
const materialized = await readMaterializedVersion(this.store, contextGraphMetaUri(localCgId), ual, { source: 'agent.vmRefresh.materializedVersion', priority: 'background' });
|
|
2629
|
+
return materialized !== null && materialized.blockNumber >= eventBlock;
|
|
2630
|
+
}
|
|
2631
|
+
/** #2858 — give up the refresh targets held past `VM_REFRESH_MAX_AGE_MS`, with a warning each. */
|
|
2632
|
+
expireVmRefreshTargets(ctx) {
|
|
2633
|
+
// Narrow test agents built without the base constructor have no queue.
|
|
2634
|
+
const queue = this.vmRefreshQueue;
|
|
2635
|
+
if (queue === undefined)
|
|
2636
|
+
return;
|
|
2637
|
+
for (const given of queue.expire()) {
|
|
2638
|
+
this.log.warn(ctx, `VM refresh: gave up on ${given.ual} in "${given.localCgId}" after `
|
|
2639
|
+
+ `${Math.round(given.heldMs / 60_000)} min and ${given.failures} failed attempt(s) `
|
|
2640
|
+
+ `(${queue.givenUpTotal} given up so far); its copy waits for the KA's next update `
|
|
2641
|
+
+ 'or an asset fetch');
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* #2858 — highest block the update event lane may persist as scanned: just
|
|
2646
|
+
* below the oldest held target's event, so a restart replays that event and
|
|
2647
|
+
* its target is recorded again. Targets held past their maximum age are
|
|
2648
|
+
* given up first, so one that never settles cannot hold the cursor for good.
|
|
2649
|
+
*/
|
|
2650
|
+
vmRefreshPersistCeiling() {
|
|
2651
|
+
this.expireVmRefreshTargets(createOperationContext('system'));
|
|
2652
|
+
const oldest = this.vmRefreshQueue?.oldestBlockNumber();
|
|
2653
|
+
return oldest === undefined ? undefined : oldest - 1;
|
|
2654
|
+
}
|
|
2655
|
+
/** #2858 — the refresh lane's state, for `/api/status`. */
|
|
2656
|
+
getVmRefreshStatus() {
|
|
2657
|
+
const queue = this.vmRefreshQueue;
|
|
2658
|
+
const oldest = queue?.oldestBlockNumber();
|
|
2659
|
+
return {
|
|
2660
|
+
heldTargets: queue?.size ?? 0,
|
|
2661
|
+
persistCeiling: oldest === undefined ? null : oldest - 1,
|
|
2662
|
+
givenUpTotal: queue?.givenUpTotal ?? 0,
|
|
2663
|
+
refusedTotal: queue?.refusedTotal ?? 0,
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Local graphs whose root metadata graph describes this graph-scoped KA.
|
|
2668
|
+
* One subject-bound read; the caller filters them to reconcile targets.
|
|
2669
|
+
*/
|
|
2670
|
+
async localGraphsDescribingKnowledgeAsset(ual) {
|
|
2671
|
+
const result = await this.store.query(`SELECT DISTINCT ?g WHERE { GRAPH ?g { <${assertSafeIri(ual)}> `
|
|
2672
|
+
+ `<${GRAPH_KA_CONTENT_SCOPE_VERSION_PREDICATE}> ?version } }`, { source: 'agent.vmRefresh.holders', priority: 'background' });
|
|
2673
|
+
if (result.type !== 'bindings')
|
|
2674
|
+
return [];
|
|
2675
|
+
const prefix = 'did:dkg:context-graph:';
|
|
2676
|
+
const suffix = '/_meta';
|
|
2677
|
+
const holders = new Set();
|
|
2678
|
+
for (const row of result.bindings) {
|
|
2679
|
+
const graph = row['g'];
|
|
2680
|
+
if (typeof graph !== 'string' || !graph.startsWith(prefix) || !graph.endsWith(suffix)) {
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
const localCgId = graph.slice(prefix.length, graph.length - suffix.length);
|
|
2684
|
+
if (localCgId.length > 0 && contextGraphMetaUri(localCgId) === graph)
|
|
2685
|
+
holders.add(localCgId);
|
|
2686
|
+
}
|
|
2687
|
+
return [...holders];
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* What the refresh lane decides on for one KA in one graph: the root and
|
|
2691
|
+
* assertion version the confirmed graph-scoped copy recorded, the
|
|
2692
|
+
* transaction that confirmed it when it is receipt-backed, and whether the
|
|
2693
|
+
* workspace stages a newer version than that copy. A staged version only
|
|
2694
|
+
* delays the refresh (see `VM_REFRESH_STAGED_GRACE_MS`). A head at the
|
|
2695
|
+
* confirmed version or older stages nothing newer; a corrupt one is read
|
|
2696
|
+
* past by the exact fetch.
|
|
2697
|
+
*/
|
|
2698
|
+
async readVmRefreshLocalState(localCgId, ual) {
|
|
2699
|
+
const read = await readConfirmedGraphKnowledgeAssetMetadataEnvelope(this.store, {
|
|
2700
|
+
contextGraphId: localCgId,
|
|
2701
|
+
ual,
|
|
2702
|
+
});
|
|
2703
|
+
if (read.state !== 'confirmed')
|
|
2704
|
+
return { kind: 'none' };
|
|
2705
|
+
let staged = false;
|
|
2706
|
+
try {
|
|
2707
|
+
const head = await resolveKnowledgeAssetWorkspaceHead({
|
|
2708
|
+
store: this.store,
|
|
2709
|
+
graphManager: new GraphManager(this.store),
|
|
2710
|
+
contextGraphId: localCgId,
|
|
2711
|
+
kaUal: ual,
|
|
2712
|
+
...(read.envelope.subGraphName ? { subGraphName: read.envelope.subGraphName } : {}),
|
|
2713
|
+
});
|
|
2714
|
+
staged = head !== undefined
|
|
2715
|
+
&& BigInt(head.assertionVersion) > BigInt(read.envelope.assertionVersion);
|
|
2716
|
+
}
|
|
2717
|
+
catch (err) {
|
|
2718
|
+
if (!isKnowledgeAssetWorkspaceHeadCorruptError(err))
|
|
2719
|
+
throw err;
|
|
2720
|
+
}
|
|
2721
|
+
const transactionHash = read.envelope.transactionHash?.trim().toLowerCase();
|
|
2722
|
+
return {
|
|
2723
|
+
kind: 'confirmed',
|
|
2724
|
+
merkleRoot: ethers.hexlify(read.envelope.merkleRoot).toLowerCase(),
|
|
2725
|
+
assertionVersion: BigInt(read.envelope.assertionVersion),
|
|
2726
|
+
...(transactionHash ? { transactionHash } : {}),
|
|
2727
|
+
staged,
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
/**
|
|
2731
|
+
* #2858 — start this graph's refresh worker when it has due targets and
|
|
2732
|
+
* none is running. The worker runs beside the reconcile pass that starts
|
|
2733
|
+
* it, in the background RPC class and store lane, so the pass never waits
|
|
2734
|
+
* on a peer for a refresh. At most `VM_REFRESH_MAX_WORKERS` run at once; a
|
|
2735
|
+
* graph left waiting starts on its next pass or when a running worker ends.
|
|
2736
|
+
* Returns the graph's running worker, if any.
|
|
2737
|
+
*/
|
|
2738
|
+
startVmRefreshWorker(localCgId, onChainId, isTargetCurrent, signal) {
|
|
2739
|
+
// Narrow test agents built without the base constructor have neither.
|
|
2740
|
+
const queue = this.vmRefreshQueue;
|
|
2741
|
+
const workers = this.vmRefreshWorkers;
|
|
2742
|
+
if (queue === undefined || workers === undefined)
|
|
2743
|
+
return undefined;
|
|
2744
|
+
const running = workers.get(localCgId);
|
|
2745
|
+
if (running !== undefined)
|
|
2746
|
+
return running;
|
|
2747
|
+
if (signal?.aborted || !isTargetCurrent() || !queue.hasDue(localCgId))
|
|
2748
|
+
return undefined;
|
|
2749
|
+
if (workers.size >= DKGAgentBase.VM_REFRESH_MAX_WORKERS) {
|
|
2750
|
+
this.log.debug(createOperationContext('system'), `VM refresh for "${localCgId}" waits for a free worker`);
|
|
2751
|
+
return undefined;
|
|
2752
|
+
}
|
|
2753
|
+
const worker = withOwnedRpcRequestContext({ requestClass: 'background', ...(signal ? { signal } : {}) }, () => withDefaultStoreWorkPriority('background', () => this.runVmRefreshesForCg(localCgId, onChainId, isTargetCurrent, signal))).catch((err) => {
|
|
2754
|
+
this.log.warn(createOperationContext('system'), `VM refresh worker for "${localCgId}" stopped: `
|
|
2755
|
+
+ `${err instanceof Error ? err.message : String(err)}`);
|
|
2756
|
+
}).finally(() => {
|
|
2757
|
+
if (workers.get(localCgId) === worker)
|
|
2758
|
+
workers.delete(localCgId);
|
|
2759
|
+
if (signal?.aborted || this.vmReconcileRotationClosed)
|
|
2760
|
+
return;
|
|
2761
|
+
// Hand the freed slot on: this graph past its per-run bound, or a graph
|
|
2762
|
+
// that found every slot taken. Their passes start the workers.
|
|
2763
|
+
let free = DKGAgentBase.VM_REFRESH_MAX_WORKERS - workers.size;
|
|
2764
|
+
for (const dueCgId of queue.dueContextGraphIds()) {
|
|
2765
|
+
if (free <= 0)
|
|
2766
|
+
break;
|
|
2767
|
+
if (workers.has(dueCgId))
|
|
2768
|
+
continue;
|
|
2769
|
+
if (!this.isVmReconcileTargetSelected(dueCgId)) {
|
|
2770
|
+
// The graph left VM reconciliation; no pass will work its targets.
|
|
2771
|
+
queue.clearContextGraph(dueCgId);
|
|
2772
|
+
continue;
|
|
2773
|
+
}
|
|
2774
|
+
void this.vmReconcileScheduling?.triggerLive(dueCgId);
|
|
2775
|
+
free -= 1;
|
|
2776
|
+
}
|
|
2777
|
+
});
|
|
2778
|
+
workers.set(localCgId, worker);
|
|
2779
|
+
trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, worker);
|
|
2780
|
+
return worker;
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Work off up to `VM_REFRESH_MAX_PER_PASS` of this graph's due refresh
|
|
2784
|
+
* targets. Each attempt is bounded by `VM_REFRESH_ATTEMPT_TIMEOUT_MS` and
|
|
2785
|
+
* its peer steps by `VM_REFRESH_PEER_STEP_TIMEOUT_MS`, and every outcome is
|
|
2786
|
+
* settled and logged: an attempt that fails, times out or finds no holder
|
|
2787
|
+
* backs off and asks the next peer window when it is due again. A closing
|
|
2788
|
+
* lifecycle or a replaced reconcile target keeps the target for the next
|
|
2789
|
+
* worker. Never throws.
|
|
2790
|
+
*/
|
|
2791
|
+
async runVmRefreshesForCg(localCgId, onChainId, isTargetCurrent, signal) {
|
|
2792
|
+
// Narrow test agents built without the base constructor have no queue.
|
|
2793
|
+
const queue = this.vmRefreshQueue;
|
|
2794
|
+
if (queue === undefined)
|
|
2795
|
+
return;
|
|
2796
|
+
const due = queue.due(localCgId, DKGAgentBase.VM_REFRESH_MAX_PER_PASS);
|
|
2797
|
+
if (due.length === 0)
|
|
2798
|
+
return;
|
|
2799
|
+
const ctx = createOperationContext('system');
|
|
2800
|
+
const stopped = (target) => {
|
|
2801
|
+
if (!signal?.aborted && isTargetCurrent())
|
|
2802
|
+
return false;
|
|
2803
|
+
this.log.debug(ctx, `VM refresh of ${target.ual} in "${localCgId}" deferred: its reconcile target closed`);
|
|
2804
|
+
return true;
|
|
2805
|
+
};
|
|
2806
|
+
for (const target of due) {
|
|
2807
|
+
if (stopped(target))
|
|
2808
|
+
return;
|
|
2809
|
+
let attempt;
|
|
2810
|
+
let failed = false;
|
|
2811
|
+
try {
|
|
2812
|
+
attempt = await runBoundedOperation((attemptSignal) => this.refreshConfirmedVmCopy(target, onChainId, isTargetCurrent, attemptSignal), {
|
|
2813
|
+
timeoutMs: DKGAgentBase.VM_REFRESH_ATTEMPT_TIMEOUT_MS,
|
|
2814
|
+
label: `VM refresh of ${target.ual}`,
|
|
2815
|
+
...(signal ? { signal } : {}),
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
catch (err) {
|
|
2819
|
+
if (stopped(target))
|
|
2820
|
+
return;
|
|
2821
|
+
failed = true;
|
|
2822
|
+
attempt = {
|
|
2823
|
+
outcome: 'retry',
|
|
2824
|
+
detail: isBoundedOperationTimeoutError(err)
|
|
2825
|
+
? err.message
|
|
2826
|
+
: `failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
if (stopped(target))
|
|
2830
|
+
return;
|
|
2831
|
+
const retryAt = queue.settle(target, attempt.outcome);
|
|
2832
|
+
const subject = `${target.ual} in "${localCgId}"`;
|
|
2833
|
+
if (attempt.outcome === 'refreshed') {
|
|
2834
|
+
this.log.info(ctx, `VM refresh: ${subject} now holds the current version (${attempt.detail})`);
|
|
2835
|
+
}
|
|
2836
|
+
else if (attempt.outcome === 'retry') {
|
|
2837
|
+
const retry = retryAt === undefined
|
|
2838
|
+
? 'retrying on a later pass'
|
|
2839
|
+
: `retrying in ${Math.max(0, Math.round((retryAt - Date.now()) / 1000))}s`;
|
|
2840
|
+
const message = `VM refresh of ${subject} did not complete (${attempt.detail}); ${retry}`;
|
|
2841
|
+
if (failed)
|
|
2842
|
+
this.log.warn(ctx, message);
|
|
2843
|
+
else
|
|
2844
|
+
this.log.info(ctx, message);
|
|
2845
|
+
}
|
|
2846
|
+
else {
|
|
2847
|
+
this.log.debug(ctx, `VM refresh of ${subject} settled as ${attempt.outcome}: ${attempt.detail}`);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* One refresh attempt: settle from local state when the copy already holds
|
|
2853
|
+
* the event's root, otherwise read the chain's current root. A copy at that
|
|
2854
|
+
* root is current (the event was older or superseded — nothing is rolled
|
|
2855
|
+
* back); a different one is fetched as the exact current version, which
|
|
2856
|
+
* replaces the older assertion in place. `signal` is the attempt's own: it
|
|
2857
|
+
* stops the fetch at the attempt deadline.
|
|
2858
|
+
*/
|
|
2859
|
+
async refreshConfirmedVmCopy(target, onChainId, isTargetCurrent, signal) {
|
|
2860
|
+
const local = await this.readVmRefreshLocalState(target.localCgId, target.ual);
|
|
2861
|
+
if (local.kind === 'none') {
|
|
2862
|
+
return { outcome: 'not-applicable', detail: 'no confirmed copy is held any more' };
|
|
2863
|
+
}
|
|
2864
|
+
// A version still staged here was not promoted by the lane that staged it
|
|
2865
|
+
// (or nothing on this node promotes it). The exact fetch below verifies it
|
|
2866
|
+
// against the chain like any other copy, and a staged version newer than
|
|
2867
|
+
// the chain's stays in the workspace: the fetch writes only Verifiable
|
|
2868
|
+
// Memory.
|
|
2869
|
+
// A copy at the update's root is current, unless the nudge found it
|
|
2870
|
+
// there already: then only the chain's version can tell (a repeated root).
|
|
2871
|
+
if (!target.checkVersion && local.merkleRoot === target.merkleRoot) {
|
|
2872
|
+
return { outcome: 'current', detail: 'the copy holds the update root' };
|
|
2873
|
+
}
|
|
2874
|
+
if (typeof this.chain.getLatestMerkleRoot !== 'function') {
|
|
2875
|
+
return { outcome: 'not-applicable', detail: 'the chain adapter reads no latest root' };
|
|
2876
|
+
}
|
|
2877
|
+
let chainRoot = ethers.hexlify(await this.chain.getLatestMerkleRoot(target.kaId, { signal })).toLowerCase();
|
|
2878
|
+
if (!isTargetCurrent())
|
|
2879
|
+
throw new VmReconcileQueueClosedError();
|
|
2880
|
+
let chainVersion;
|
|
2881
|
+
if (chainRoot === local.merkleRoot) {
|
|
2882
|
+
// An older or superseded event, a read that has not seen the update yet
|
|
2883
|
+
// (a lagging endpoint, a confirmation depth above one), or an update
|
|
2884
|
+
// that repeated the copy's root. Settle only on a coherent view at or
|
|
2885
|
+
// after the update's block, by version as well as root.
|
|
2886
|
+
const confirmed = await this.confirmVmRefreshCurrentAtEventBlock(target, local, isTargetCurrent, signal);
|
|
2887
|
+
if (confirmed.kind === 'settled')
|
|
2888
|
+
return confirmed.attempt;
|
|
2889
|
+
// The pinned view names a newer version: fetch it.
|
|
2890
|
+
chainRoot = confirmed.chainRoot;
|
|
2891
|
+
chainVersion = confirmed.rootCount;
|
|
2892
|
+
}
|
|
2893
|
+
let result;
|
|
2894
|
+
try {
|
|
2895
|
+
result = await this.runExactAssetFetchForContextGraph(target.localCgId, [target.ual], {
|
|
2896
|
+
isCurrent: () => isTargetCurrent() && !signal?.aborted,
|
|
2897
|
+
...(signal ? { signal } : {}),
|
|
2898
|
+
expectedOnChainId: onChainId,
|
|
2899
|
+
maxPeers: DKGAgentBase.VM_RECONCILE_EXACT_PEER_MAX,
|
|
2900
|
+
// Each retry asks the next window of candidates, so a holder outside
|
|
2901
|
+
// the first few peers is still reached.
|
|
2902
|
+
peerWindowIndex: target.failures,
|
|
2903
|
+
// An update keeps the UAL. Its old responder session can outlive the
|
|
2904
|
+
// previous version, so every refresh attempt needs a new snapshot.
|
|
2905
|
+
forceFreshExactSession: true,
|
|
2906
|
+
// A candidate that cannot be reached (after a restart, the curator
|
|
2907
|
+
// is often not connected yet) yields to the next one.
|
|
2908
|
+
peerStepTimeoutMs: DKGAgentBase.VM_REFRESH_PEER_STEP_TIMEOUT_MS,
|
|
2909
|
+
// Evidence that has not seen the update would settle on the old version.
|
|
2910
|
+
...((target.proofBlockNumber ?? target.blockNumber) === undefined
|
|
2911
|
+
? {} : { minVersionBlock: target.proofBlockNumber ?? target.blockNumber }),
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
catch (err) {
|
|
2915
|
+
// This adapter cannot prove an exact version at all. Anything else,
|
|
2916
|
+
// including a conflict such as a version snapshot the endpoints did
|
|
2917
|
+
// not agree on, is retried after backoff.
|
|
2918
|
+
if (err instanceof VmReconcileUnavailableError) {
|
|
2919
|
+
return { outcome: 'not-applicable', detail: 'this adapter cannot prove an exact version' };
|
|
2920
|
+
}
|
|
2921
|
+
if (err instanceof ExactAssetVersionBehindError) {
|
|
2922
|
+
return { outcome: 'retry', detail: err.message };
|
|
2923
|
+
}
|
|
2924
|
+
throw err;
|
|
2925
|
+
}
|
|
2926
|
+
const status = result.items[0]?.status;
|
|
2927
|
+
if (status === 'materialized' || status === 'fetched') {
|
|
2928
|
+
return { outcome: 'refreshed', detail: `${status} after ${result.peerAttempts} peer attempt(s)` };
|
|
2929
|
+
}
|
|
2930
|
+
if (status === 'already-present') {
|
|
2931
|
+
return { outcome: 'current', detail: 'the exact inspection found the current version' };
|
|
2932
|
+
}
|
|
2933
|
+
// The exact inspection can refuse a refreshed copy while a workspace head
|
|
2934
|
+
// names another version (an older one, or one staged ahead of the chain);
|
|
2935
|
+
// the confirmed copy itself is the answer here.
|
|
2936
|
+
const after = await this.readVmRefreshLocalState(target.localCgId, target.ual);
|
|
2937
|
+
if (after.kind === 'confirmed'
|
|
2938
|
+
&& after.merkleRoot === chainRoot
|
|
2939
|
+
&& (chainVersion === undefined || after.assertionVersion >= chainVersion)) {
|
|
2940
|
+
return { outcome: 'refreshed', detail: 'the confirmed copy holds the chain root' };
|
|
2941
|
+
}
|
|
2942
|
+
return {
|
|
2943
|
+
outcome: 'retry',
|
|
2944
|
+
detail: `no peer served the current version; ${result.peerAttempts} peer attempt(s)`,
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
/**
|
|
2948
|
+
* #2858 — the copy holds the root a live chain read returned. That read can
|
|
2949
|
+
* predate the update (a lagging endpoint, a confirmation depth above one),
|
|
2950
|
+
* and a root names content, not a version: an update may repeat an earlier
|
|
2951
|
+
* root. So settle from a coherent view at or after the event's block:
|
|
2952
|
+
* `current` when it holds the copy's root at a version no newer than the
|
|
2953
|
+
* copy's, `retry` while it has not reached that block, and the root and
|
|
2954
|
+
* version it shows otherwise, which the caller then fetches. On an adapter
|
|
2955
|
+
* that reads no pinned view, the live read settles it.
|
|
2956
|
+
*/
|
|
2957
|
+
async confirmVmRefreshCurrentAtEventBlock(target, local, isTargetCurrent, signal) {
|
|
2958
|
+
const current = {
|
|
2959
|
+
kind: 'settled',
|
|
2960
|
+
attempt: { outcome: 'current', detail: 'the copy holds the chain root and version' },
|
|
2961
|
+
};
|
|
2962
|
+
if (typeof this.chain.readKnowledgeAssetVersionSnapshot !== 'function')
|
|
2963
|
+
return current;
|
|
2964
|
+
const view = await this.chain.readKnowledgeAssetVersionSnapshot(target.kaId, { signal });
|
|
2965
|
+
if (!isTargetCurrent())
|
|
2966
|
+
throw new VmReconcileQueueClosedError();
|
|
2967
|
+
if (view === null) {
|
|
2968
|
+
return {
|
|
2969
|
+
kind: 'settled',
|
|
2970
|
+
attempt: { outcome: 'retry', detail: 'no coherent chain view confirms the copy' },
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
const proofBlock = target.proofBlockNumber ?? target.blockNumber;
|
|
2974
|
+
if (proofBlock !== undefined && view.blockNumber < proofBlock) {
|
|
2975
|
+
return {
|
|
2976
|
+
kind: 'settled',
|
|
2977
|
+
attempt: {
|
|
2978
|
+
outcome: 'retry',
|
|
2979
|
+
detail: `the chain view at block ${view.blockNumber} is behind the update's block `
|
|
2980
|
+
+ `${proofBlock}`,
|
|
2981
|
+
},
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
const viewRoot = view.latestRoot.toLowerCase();
|
|
2985
|
+
return viewRoot === local.merkleRoot && view.rootCount <= local.assertionVersion
|
|
2986
|
+
? current
|
|
2987
|
+
: { kind: 'newer', chainRoot: viewRoot, rootCount: view.rootCount };
|
|
2988
|
+
}
|
|
2467
2989
|
/**
|
|
2468
2990
|
* Canonical evidence-gated VM reconciliation operation for one CG.
|
|
2469
2991
|
*
|
|
@@ -2529,6 +3051,31 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2529
3051
|
throw new VmReconcileQueueClosedError();
|
|
2530
3052
|
if (!canRead)
|
|
2531
3053
|
throw new ContextGraphNotFoundError(localCgId);
|
|
3054
|
+
// Bound through the prototype, as narrow hosts that borrow this operator
|
|
3055
|
+
// method do not carry the shared tail.
|
|
3056
|
+
return SwmHostModeMethods.prototype.runExactAssetFetchForContextGraph.call(this, localCgId, requestedUals, {
|
|
3057
|
+
isCurrent,
|
|
3058
|
+
...(signal ? { signal } : {}),
|
|
3059
|
+
...(options.peerIds === undefined ? {} : { peerIds: options.peerIds }),
|
|
3060
|
+
...(subscription.onChainId ? { expectedOnChainId: subscription.onChainId } : {}),
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
/**
|
|
3064
|
+
* The exact-asset fetch once its caller has proved read authority and owns
|
|
3065
|
+
* the lifecycle: chain evidence, local inspection against the current root
|
|
3066
|
+
* and version, a bounded peer traversal, and re-inspection. Shared by the
|
|
3067
|
+
* operator fetch above and the VM refresh lane ({@link refreshConfirmedVmCopy}).
|
|
3068
|
+
*/
|
|
3069
|
+
async runExactAssetFetchForContextGraph(localCgId, requestedUals, options) {
|
|
3070
|
+
const { isCurrent, signal, peerStepTimeoutMs } = options;
|
|
3071
|
+
/** One network step, under `peerStepTimeoutMs` when the caller set it. */
|
|
3072
|
+
const runPeerStep = (label, step) => (peerStepTimeoutMs === undefined
|
|
3073
|
+
? step(signal)
|
|
3074
|
+
: runBoundedOperation(step, {
|
|
3075
|
+
timeoutMs: peerStepTimeoutMs,
|
|
3076
|
+
label,
|
|
3077
|
+
...(signal ? { signal } : {}),
|
|
3078
|
+
}));
|
|
2532
3079
|
if (typeof this.chain.getKAContextGraphId !== 'function'
|
|
2533
3080
|
|| typeof this.chain.readKnowledgeAssetVersionSnapshot !== 'function') {
|
|
2534
3081
|
throw new VmReconcileUnavailableError();
|
|
@@ -2541,7 +3088,11 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2541
3088
|
contextGraphId: localCgId,
|
|
2542
3089
|
requestedUals,
|
|
2543
3090
|
...(options.peerIds === undefined ? {} : { peerIds: options.peerIds }),
|
|
2544
|
-
...(
|
|
3091
|
+
...(options.expectedOnChainId ? { expectedOnChainId: options.expectedOnChainId } : {}),
|
|
3092
|
+
...(options.maxPeers === undefined ? {} : { maxPeers: options.maxPeers }),
|
|
3093
|
+
...(options.minVersionBlock === undefined
|
|
3094
|
+
? {}
|
|
3095
|
+
: { minVersionBlock: options.minVersionBlock }),
|
|
2545
3096
|
}, {
|
|
2546
3097
|
chainId: this.chain.chainId,
|
|
2547
3098
|
signal,
|
|
@@ -2578,34 +3129,59 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2578
3129
|
return 'missing';
|
|
2579
3130
|
},
|
|
2580
3131
|
resolvePeerIds: async () => {
|
|
2581
|
-
const curatorResolution = await this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
3132
|
+
const curatorResolution = await runPeerStep(`Curator resolution for "${localCgId}"`, (stepSignal) => this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
2582
3133
|
maxPeerIds: MAX_CONTEXT_GRAPH_ASSET_FETCH_PEERS,
|
|
2583
|
-
signal,
|
|
3134
|
+
signal: stepSignal,
|
|
2584
3135
|
isCurrent,
|
|
2585
|
-
}).catch(() =>
|
|
3136
|
+
})).catch((error) => {
|
|
3137
|
+
if (isBoundedOperationTimeoutError(error)) {
|
|
3138
|
+
this.log.info(ctx, `Exact asset fetch: ${error.message}; asking connected peers`);
|
|
3139
|
+
}
|
|
3140
|
+
return { peerIds: [] };
|
|
3141
|
+
});
|
|
2586
3142
|
if (!isCurrent())
|
|
2587
3143
|
throw new VmReconcileQueueClosedError();
|
|
2588
3144
|
const connectedPeerIds = this.node?.libp2p?.getConnections?.()
|
|
2589
3145
|
?.map((connection) => connection.remotePeer.toString()) ?? [];
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3146
|
+
const usable = (peerId) => Boolean(peerId && peerId !== this.peerId);
|
|
3147
|
+
const curators = [...new Set(curatorResolution.peerIds.filter(usable))];
|
|
3148
|
+
const curatorSet = new Set(curators);
|
|
3149
|
+
const ordinary = [...new Set([this.preferredSyncPeers.get(localCgId), ...connectedPeerIds].filter(usable))].filter((peerId) => !curatorSet.has(peerId));
|
|
3150
|
+
const windowIndex = options.peerWindowIndex !== undefined
|
|
3151
|
+
&& Number.isSafeInteger(options.peerWindowIndex)
|
|
3152
|
+
&& options.peerWindowIndex > 0
|
|
3153
|
+
? options.peerWindowIndex
|
|
3154
|
+
: 0;
|
|
3155
|
+
const windowSize = exactAssetFetchPeerWindow(options.maxPeers);
|
|
3156
|
+
const curatorSlots = Math.min(curators.length, windowSize);
|
|
3157
|
+
return [
|
|
3158
|
+
...rotatePeerIds(curators, windowIndex * curatorSlots),
|
|
3159
|
+
...rotatePeerIds(ordinary, windowIndex * (windowSize - curatorSlots)),
|
|
3160
|
+
];
|
|
2595
3161
|
},
|
|
2596
|
-
|
|
2597
|
-
|
|
3162
|
+
// A step past its bound rejects; the traversal logs it and moves on to
|
|
3163
|
+
// the next candidate.
|
|
3164
|
+
preparePeer: (peerId) => runPeerStep('Peer preparation', async (stepSignal) => {
|
|
3165
|
+
await this.ensurePeerConnected(peerId, { signal: stepSignal });
|
|
2598
3166
|
if (!isCurrent())
|
|
2599
3167
|
throw new VmReconcileQueueClosedError();
|
|
2600
3168
|
const remotePeer = this.node.libp2p.getConnections()
|
|
2601
3169
|
.find((connection) => connection.remotePeer.toString() === peerId)
|
|
2602
3170
|
?.remotePeer;
|
|
2603
|
-
if (!remotePeer || !(await this.waitForSyncProtocol(remotePeer,
|
|
3171
|
+
if (!remotePeer || !(await this.waitForSyncProtocol(remotePeer, stepSignal)))
|
|
2604
3172
|
return false;
|
|
2605
|
-
return this.ensurePeerAdmittedForRecovery(peerId, ctx, 'Exact asset fetch peer',
|
|
2606
|
-
},
|
|
3173
|
+
return this.ensurePeerAdmittedForRecovery(peerId, ctx, 'Exact asset fetch peer', stepSignal);
|
|
3174
|
+
}),
|
|
2607
3175
|
fetchFromPeer: async (peerId, uals) => {
|
|
2608
|
-
await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, [...uals], {
|
|
3176
|
+
await this.syncExactKnowledgeAssetsFromPeerDetailed(peerId, localCgId, [...uals], {
|
|
3177
|
+
signal,
|
|
3178
|
+
isCurrent,
|
|
3179
|
+
// The same UAL can move to a new root while the responder still
|
|
3180
|
+
// holds its older exact-asset page session. A refresh must start
|
|
3181
|
+
// from a new snapshot on every attempt; its pages still share one
|
|
3182
|
+
// session within this invocation.
|
|
3183
|
+
forceFreshExactSession: options.forceFreshExactSession === true,
|
|
3184
|
+
});
|
|
2609
3185
|
},
|
|
2610
3186
|
flush: async () => {
|
|
2611
3187
|
await this.store.flush?.({
|
|
@@ -2638,12 +3214,18 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2638
3214
|
this.log.debug(createOperationContext('system'), `VM reconcile for "${localCgId}" stopped: superseded by cleartext adoption of "${supersedingId}"`);
|
|
2639
3215
|
return;
|
|
2640
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;
|
|
2641
3221
|
this.log.warn(createOperationContext('system'), `VM reconcile for "${localCgId}" failed; retrying on the periodic sweep: ${err instanceof Error ? err.message : String(err)}`);
|
|
2642
3222
|
}, {
|
|
2643
3223
|
concurrency: DKGAgentBase.VM_RECONCILE_CONCURRENCY,
|
|
2644
3224
|
maxPending: DKGAgentBase.VM_RECONCILE_QUEUE_MAX_PENDING,
|
|
2645
3225
|
maxForegroundBurst: DKGAgentBase.VM_RECONCILE_MAX_FOREGROUND_BURST,
|
|
2646
|
-
|
|
3226
|
+
discoveryBatchSize: DKGAgentBase.VM_RECONCILE_UNBOUND_BATCH_SIZE,
|
|
3227
|
+
periodicBoundBatchSize: DKGAgentBase.VM_RECONCILE_PERIODIC_BOUND_BATCH_SIZE,
|
|
3228
|
+
});
|
|
2647
3229
|
this.vmReconcileScheduling = scheduling;
|
|
2648
3230
|
}
|
|
2649
3231
|
return scheduling;
|
|
@@ -2668,11 +3250,46 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2668
3250
|
? work()
|
|
2669
3251
|
: withOwnedRpcRequestContext({ requestClass: 'background', ...(lifecycleSignal ? { signal: lifecycleSignal } : {}) }, () => withDefaultStoreWorkPriority('background', work));
|
|
2670
3252
|
const physicalRun = runInLane(async () => {
|
|
2671
|
-
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;
|
|
2672
3284
|
const isTargetCurrent = () => isLifecycleCurrent()
|
|
2673
3285
|
&& this.isVmReconcileTargetCurrent(localCgId, target, lifecycleGeneration);
|
|
2674
3286
|
if (!isTargetCurrent())
|
|
2675
3287
|
throw new VmReconcileQueueClosedError();
|
|
3288
|
+
// #2858 — confirmed copies behind an on-chain update. The ordinal cursor
|
|
3289
|
+
// never revisits a settled ordinal, so a worker beside this pass
|
|
3290
|
+
// refreshes them. The pass never waits on it, and a slice that fails
|
|
3291
|
+
// does not hold it back.
|
|
3292
|
+
this.startVmRefreshWorker(localCgId, target.onChainId, isTargetCurrent, lifecycleSignal);
|
|
2676
3293
|
// Reconcile on a private cursor snapshot. The caller-facing abort race may
|
|
2677
3294
|
// finish before an adapter physically settles; a stale continuation must
|
|
2678
3295
|
// never mutate the live cursor or persist a watermark into a new binding.
|
|
@@ -2683,10 +3300,12 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2683
3300
|
};
|
|
2684
3301
|
let pendingWatermark;
|
|
2685
3302
|
let result;
|
|
3303
|
+
let engineTimings;
|
|
2686
3304
|
try {
|
|
2687
3305
|
result = await reconcileContextGraph(this.createVmReconcileDeps(localCgId, lifecycleGeneration, target, lifecycleSignal, {
|
|
2688
3306
|
identityCursor: target.cursor,
|
|
2689
3307
|
persistWatermark: (_lcg, watermark) => { pendingWatermark = watermark; },
|
|
3308
|
+
observePassTimings: (timings) => { engineTimings = timings; },
|
|
2690
3309
|
}), workingCursor, localCgId, target.onChainCgId);
|
|
2691
3310
|
}
|
|
2692
3311
|
catch (err) {
|
|
@@ -2706,16 +3325,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2706
3325
|
}
|
|
2707
3326
|
if (!isTargetCurrent())
|
|
2708
3327
|
throw new VmReconcileQueueClosedError();
|
|
3328
|
+
let flushMs = 0;
|
|
2709
3329
|
if (result.reconciled > 0 || pendingWatermark !== undefined) {
|
|
3330
|
+
const flushStartedAt = performance.now();
|
|
2710
3331
|
await this.store.flush?.({
|
|
2711
3332
|
priority: 'background',
|
|
2712
3333
|
source: 'agent.vmReconcile.materialization.flush',
|
|
2713
3334
|
});
|
|
3335
|
+
flushMs = performance.now() - flushStartedAt;
|
|
2714
3336
|
if (!isTargetCurrent())
|
|
2715
3337
|
throw new VmReconcileQueueClosedError();
|
|
2716
3338
|
}
|
|
3339
|
+
let watermarkMs = 0;
|
|
2717
3340
|
if (pendingWatermark !== undefined) {
|
|
3341
|
+
const watermarkStartedAt = performance.now();
|
|
2718
3342
|
await this.persistVmReconcileWatermark(localCgId, pendingWatermark, target);
|
|
3343
|
+
watermarkMs = performance.now() - watermarkStartedAt;
|
|
2719
3344
|
if (!isTargetCurrent())
|
|
2720
3345
|
throw new VmReconcileQueueClosedError();
|
|
2721
3346
|
}
|
|
@@ -2724,11 +3349,40 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2724
3349
|
target.cursor.scanOrdinal = workingCursor.scanOrdinal;
|
|
2725
3350
|
const response = this.toContextGraphReconcileResult(localCgId, source, target, result);
|
|
2726
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
|
+
});
|
|
2727
3364
|
// The reconciler owns the continuation policy: productive slices, stale
|
|
2728
3365
|
// bindings, and explicit provider rotations continue immediately, while
|
|
2729
3366
|
// pending-only historical inventory yields to the periodic sweep.
|
|
2730
|
-
|
|
2731
|
-
|
|
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);
|
|
2732
3386
|
}
|
|
2733
3387
|
else if (isTargetCurrent()) {
|
|
2734
3388
|
// RS heal is bounded, best-effort maintenance. Run it only after the
|
|
@@ -2749,6 +3403,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2749
3403
|
if (!isTargetCurrent())
|
|
2750
3404
|
throw new VmReconcileQueueClosedError();
|
|
2751
3405
|
}
|
|
3406
|
+
noteVmReconcilePassEnd(this, localCgId, performance.now());
|
|
2752
3407
|
return response;
|
|
2753
3408
|
});
|
|
2754
3409
|
trackVmReconcilePhysicalRun(this.vmReconcilePhysicalRuns, physicalRun);
|
|
@@ -2835,8 +3490,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2835
3490
|
// Central defense for periodic, live-chain, and manual reconciliation.
|
|
2836
3491
|
// Every dispatcher entry point converges here and must independently prove
|
|
2837
3492
|
// read authority. Never let a persisted subscription authorize itself.
|
|
3493
|
+
let authorityDecision;
|
|
2838
3494
|
const authorityRead = withRpcUsageSite(CG_AUTH_RPC_SITES.vmReconcile, () => this.canReadContextGraph(localCgId, {
|
|
2839
3495
|
allowSubscriptionFallback: false,
|
|
3496
|
+
onReadAuthorityDecision: (decision) => { authorityDecision = decision; },
|
|
2840
3497
|
}));
|
|
2841
3498
|
// Cancellation releases the dispatcher worker, but an underlying store/RPC
|
|
2842
3499
|
// read may ignore it. Keep that physical dependency in the shutdown drain.
|
|
@@ -2844,8 +3501,16 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
2844
3501
|
const canRead = await raceVmReconcileAbort(authorityRead, signal).catch(() => false);
|
|
2845
3502
|
if (!isCurrent())
|
|
2846
3503
|
throw new VmReconcileQueueClosedError();
|
|
2847
|
-
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
|
+
}
|
|
2848
3512
|
throw new ContextGraphNotFoundError(localCgId);
|
|
3513
|
+
}
|
|
2849
3514
|
if (this.contextGraphBindingState.currentBindingFor(localCgId, sub) === undefined
|
|
2850
3515
|
&& sub.subscribed) {
|
|
2851
3516
|
await this.selfPrimeSubscriptionOnChainId(localCgId, sub, undefined, isCurrent, signal);
|
|
@@ -3136,7 +3801,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3136
3801
|
reconcileOrdinal: (lcg, ocg, ordinal, headBlock, context) => this.reconcileChainOrdinal(lcg, ocg, ordinal, headBlock, {
|
|
3137
3802
|
isTargetCurrent,
|
|
3138
3803
|
revalidateTarget,
|
|
3139
|
-
deferActiveFetch: true,
|
|
3140
3804
|
rememberFinalizedEvidence: context === undefined
|
|
3141
3805
|
|| context.headOrdinal - ordinal <= DKGAgentBase.VM_RECONCILE_BATCH_SIZE,
|
|
3142
3806
|
}),
|
|
@@ -3171,6 +3835,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3171
3835
|
},
|
|
3172
3836
|
confirmationDepth: DKGAgentBase.VM_RECONCILE_CONFIRMATION_DEPTH,
|
|
3173
3837
|
log: (msg) => this.log.info(createOperationContext('system'), msg),
|
|
3838
|
+
...(execution?.observePassTimings ? { observePassTimings: execution.observePassTimings } : {}),
|
|
3174
3839
|
};
|
|
3175
3840
|
}
|
|
3176
3841
|
async persistVmReconcileWatermark(localCgId, watermark, target) {
|
|
@@ -3217,7 +3882,11 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3217
3882
|
});
|
|
3218
3883
|
return;
|
|
3219
3884
|
}
|
|
3220
|
-
|
|
3885
|
+
// Authoritative progress follows the subscription's own lifetime. A durable
|
|
3886
|
+
// row (always-on member intent, or a Core's host-only obligation) is saved
|
|
3887
|
+
// before the live cursor moves. An on-demand subscription writes nothing:
|
|
3888
|
+
// like the subscription itself, its progress lives only in this process.
|
|
3889
|
+
await this.persistContextGraphSyncStateStrict(localCgId, { ...sub, lastReconciledOrdinal: watermark }, 'VM reconcile cursor', isTargetCurrent);
|
|
3221
3890
|
if (!isTargetCurrent())
|
|
3222
3891
|
return;
|
|
3223
3892
|
sub.lastReconciledOrdinal = watermark;
|
|
@@ -3545,452 +4214,56 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
3545
4214
|
return { status: 'skipped', reason: 'failed' };
|
|
3546
4215
|
}
|
|
3547
4216
|
}
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
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,
|
|
4239
|
+
});
|
|
3556
4240
|
}
|
|
3557
|
-
|
|
3558
|
-
return
|
|
3559
|
-
metaGraph: contextGraphWorkspaceMetaGraphUri(localCgId),
|
|
3560
|
-
dataGraph: contextGraphWorkspaceGraphUri(localCgId),
|
|
3561
|
-
}];
|
|
4241
|
+
vmReconcileRotationNow() {
|
|
4242
|
+
return performance.now();
|
|
3562
4243
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
// `listSubGraphs` finds sub-graphs by their data or `_meta` graphs; a
|
|
3566
|
-
// hosted-only core holds only `<cg>/<sub>/_shared_memory*` copies, which
|
|
3567
|
-
// its StorageACK ledger names.
|
|
3568
|
-
const subGraphs = [...new Set([
|
|
3569
|
-
...await graphManager.listSubGraphs(localCgId),
|
|
3570
|
-
...await this.storageAckLedgerNamespaceSubGraphs(localCgId),
|
|
3571
|
-
])];
|
|
3572
|
-
const subGraphNamespaces = subGraphs
|
|
3573
|
-
.map((sg) => ({
|
|
3574
|
-
metaGraph: graphManager.sharedMemoryMetaUri(localCgId, sg),
|
|
3575
|
-
dataGraph: graphManager.sharedMemoryUri(localCgId, sg),
|
|
3576
|
-
}))
|
|
3577
|
-
.sort((a, b) => `${a.metaGraph}\0${a.dataGraph}`.localeCompare(`${b.metaGraph}\0${b.dataGraph}`));
|
|
3578
|
-
return [
|
|
3579
|
-
...this.vmReconcileRootSwmCandidateNamespaces(localCgId),
|
|
3580
|
-
...subGraphNamespaces,
|
|
3581
|
-
];
|
|
4244
|
+
vmReconcileRotationSlotKey(target) {
|
|
4245
|
+
return `${target.localCgId}\0${target.onChainCgId}\0${target.ordinal}`;
|
|
3582
4246
|
}
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
return { namespaces: await this.collectVmReconcileSwmCandidateNamespaces(localCgId), complete: true };
|
|
3586
|
-
}
|
|
3587
|
-
catch {
|
|
3588
|
-
return { namespaces: this.vmReconcileRootSwmCandidateNamespaces(localCgId), complete: false };
|
|
3589
|
-
}
|
|
4247
|
+
clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal) {
|
|
4248
|
+
this.vmReconcileRotationState.delete(`${localCgId}\0${onChainCgId.toString()}\0${ordinal}`);
|
|
3590
4249
|
}
|
|
3591
|
-
|
|
3592
|
-
return
|
|
3593
|
-
.map((namespace) => `${namespace.metaGraph}\0${namespace.dataGraph}`)
|
|
3594
|
-
.sort()
|
|
3595
|
-
.join('\n');
|
|
4250
|
+
vmReconcileRotationFingerprint(target) {
|
|
4251
|
+
return `${target.ual}\0${target.merkleRoot.toLowerCase()}`;
|
|
3596
4252
|
}
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
if (typeof getConnections !== 'function')
|
|
3604
|
-
return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
|
|
3605
|
-
const peerIds = [...new Map(getConnections.call(libp2p)
|
|
3606
|
-
.map((connection) => [connection.remotePeer?.toString(), connection.remotePeer])
|
|
3607
|
-
.filter((entry) => typeof entry[0] === 'string' && entry[0].length > 0 && !!entry[1])).keys()].sort();
|
|
3608
|
-
const orderedPeers = this.selectCatchupPeers(peerIds.map((peerId) => ({ toString: () => peerId })), preferredPeerId, isPrivateContextGraph);
|
|
3609
|
-
return createVmReconcilePeerTopology({
|
|
3610
|
-
preferredPeerId: preferredPeerId ?? null,
|
|
3611
|
-
privateOnly: isPrivateContextGraph,
|
|
3612
|
-
peers: orderedPeers.map((peer) => {
|
|
3613
|
-
const peerId = peer.toString();
|
|
3614
|
-
return {
|
|
3615
|
-
peerId,
|
|
3616
|
-
core: this.peerCapabilityRegistry.supportsCore(peerId),
|
|
3617
|
-
};
|
|
3618
|
-
}),
|
|
3619
|
-
});
|
|
3620
|
-
}
|
|
3621
|
-
catch {
|
|
3622
|
-
return UNREADABLE_VM_RECONCILE_PEER_TOPOLOGY;
|
|
4253
|
+
vmReconcileObservedCandidatePeerIds(localCgId) {
|
|
4254
|
+
const curatorOrder = this.vmReconcileCuratorPeersByCg.get(localCgId) ?? [];
|
|
4255
|
+
const libp2p = this.node?.libp2p;
|
|
4256
|
+
const getConnections = libp2p?.getConnections;
|
|
4257
|
+
if (typeof getConnections !== 'function') {
|
|
4258
|
+
return curatorOrder.slice(0, DKGAgentBase.VM_RECONCILE_EXACT_ROSTER_MAX);
|
|
3623
4259
|
}
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
// its one-time seed. It lets large (> fingerprint cap) stores reuse a
|
|
3632
|
-
// proven negative without reconstructing or sampling their SWM content.
|
|
3633
|
-
const changelog = asChangelogReader(this.store);
|
|
3634
|
-
if (changelog) {
|
|
3635
|
-
const head = await changelog.changelogHead({
|
|
3636
|
-
priority: 'background',
|
|
3637
|
-
source: 'agent.vmReconcile.negativeGeneration',
|
|
3638
|
-
});
|
|
3639
|
-
return `changelog:${head.era}:${head.seq}`;
|
|
3640
|
-
}
|
|
3641
|
-
const parts = [];
|
|
3642
|
-
const digestRows = (rows) => createHash('sha256').update(rows.join('\n'), 'utf8').digest('hex');
|
|
3643
|
-
const maxRows = DKGAgentBase.VM_RECONCILE_SWM_GEN_FINGERPRINT_MAX_ROWS;
|
|
3644
|
-
const isTooLarge = (rows) => rows.length > maxRows;
|
|
3645
|
-
for (const namespace of candidateNamespaces) {
|
|
3646
|
-
const metaGraph = assertSafeIri(namespace.metaGraph);
|
|
3647
|
-
const dataGraph = assertSafeIri(namespace.dataGraph);
|
|
3648
|
-
const operationRows = await this.store.query(`SELECT ?op ?root ?ts WHERE {
|
|
3649
|
-
GRAPH <${metaGraph}> {
|
|
3650
|
-
?op <http://dkg.io/ontology/rootEntity> ?root .
|
|
3651
|
-
OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
|
|
3652
|
-
}
|
|
3653
|
-
} ORDER BY ?op ?root ?ts LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.operations' });
|
|
3654
|
-
if (operationRows.type !== 'bindings')
|
|
3655
|
-
return null;
|
|
3656
|
-
if (isTooLarge(operationRows.bindings))
|
|
3657
|
-
return null;
|
|
3658
|
-
const operations = operationRows.bindings
|
|
3659
|
-
.map((row) => [
|
|
3660
|
-
String(row['op'] ?? ''),
|
|
3661
|
-
String(row['root'] ?? ''),
|
|
3662
|
-
String(row['ts'] ?? ''),
|
|
3663
|
-
].join('\0'))
|
|
3664
|
-
.sort();
|
|
3665
|
-
const dataRows = await this.store.query(`SELECT ?s ?p ?o WHERE {
|
|
3666
|
-
GRAPH <${dataGraph}> { ?s ?p ?o . }
|
|
3667
|
-
} ORDER BY ?s ?p ?o LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.data' });
|
|
3668
|
-
if (dataRows.type !== 'bindings')
|
|
3669
|
-
return null;
|
|
3670
|
-
if (isTooLarge(dataRows.bindings))
|
|
3671
|
-
return null;
|
|
3672
|
-
const dataTriples = dataRows.bindings
|
|
3673
|
-
.map((row) => [
|
|
3674
|
-
String(row['s'] ?? ''),
|
|
3675
|
-
String(row['p'] ?? ''),
|
|
3676
|
-
String(row['o'] ?? ''),
|
|
3677
|
-
].join('\0'))
|
|
3678
|
-
.sort();
|
|
3679
|
-
const privateRootRows = await this.store.query(`SELECT ?privateEntity ?privateRoot WHERE {
|
|
3680
|
-
GRAPH <${metaGraph}> {
|
|
3681
|
-
?privateEntity <http://dkg.io/ontology/privateMerkleRoot> ?privateRoot .
|
|
3682
|
-
}
|
|
3683
|
-
} ORDER BY ?privateEntity ?privateRoot LIMIT ${maxRows + 1}`, { source: 'agent.vmReconcile.swmFingerprint.privateRoots' });
|
|
3684
|
-
if (privateRootRows.type !== 'bindings')
|
|
3685
|
-
return null;
|
|
3686
|
-
if (isTooLarge(privateRootRows.bindings))
|
|
3687
|
-
return null;
|
|
3688
|
-
const privateRoots = privateRootRows.bindings
|
|
3689
|
-
.map((row) => [
|
|
3690
|
-
String(row['privateEntity'] ?? ''),
|
|
3691
|
-
String(row['privateRoot'] ?? ''),
|
|
3692
|
-
].join('\0'))
|
|
3693
|
-
.sort();
|
|
3694
|
-
parts.push([
|
|
3695
|
-
`meta:${namespace.metaGraph}`,
|
|
3696
|
-
`data:${namespace.dataGraph}`,
|
|
3697
|
-
`ops:${operations.length}`,
|
|
3698
|
-
`opHash:${digestRows(operations)}`,
|
|
3699
|
-
`dataTriples:${dataTriples.length}`,
|
|
3700
|
-
`dataHash:${digestRows(dataTriples)}`,
|
|
3701
|
-
`privateRoots:${privateRoots.length}`,
|
|
3702
|
-
`privateRootHash:${digestRows(privateRoots)}`,
|
|
3703
|
-
].join(';'));
|
|
3704
|
-
}
|
|
3705
|
-
// Catch writes into a newly-created/unregistered namespace that the
|
|
3706
|
-
// current namespace enumeration cannot yet name. This process-local term
|
|
3707
|
-
// complements (rather than replaces) the content fingerprint: after a
|
|
3708
|
-
// restart the fingerprint remains the correctness gate while the counter
|
|
3709
|
-
// restarts harmlessly.
|
|
3710
|
-
const rootDataGraph = candidateNamespaces[0]?.dataGraph ?? '';
|
|
3711
|
-
const swmSuffix = rootDataGraph.indexOf('/_shared_memory');
|
|
3712
|
-
const graphPrefix = swmSuffix >= 0 ? `${rootDataGraph.slice(0, swmSuffix)}/` : rootDataGraph;
|
|
3713
|
-
const writeRevision = asGraphWriteRevisionSource(this.store)?.getWriteRevision(graphPrefix);
|
|
3714
|
-
// A remote mutation can still commit after this fingerprint's reads.
|
|
3715
|
-
// Never turn such an in-flight/indeterminate observation into a stable
|
|
3716
|
-
// negative-cache key.
|
|
3717
|
-
if (writeRevision && !writeRevision.stable)
|
|
3718
|
-
return null;
|
|
3719
|
-
if (writeRevision)
|
|
3720
|
-
parts.push(`writeGen:${writeRevision.generation}`);
|
|
3721
|
-
return parts.join('|');
|
|
3722
|
-
}
|
|
3723
|
-
catch {
|
|
3724
|
-
// Probe failures are not a stable SWM generation. Callers must not cache
|
|
3725
|
-
// or preserve a negative-cache gate from this result.
|
|
3726
|
-
}
|
|
3727
|
-
return null;
|
|
3728
|
-
}
|
|
3729
|
-
vmReconcileSwmGenHasOperations(swmGen) {
|
|
3730
|
-
return swmGen.split('|').some((part) => {
|
|
3731
|
-
const match = /(?:^|;)ops:(\d+)(?:;|$)/.exec(part);
|
|
3732
|
-
return match ? Number(match[1]) > 0 : false;
|
|
3733
|
-
});
|
|
3734
|
-
}
|
|
3735
|
-
vmReconcileSwmGenSupportsDurableNegative(swmGen) {
|
|
3736
|
-
// A changelog cursor covers every descendant graph mutation. The fallback
|
|
3737
|
-
// fingerprint covers only the bare SWM bucket, so an operation whose data
|
|
3738
|
-
// later lands in a per-KA child graph must fail open after restart.
|
|
3739
|
-
return swmGen.startsWith('changelog:') || !this.vmReconcileSwmGenHasOperations(swmGen);
|
|
3740
|
-
}
|
|
3741
|
-
vmReconcileSwmGenContainsSnapshot(cachedSwmGen, currentSwmGen) {
|
|
3742
|
-
return cachedSwmGen === currentSwmGen || cachedSwmGen.split('|').includes(currentSwmGen);
|
|
3743
|
-
}
|
|
3744
|
-
vmReconcileWorkspaceOperationPattern(candidateMetaGraphs) {
|
|
3745
|
-
const branches = [];
|
|
3746
|
-
for (const graph of candidateMetaGraphs) {
|
|
3747
|
-
try {
|
|
3748
|
-
branches.push(`{ GRAPH <${assertSafeIri(graph)}> {
|
|
3749
|
-
?op <http://dkg.io/ontology/rootEntity> ?root .
|
|
3750
|
-
OPTIONAL { ?op <http://dkg.io/ontology/publishedAt> ?ts . }
|
|
3751
|
-
} }`);
|
|
3752
|
-
}
|
|
3753
|
-
catch {
|
|
3754
|
-
// Skip unsafe graph names instead of building a malformed query.
|
|
3755
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
return branches.join(' UNION ');
|
|
3758
|
-
}
|
|
3759
|
-
deleteVmReconcileNegativeCacheEntry(cacheKey) {
|
|
3760
|
-
const existing = this.vmReconcileNegativeCache.get(cacheKey);
|
|
3761
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, existing?.localCgId ?? cacheKey.slice(0, Math.max(0, cacheKey.indexOf('\0'))));
|
|
3762
|
-
if (existing) {
|
|
3763
|
-
this.vmReconcileNegativeCache.delete(cacheKey);
|
|
3764
|
-
const keys = this.vmReconcileNegativeCacheKeysByCg.get(existing.localCgId);
|
|
3765
|
-
if (keys) {
|
|
3766
|
-
keys.delete(cacheKey);
|
|
3767
|
-
if (keys.size === 0)
|
|
3768
|
-
this.vmReconcileNegativeCacheKeysByCg.delete(existing.localCgId);
|
|
3769
|
-
}
|
|
3770
|
-
}
|
|
3771
|
-
void this.config.contextGraphSubscriptionStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
|
|
3772
|
-
// The in-memory invalidation remains authoritative for this process.
|
|
3773
|
-
});
|
|
3774
|
-
}
|
|
3775
|
-
indexVmReconcileNegativeCacheEntry(localCgId, cacheKey) {
|
|
3776
|
-
let keys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
|
|
3777
|
-
if (!keys) {
|
|
3778
|
-
keys = new Set();
|
|
3779
|
-
this.vmReconcileNegativeCacheKeysByCg.set(localCgId, keys);
|
|
3780
|
-
}
|
|
3781
|
-
keys.add(cacheKey);
|
|
3782
|
-
}
|
|
3783
|
-
markVmReconcileNegativeCacheHydrated(cacheKey, localCgId) {
|
|
3784
|
-
// Access order keeps actively reused keys resident while old one-shot
|
|
3785
|
-
// misses fall out. Eviction is fail-open: it only permits another durable
|
|
3786
|
-
// lookup if the same key is encountered later.
|
|
3787
|
-
this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
|
|
3788
|
-
this.vmReconcileNegativeCacheHydrated.set(cacheKey, localCgId);
|
|
3789
|
-
while (this.vmReconcileNegativeCacheHydrated.size
|
|
3790
|
-
> DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
3791
|
-
const oldestKey = this.vmReconcileNegativeCacheHydrated.keys().next().value;
|
|
3792
|
-
if (oldestKey === undefined)
|
|
3793
|
-
break;
|
|
3794
|
-
this.vmReconcileNegativeCacheHydrated.delete(oldestKey);
|
|
3795
|
-
}
|
|
3796
|
-
}
|
|
3797
|
-
async shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId) {
|
|
3798
|
-
let cached = this.vmReconcileNegativeCache.get(cacheKey);
|
|
3799
|
-
const durableStateAlreadyConsulted = this.vmReconcileNegativeCacheHydrated.has(cacheKey);
|
|
3800
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
|
|
3801
|
-
if (!cached && !durableStateAlreadyConsulted) {
|
|
3802
|
-
try {
|
|
3803
|
-
const durable = await this.config.contextGraphSubscriptionStore
|
|
3804
|
-
?.loadVmReconcileNegative?.(cacheKey);
|
|
3805
|
-
const durablePeerTopology = durable
|
|
3806
|
-
? isVmReconcilePeerTopology(durable.peerTopology)
|
|
3807
|
-
? durable.peerTopology
|
|
3808
|
-
: parseLegacyVmReconcilePeerTopologyKey(durable.peerTopologyKey)
|
|
3809
|
-
: null;
|
|
3810
|
-
const durableCleanMissPeerIds = durablePeerTopology
|
|
3811
|
-
? parseVmReconcileCleanMissPeerIds(durable?.cleanMissPeerIds ?? [], durablePeerTopology)
|
|
3812
|
-
: null;
|
|
3813
|
-
if (durable &&
|
|
3814
|
-
durable.cacheKey === cacheKey &&
|
|
3815
|
-
durable.localCgId === localCgId &&
|
|
3816
|
-
Number.isInteger(durable.failures) && durable.failures > 0 &&
|
|
3817
|
-
Number.isFinite(durable.nextRetryAt) &&
|
|
3818
|
-
typeof durable.swmGen === 'string' &&
|
|
3819
|
-
Array.isArray(durable.candidateNamespaces) &&
|
|
3820
|
-
durable.candidateNamespaces.every((item) => typeof item?.metaGraph === 'string' && typeof item?.dataGraph === 'string') &&
|
|
3821
|
-
durablePeerTopology !== null &&
|
|
3822
|
-
durableCleanMissPeerIds !== null) {
|
|
3823
|
-
if (!this.vmReconcileSwmGenSupportsDurableNegative(durable.swmGen)) {
|
|
3824
|
-
await this.config.contextGraphSubscriptionStore
|
|
3825
|
-
?.deleteVmReconcileNegative?.(cacheKey);
|
|
3826
|
-
}
|
|
3827
|
-
else {
|
|
3828
|
-
cached = {
|
|
3829
|
-
localCgId: durable.localCgId,
|
|
3830
|
-
failures: durable.failures,
|
|
3831
|
-
nextRetryAt: durable.nextRetryAt,
|
|
3832
|
-
swmGen: durable.swmGen,
|
|
3833
|
-
candidateNamespaces: durable.candidateNamespaces,
|
|
3834
|
-
peerTopology: durablePeerTopology,
|
|
3835
|
-
cleanMissPeerIds: durableCleanMissPeerIds,
|
|
3836
|
-
};
|
|
3837
|
-
this.vmReconcileNegativeCache.set(cacheKey, cached);
|
|
3838
|
-
this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
}
|
|
3842
|
-
catch {
|
|
3843
|
-
// Persistence is an accelerator only. Fail open to an authoritative scan.
|
|
3844
|
-
}
|
|
3845
|
-
}
|
|
3846
|
-
if (!cached)
|
|
3847
|
-
return false;
|
|
3848
|
-
if (Date.now() >= cached.nextRetryAt)
|
|
3849
|
-
return false;
|
|
3850
|
-
try {
|
|
3851
|
-
try {
|
|
3852
|
-
await this.primeCatchupConnections();
|
|
3853
|
-
}
|
|
3854
|
-
catch {
|
|
3855
|
-
// Best effort only; an unchanged connection view can still honor the
|
|
3856
|
-
// cached miss until the backoff expires.
|
|
3857
|
-
}
|
|
3858
|
-
const currentPeerTopology = await this.vmReconcilePeerTopology(localCgId);
|
|
3859
|
-
if (!canReuseVmReconcilePeerTopology({
|
|
3860
|
-
topology: cached.peerTopology,
|
|
3861
|
-
cleanMissPeerIds: cached.cleanMissPeerIds,
|
|
3862
|
-
}, currentPeerTopology)) {
|
|
3863
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3864
|
-
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
3865
|
-
return false;
|
|
3866
|
-
}
|
|
3867
|
-
const currentNamespaces = await this.collectVmReconcileSwmCandidateNamespacesBestEffort(localCgId);
|
|
3868
|
-
const currentNamespaceKey = this.vmReconcileSwmNamespaceKey(currentNamespaces.namespaces);
|
|
3869
|
-
const cachedNamespaceKey = this.vmReconcileSwmNamespaceKey(cached.candidateNamespaces);
|
|
3870
|
-
if (currentNamespaceKey !== cachedNamespaceKey) {
|
|
3871
|
-
if (!currentNamespaces.complete) {
|
|
3872
|
-
const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
|
|
3873
|
-
if (currentSwmGen === null) {
|
|
3874
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3875
|
-
return false;
|
|
3876
|
-
}
|
|
3877
|
-
if (!this.vmReconcileSwmGenContainsSnapshot(cached.swmGen, currentSwmGen)) {
|
|
3878
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3879
|
-
return false;
|
|
3880
|
-
}
|
|
3881
|
-
return true;
|
|
3882
|
-
}
|
|
3883
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3884
|
-
return false;
|
|
3885
|
-
}
|
|
3886
|
-
const pattern = this.vmReconcileWorkspaceOperationPattern(currentNamespaces.namespaces.map((namespace) => namespace.metaGraph));
|
|
3887
|
-
if (!pattern)
|
|
3888
|
-
return true;
|
|
3889
|
-
const currentSwmGen = await this.readVmReconcileSwmGen(currentNamespaces.namespaces);
|
|
3890
|
-
if (currentSwmGen === null) {
|
|
3891
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3892
|
-
return false;
|
|
3893
|
-
}
|
|
3894
|
-
if (currentSwmGen !== cached.swmGen) {
|
|
3895
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3896
|
-
return false;
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
catch {
|
|
3900
|
-
// Unexpected validation failures leave the existing backoff in place;
|
|
3901
|
-
// expected generation probe failures return null and clear the gate above.
|
|
3902
|
-
}
|
|
3903
|
-
return true;
|
|
3904
|
-
}
|
|
3905
|
-
recordVmReconcileNegativeCache(cacheKey, localCgId, state) {
|
|
3906
|
-
if (state.swmGen === null) {
|
|
3907
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
3908
|
-
return;
|
|
3909
|
-
}
|
|
3910
|
-
this.pruneVmReconcileState();
|
|
3911
|
-
const previous = this.vmReconcileNegativeCache.get(cacheKey);
|
|
3912
|
-
const failures = (previous?.failures ?? 0) + 1;
|
|
3913
|
-
const exponentialBackoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_BASE_MS * 2 ** Math.max(0, failures - 1));
|
|
3914
|
-
const jitterSample = createHash('sha256')
|
|
3915
|
-
.update(`${this.node.peerId.toString()}\0${cacheKey}\0${failures}`)
|
|
3916
|
-
.digest()
|
|
3917
|
-
.readUInt32BE(0) / 0x1_0000_0000;
|
|
3918
|
-
const backoff = Math.min(DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS, Math.max(1, Math.round(exponentialBackoff * (0.8 + jitterSample * 0.4))));
|
|
3919
|
-
getMetrics().storeRetryAttemptsTotal.add(1, {
|
|
3920
|
-
scope: 'vm_reconcile',
|
|
3921
|
-
reason: 'no_swm',
|
|
3922
|
-
attempt: Math.min(failures, 16),
|
|
3923
|
-
});
|
|
3924
|
-
if (previous) {
|
|
3925
|
-
// Replace in place without racing an asynchronous durable DELETE against
|
|
3926
|
-
// the SAVE below. The record key/local CG are unchanged here.
|
|
3927
|
-
this.vmReconcileNegativeCache.delete(cacheKey);
|
|
3928
|
-
const keys = this.vmReconcileNegativeCacheKeysByCg.get(previous.localCgId);
|
|
3929
|
-
keys?.delete(cacheKey);
|
|
3930
|
-
if (keys?.size === 0)
|
|
3931
|
-
this.vmReconcileNegativeCacheKeysByCg.delete(previous.localCgId);
|
|
3932
|
-
}
|
|
3933
|
-
const record = {
|
|
3934
|
-
localCgId,
|
|
3935
|
-
failures,
|
|
3936
|
-
nextRetryAt: Date.now() + backoff,
|
|
3937
|
-
swmGen: state.swmGen,
|
|
3938
|
-
candidateNamespaces: state.candidateNamespaces,
|
|
3939
|
-
peerTopology: state.peerTopology,
|
|
3940
|
-
cleanMissPeerIds: state.cleanMissPeerIds,
|
|
3941
|
-
};
|
|
3942
|
-
this.vmReconcileNegativeCache.set(cacheKey, record);
|
|
3943
|
-
this.markVmReconcileNegativeCacheHydrated(cacheKey, localCgId);
|
|
3944
|
-
this.indexVmReconcileNegativeCacheEntry(localCgId, cacheKey);
|
|
3945
|
-
const durableStore = this.config.contextGraphSubscriptionStore;
|
|
3946
|
-
if (this.vmReconcileSwmGenSupportsDurableNegative(record.swmGen)) {
|
|
3947
|
-
void durableStore?.saveVmReconcileNegative?.({
|
|
3948
|
-
cacheKey,
|
|
3949
|
-
localCgId: record.localCgId,
|
|
3950
|
-
failures: record.failures,
|
|
3951
|
-
nextRetryAt: record.nextRetryAt,
|
|
3952
|
-
swmGen: record.swmGen,
|
|
3953
|
-
candidateNamespaces: record.candidateNamespaces,
|
|
3954
|
-
peerTopologyKey: encodeLegacyVmReconcilePeerTopologyKey(record.peerTopology),
|
|
3955
|
-
peerTopology: record.peerTopology,
|
|
3956
|
-
cleanMissPeerIds: record.cleanMissPeerIds,
|
|
3957
|
-
}).catch(() => {
|
|
3958
|
-
// Persistence is an accelerator only; the process-local gate still works.
|
|
3959
|
-
});
|
|
3960
|
-
}
|
|
3961
|
-
else {
|
|
3962
|
-
void durableStore?.deleteVmReconcileNegative?.(cacheKey).catch(() => {
|
|
3963
|
-
// Fail open after restart even if best-effort cleanup cannot complete.
|
|
3964
|
-
});
|
|
3965
|
-
}
|
|
3966
|
-
this.pruneVmReconcileState();
|
|
3967
|
-
}
|
|
3968
|
-
vmReconcileRotationNow() {
|
|
3969
|
-
return performance.now();
|
|
3970
|
-
}
|
|
3971
|
-
vmReconcileRotationSlotKey(target) {
|
|
3972
|
-
return `${target.localCgId}\0${target.onChainCgId}\0${target.ordinal}`;
|
|
3973
|
-
}
|
|
3974
|
-
clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal) {
|
|
3975
|
-
this.vmReconcileRotationState.delete(`${localCgId}\0${onChainCgId.toString()}\0${ordinal}`);
|
|
3976
|
-
}
|
|
3977
|
-
vmReconcileRotationFingerprint(target) {
|
|
3978
|
-
return `${target.ual}\0${target.merkleRoot.toLowerCase()}`;
|
|
3979
|
-
}
|
|
3980
|
-
vmReconcileObservedCandidatePeerIds(localCgId) {
|
|
3981
|
-
const curatorOrder = this.vmReconcileCuratorPeersByCg.get(localCgId) ?? [];
|
|
3982
|
-
const libp2p = this.node?.libp2p;
|
|
3983
|
-
const getConnections = libp2p?.getConnections;
|
|
3984
|
-
if (typeof getConnections !== 'function') {
|
|
3985
|
-
return curatorOrder.slice(0, DKGAgentBase.VM_RECONCILE_EXACT_ROSTER_MAX);
|
|
3986
|
-
}
|
|
3987
|
-
const peersById = new Map();
|
|
3988
|
-
for (const connection of getConnections.call(libp2p)) {
|
|
3989
|
-
const peer = connection.remotePeer;
|
|
3990
|
-
const peerId = peer?.toString();
|
|
3991
|
-
if (!peer || !peerId || peerId === this.peerId || peersById.has(peerId))
|
|
3992
|
-
continue;
|
|
3993
|
-
peersById.set(peerId, peer);
|
|
4260
|
+
const peersById = new Map();
|
|
4261
|
+
for (const connection of getConnections.call(libp2p)) {
|
|
4262
|
+
const peer = connection.remotePeer;
|
|
4263
|
+
const peerId = peer?.toString();
|
|
4264
|
+
if (!peer || !peerId || peerId === this.peerId || peersById.has(peerId))
|
|
4265
|
+
continue;
|
|
4266
|
+
peersById.set(peerId, peer);
|
|
3994
4267
|
}
|
|
3995
4268
|
// `getConnections()` iteration order is transport state, not candidate
|
|
3996
4269
|
// identity. Sort before tiering/capping so harmless connection reorder can
|
|
@@ -4244,11 +4517,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4244
4517
|
* mutable transition so partial responses revoke affinity consistently.
|
|
4245
4518
|
* Unavailable peers and the global considered-peer cap remain authoritative.
|
|
4246
4519
|
*/
|
|
4247
|
-
selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy) {
|
|
4520
|
+
selectVmReconcileExactCandidate(record, fallbackCandidatePeerIds, policy, binding) {
|
|
4248
4521
|
const uncreditedCandidateOrder = record
|
|
4249
4522
|
? this.vmReconcileUncreditedCandidateOrder(record)
|
|
4250
4523
|
: fallbackCandidatePeerIds;
|
|
4251
|
-
|
|
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);
|
|
4252
4540
|
}
|
|
4253
4541
|
findVmReconcileRotationReplacement(requestingCgId) {
|
|
4254
4542
|
if (this.vmReconcileRotationState.size < DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
@@ -4319,7 +4607,11 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4319
4607
|
if (key.startsWith(prefix))
|
|
4320
4608
|
this.vmReconcileRotationState.delete(key);
|
|
4321
4609
|
}
|
|
4610
|
+
this.vmReconcileTransportBudgetPolicy.forgetContextGraph(localCgId);
|
|
4611
|
+
this.vmReconcileStreamSetbackPolicy.forgetContextGraph(localCgId);
|
|
4322
4612
|
this.vmReconcileRotationAdmissionCursorByCg.delete(localCgId);
|
|
4613
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy?.forgetContextGraph(localCgId);
|
|
4614
|
+
existingVmRecoveryPreparation(this)?.discard(localCgId);
|
|
4323
4615
|
}
|
|
4324
4616
|
closeVmReconcileRotationState() {
|
|
4325
4617
|
this.vmReconcileLifecycleController?.abort();
|
|
@@ -4330,10 +4622,22 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4330
4622
|
// without running the base constructor. Shutdown must remain best-effort
|
|
4331
4623
|
// for that supported test seam and never mask later teardown failures.
|
|
4332
4624
|
this.vmReconcileRotationState?.clear();
|
|
4625
|
+
this.vmReconcileTransportBudgetPolicy?.clear();
|
|
4626
|
+
this.vmReconcileStreamSetbackPolicy?.clear();
|
|
4333
4627
|
this.vmReconcileRotationAdmissionCursorByCg?.clear();
|
|
4334
4628
|
this.vmReconcileCuratorPeersByCg?.clear();
|
|
4335
4629
|
this.vmReconcileCuratorPageCursorByCg?.clear();
|
|
4336
4630
|
this.vmReconcileExactPeerCapabilities?.clear();
|
|
4631
|
+
this.vmReconcilePublicCoreTransportPreferencePolicy?.clear();
|
|
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
|
+
}
|
|
4337
4641
|
}
|
|
4338
4642
|
openVmReconcileRotationState() {
|
|
4339
4643
|
if (!this.vmReconcileLifecycleController || this.vmReconcileLifecycleController.signal.aborted) {
|
|
@@ -4360,6 +4664,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4360
4664
|
return;
|
|
4361
4665
|
if (peerId !== undefined) {
|
|
4362
4666
|
capturedRecord.attemptedPeerIds.add(peerId);
|
|
4667
|
+
// This attempt gave a verdict: an earlier setback with the peer is history.
|
|
4668
|
+
capturedRecord.streamSetbackPeerIds?.delete(peerId);
|
|
4363
4669
|
if (disposition === 'clean-absent')
|
|
4364
4670
|
capturedRecord.cleanAbsentPeerIds.add(peerId);
|
|
4365
4671
|
// Preserve fairly accumulated proof progress while other targets share
|
|
@@ -4384,6 +4690,23 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4384
4690
|
}
|
|
4385
4691
|
this.touchVmReconcileRotationRecord(slotKey, capturedRecord);
|
|
4386
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
|
+
}
|
|
4387
4710
|
creditVmReconcileCleanAbsence(target, peerId, expectedCandidatePeerIds, capturedRecord) {
|
|
4388
4711
|
this.settleVmReconcileRotationAttempt(target, peerId, 'clean-absent', expectedCandidatePeerIds, capturedRecord);
|
|
4389
4712
|
}
|
|
@@ -4414,26 +4737,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4414
4737
|
this.installVmReconcileActiveFetchCooldown(localCgId, now);
|
|
4415
4738
|
return true;
|
|
4416
4739
|
}
|
|
4417
|
-
vmReconcileActiveFetchHadUsableResponse(result) {
|
|
4418
|
-
if ((result.peersSucceeded ?? 0) > 0)
|
|
4419
|
-
return true;
|
|
4420
|
-
if ((result.sharedMemorySynced ?? 0) > 0)
|
|
4421
|
-
return true;
|
|
4422
|
-
const shared = result.diagnostics?.sharedMemory;
|
|
4423
|
-
if (!shared)
|
|
4424
|
-
return false;
|
|
4425
|
-
return (shared.insertedDataTriples ?? 0) > 0
|
|
4426
|
-
|| (shared.insertedMetaTriples ?? 0) > 0
|
|
4427
|
-
|| (shared.checkpointAdvances ?? 0) > 0
|
|
4428
|
-
|| ((shared.completedPhases ?? 0) > 0 && (shared.resumedPhases ?? 0) > 0);
|
|
4429
|
-
}
|
|
4430
4740
|
pruneVmReconcileState(now = Date.now()) {
|
|
4431
|
-
while (this.vmReconcileNegativeCache.size > DKGAgentBase.VM_RECONCILE_CACHE_MAX_ENTRIES) {
|
|
4432
|
-
const oldestKey = this.vmReconcileNegativeCache.keys().next().value;
|
|
4433
|
-
if (oldestKey === undefined)
|
|
4434
|
-
break;
|
|
4435
|
-
this.deleteVmReconcileNegativeCacheEntry(oldestKey);
|
|
4436
|
-
}
|
|
4437
4741
|
for (const [localCgId, cooldown] of this.vmReconcileFetchCooldowns) {
|
|
4438
4742
|
if (now - cooldown.startedAt >= DKGAgentBase.VM_RECONCILE_SWEEP_INTERVAL_MS) {
|
|
4439
4743
|
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
@@ -4492,22 +4796,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4492
4796
|
});
|
|
4493
4797
|
}
|
|
4494
4798
|
forceClearVmReconcileStateForContextGraph(localCgId, options = {}) {
|
|
4495
|
-
const negativeCacheKeys = this.vmReconcileNegativeCacheKeysByCg.get(localCgId);
|
|
4496
|
-
if (negativeCacheKeys) {
|
|
4497
|
-
for (const cacheKey of Array.from(negativeCacheKeys)) {
|
|
4498
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
4499
|
-
}
|
|
4500
|
-
}
|
|
4501
|
-
for (const [cacheKey, hydratedLocalCgId] of this.vmReconcileNegativeCacheHydrated) {
|
|
4502
|
-
if (hydratedLocalCgId === localCgId) {
|
|
4503
|
-
this.vmReconcileNegativeCacheHydrated.delete(cacheKey);
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
void this.config.contextGraphSubscriptionStore
|
|
4507
|
-
?.deleteVmReconcileNegativesForContextGraph?.(localCgId)
|
|
4508
|
-
.catch(() => {
|
|
4509
|
-
// Best-effort durable cleanup; generation checks still reject stale rows.
|
|
4510
|
-
});
|
|
4511
4799
|
this.reconcileCursors.delete(localCgId);
|
|
4512
4800
|
if (options.includeSelectedCursor !== false
|
|
4513
4801
|
&& this.selectedVmReconcileCursors.delete(localCgId)) {
|
|
@@ -4520,6 +4808,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4520
4808
|
this.vmReconcileCatchupPeerCursor.delete(localCgId);
|
|
4521
4809
|
this.vmReconcileCatchupPeerOrder.delete(localCgId);
|
|
4522
4810
|
this.clearRecentVmReconcileStateForContextGraph(localCgId);
|
|
4811
|
+
this.vmRefreshQueue?.clearContextGraph(localCgId);
|
|
4523
4812
|
}
|
|
4524
4813
|
clearRecentVmReconcileStateForContextGraph(localCgId) {
|
|
4525
4814
|
this.recentReconciledUals.deleteByPrefix(`${localCgId}\0`);
|
|
@@ -4595,97 +4884,120 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4595
4884
|
}
|
|
4596
4885
|
pruneVmReconcileCacheKeySiblings(cacheKey) {
|
|
4597
4886
|
const prefix = this.vmReconcileCacheKeyPrefix(cacheKey);
|
|
4598
|
-
for (const key of this.vmReconcileNegativeCache.keys()) {
|
|
4599
|
-
if (key !== cacheKey && key.startsWith(prefix)) {
|
|
4600
|
-
this.deleteVmReconcileNegativeCacheEntry(key);
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
4887
|
this.recentReconciledUals.deleteByPrefix(prefix, cacheKey);
|
|
4604
4888
|
}
|
|
4605
|
-
vmReconcileConnectedPeerCount() {
|
|
4606
|
-
try {
|
|
4607
|
-
const libp2p = this.node?.libp2p;
|
|
4608
|
-
const getConnections = libp2p?.getConnections;
|
|
4609
|
-
if (typeof getConnections !== 'function')
|
|
4610
|
-
return 0;
|
|
4611
|
-
const uniquePeers = new Set(getConnections.call(libp2p)
|
|
4612
|
-
.map((connection) => connection.remotePeer?.toString())
|
|
4613
|
-
.filter((peerId) => typeof peerId === 'string' && peerId.length > 0));
|
|
4614
|
-
return uniquePeers.size;
|
|
4615
|
-
}
|
|
4616
|
-
catch {
|
|
4617
|
-
return 0;
|
|
4618
|
-
}
|
|
4619
|
-
}
|
|
4620
4889
|
/**
|
|
4621
|
-
* Execute one
|
|
4622
|
-
* roster
|
|
4623
|
-
*
|
|
4624
|
-
*
|
|
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.
|
|
4625
4894
|
* Returns immutable evidence for the caller to merge. A stale lifecycle is
|
|
4626
4895
|
* discriminated by whether the physical attempt had already been admitted;
|
|
4627
4896
|
* only the pre-admission variant guarantees zero attempt side effects.
|
|
4628
4897
|
*/
|
|
4629
4898
|
async executeVmRecoveryBatch(input) {
|
|
4630
|
-
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;
|
|
4631
4900
|
const unavailablePeerIdSet = new Set(unavailablePeerIds);
|
|
4632
4901
|
// The caller's last guard can race an unsubscribe/rebind before this
|
|
4633
4902
|
// executor begins. Do not retain attempt evidence or emit a fetch event
|
|
4634
4903
|
// until this exact lifecycle has been re-proved at the ownership boundary.
|
|
4635
4904
|
if (!isRecoveryCurrent())
|
|
4636
4905
|
return { kind: 'not-started-stale' };
|
|
4637
|
-
const useCachedLegacyFallback =
|
|
4906
|
+
const useCachedLegacyFallback = exactRecoveryTransportMode !== 'stream-required'
|
|
4907
|
+
&& this.vmReconcileExactFilterUnsupported(peerId);
|
|
4638
4908
|
const handledOrdinals = [];
|
|
4639
4909
|
const attemptedOrdinals = [];
|
|
4640
4910
|
const outcomes = [];
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
attempt.
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
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
|
+
});
|
|
4651
4942
|
}
|
|
4652
|
-
|
|
4653
|
-
contextGraphId: localCgId,
|
|
4654
|
-
onChainCgId: onChainCgId.toString(),
|
|
4655
|
-
action: 'fetch',
|
|
4656
|
-
ordinal: batchTarget.ordinal,
|
|
4657
|
-
kaId: batchTarget.kaId,
|
|
4658
|
-
ual: batchTarget.ual,
|
|
4659
|
-
detail: useCachedLegacyFallback
|
|
4660
|
-
? 'legacy-sync'
|
|
4661
|
-
: attempts.length > 1 ? 'exact-asset-batch' : 'exact-asset',
|
|
4662
|
-
});
|
|
4663
|
-
}
|
|
4943
|
+
};
|
|
4664
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));
|
|
4665
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
|
+
}
|
|
4666
4960
|
try {
|
|
4667
4961
|
const fallback = await this.runLegacyDurableSyncDetailed(ctx, peerId, [localCgId], undefined, undefined, undefined, {
|
|
4668
4962
|
stopOnBackoffWorthyFailure: true,
|
|
4669
|
-
priority:
|
|
4963
|
+
priority: VM_RECOVERY_SYNC_PRIORITY,
|
|
4670
4964
|
source: 'vm-recovery',
|
|
4965
|
+
onWorkStarted,
|
|
4671
4966
|
signal,
|
|
4672
4967
|
isCurrent: isRecoveryCurrent,
|
|
4968
|
+
...(remainingMs === undefined ? {} : { totalTimeoutMs: remainingMs }),
|
|
4673
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';
|
|
4674
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}`);
|
|
4675
4976
|
}
|
|
4676
4977
|
catch (fallbackError) {
|
|
4677
4978
|
this.log.info(ctx, `VM legacy fallback for "${localCgId}" from ${peerId.slice(-8)} failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);
|
|
4678
4979
|
}
|
|
4679
4980
|
};
|
|
4981
|
+
const exchangeInterval = phases?.begin();
|
|
4680
4982
|
try {
|
|
4681
4983
|
if (useCachedLegacyFallback) {
|
|
4682
4984
|
await runLegacyFallback();
|
|
4683
4985
|
}
|
|
4684
4986
|
else {
|
|
4685
|
-
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
|
+
});
|
|
4686
4992
|
const { result } = detailed;
|
|
4687
4993
|
disposition = detailed.disposition;
|
|
4688
|
-
|
|
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') {
|
|
4689
5001
|
this.rememberVmReconcileExactFilterUnsupported(peerId);
|
|
4690
5002
|
if (isRecoveryCurrent() && disposition === 'incomplete') {
|
|
4691
5003
|
await runLegacyFallback();
|
|
@@ -4697,18 +5009,44 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4697
5009
|
catch (error) {
|
|
4698
5010
|
this.log.info(ctx, `VM exact fetch for "${localCgId}" from ${peerId.slice(-8)} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4699
5011
|
}
|
|
5012
|
+
if (phases && exchangeInterval)
|
|
5013
|
+
phases.chargeSince('exchange', exchangeInterval.startedAt, exchangeInterval.before);
|
|
4700
5014
|
if (!isRecoveryCurrent())
|
|
4701
|
-
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
|
+
}
|
|
4702
5033
|
const perUalDispositions = new Map();
|
|
4703
|
-
|
|
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()) {
|
|
4704
5048
|
const batchTarget = attempt.entry.target;
|
|
4705
|
-
const outcome =
|
|
4706
|
-
isTargetCurrent: isRecoveryCurrent,
|
|
4707
|
-
revalidateTarget,
|
|
4708
|
-
deferActiveFetch: true,
|
|
4709
|
-
});
|
|
4710
|
-
if (!isRecoveryCurrent())
|
|
4711
|
-
return { kind: 'stale-after-attempt' };
|
|
5049
|
+
const outcome = revalidated[attemptIndex];
|
|
4712
5050
|
outcomes.push([batchTarget.ordinal, outcome]);
|
|
4713
5051
|
const perTargetDisposition = (outcome.status === 'reconciled' || outcome.status === 'already')
|
|
4714
5052
|
? 'found'
|
|
@@ -4717,6 +5055,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4717
5055
|
: 'incomplete';
|
|
4718
5056
|
perUalDispositions.set(batchTarget.ual, perTargetDisposition);
|
|
4719
5057
|
if (outcome.status === 'pending' && outcome.recovery) {
|
|
5058
|
+
if (!workStarted)
|
|
5059
|
+
continue;
|
|
4720
5060
|
const batchRecord = attempt.installedRecord;
|
|
4721
5061
|
if (!batchRecord || this.vmReconcileRotationClosed)
|
|
4722
5062
|
continue;
|
|
@@ -4724,10 +5064,14 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4724
5064
|
!== batchRecord)
|
|
4725
5065
|
continue;
|
|
4726
5066
|
const candidateMembershipAfter = this.vmReconcileObservedCandidatePeerIds(localCgId);
|
|
5067
|
+
if (providerTurnKept) {
|
|
5068
|
+
this.releaseVmReconcileRotationAttempt(batchTarget, peerId, batchRecord);
|
|
5069
|
+
}
|
|
4727
5070
|
if (!this.vmReconcilePeerMembershipMatches(batchRecord.candidatePeerIds, candidateMembershipAfter)) {
|
|
4728
5071
|
this.prepareVmReconcileRotationTarget(outcome.recovery, candidateMembershipAfter, this.vmReconcileRotationNow());
|
|
4729
5072
|
}
|
|
4730
|
-
else if (
|
|
5073
|
+
else if (!providerTurnKept
|
|
5074
|
+
&& this.vmReconcileRecoveryTargetMatches(batchTarget, outcome.recovery)) {
|
|
4731
5075
|
this.settleVmReconcileRotationAttempt(batchTarget, peerId, perTargetDisposition, attempt.candidatePeerIds, batchRecord, unavailablePeerIdSet);
|
|
4732
5076
|
}
|
|
4733
5077
|
}
|
|
@@ -4742,6 +5086,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4742
5086
|
attemptedOrdinals,
|
|
4743
5087
|
providerDisposition: disposition,
|
|
4744
5088
|
perUalDispositions: [...perUalDispositions],
|
|
5089
|
+
providerTurnKept,
|
|
4745
5090
|
};
|
|
4746
5091
|
}
|
|
4747
5092
|
/**
|
|
@@ -4757,6 +5102,26 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4757
5102
|
&& this.vmReconcileLifecycleGeneration === rotationGeneration
|
|
4758
5103
|
&& isTargetCurrent();
|
|
4759
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;
|
|
4760
5125
|
const noRecovery = (continuationOrdinal, cooldownOnly = false) => ({
|
|
4761
5126
|
outcomes: new Map(),
|
|
4762
5127
|
attemptedOrdinals: [],
|
|
@@ -4889,7 +5254,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4889
5254
|
...(this.vmReconcileCuratorPeersByCg.get(localCgId) ?? []),
|
|
4890
5255
|
];
|
|
4891
5256
|
const curatorPageCursor = this.vmReconcileCuratorPageCursorByCg.get(localCgId);
|
|
4892
|
-
const curatorResolution = await this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
5257
|
+
const curatorResolution = await phases.measure('roster', () => this.resolveCuratorPeerIdsForCg(localCgId, {
|
|
4893
5258
|
maxPeerIds: DKGAgentBase.VM_RECONCILE_EXACT_ROSTER_MAX,
|
|
4894
5259
|
// Once overflow is proven, expose exactly one new ordered peer per pass.
|
|
4895
5260
|
// One target can spend only one peer attempt, so advancing by more would
|
|
@@ -4904,7 +5269,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4904
5269
|
curatorIsLocal: false,
|
|
4905
5270
|
legacyTripleResolved: false,
|
|
4906
5271
|
lookupFailed: true,
|
|
4907
|
-
}));
|
|
5272
|
+
})));
|
|
4908
5273
|
if (!isRecoveryCurrent())
|
|
4909
5274
|
return staleRecovery();
|
|
4910
5275
|
const allResolvedCuratorPeerIds = [...new Set(curatorResolution.peerIds
|
|
@@ -4950,7 +5315,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4950
5315
|
let legacyPreferredPeerId;
|
|
4951
5316
|
if (resolutionSucceeded && !curatorResolution.curatorIsLocal
|
|
4952
5317
|
&& resolvedCuratorPeerIds.length === 0) {
|
|
4953
|
-
legacyPreferredPeerId = await this.resolvePreferredSyncPeerId(localCgId);
|
|
5318
|
+
legacyPreferredPeerId = await phases.measure('roster', () => this.resolvePreferredSyncPeerId(localCgId));
|
|
4954
5319
|
}
|
|
4955
5320
|
if (!isRecoveryCurrent())
|
|
4956
5321
|
return staleRecovery();
|
|
@@ -4986,9 +5351,18 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4986
5351
|
this.vmReconcileCuratorPageCursorByCg.delete(localCgId);
|
|
4987
5352
|
this.vmReconcileCuratorPageCursorByCg.set(localCgId, legacyPagination.nextPageAfterPeerId);
|
|
4988
5353
|
}
|
|
4989
|
-
if (!curatorResolution.curatorIsLocal
|
|
4990
|
-
|
|
4991
|
-
|
|
5354
|
+
if (!curatorResolution.curatorIsLocal) {
|
|
5355
|
+
if (curatorPeerIds.length > 0) {
|
|
5356
|
+
this.vmReconcileCuratorPeersByCg.delete(localCgId);
|
|
5357
|
+
this.vmReconcileCuratorPeersByCg.set(localCgId, curatorPeerIds);
|
|
5358
|
+
}
|
|
5359
|
+
else {
|
|
5360
|
+
// Without the durable phonebook an empty curator tier usually means
|
|
5361
|
+
// the owner's profile was never fetched, and this pass can only ask
|
|
5362
|
+
// peers it is already connected to. One bounded phonebook fetch serves
|
|
5363
|
+
// every graph; its completion re-schedules this graph's recovery.
|
|
5364
|
+
this.requestOnDemandAgentsPhonebook(localCgId, 'vm-reconcile');
|
|
5365
|
+
}
|
|
4992
5366
|
}
|
|
4993
5367
|
this.pruneVmReconcileState();
|
|
4994
5368
|
const connectedByPeerId = new Map(this.node.libp2p.getConnections()
|
|
@@ -4997,6 +5371,79 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
4997
5371
|
// Curator resolution may connect a missing peer, but it must not substitute
|
|
4998
5372
|
// a different ranking algorithm and invalidate an otherwise stable roster.
|
|
4999
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();
|
|
5000
5447
|
// Curator preparation may have grown or shrunk the connected candidate
|
|
5001
5448
|
// set. Re-evaluate every target against that observed change. Any roster
|
|
5002
5449
|
// change breaks backoff and starts a fresh proof cycle.
|
|
@@ -5062,6 +5509,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5062
5509
|
const providerPolicy = new VmRecoveryProviderPolicy();
|
|
5063
5510
|
const handledBatchOrdinals = new Set();
|
|
5064
5511
|
let recoveryWorkRan = false;
|
|
5512
|
+
let localAdmissionDeferred = false;
|
|
5065
5513
|
for (let eligibleIndex = 0; eligibleIndex < eligible.length; eligibleIndex += 1) {
|
|
5066
5514
|
if (!isRecoveryCurrent())
|
|
5067
5515
|
break;
|
|
@@ -5069,6 +5517,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5069
5517
|
const { target } = entry;
|
|
5070
5518
|
if (handledBatchOrdinals.has(target.ordinal))
|
|
5071
5519
|
continue;
|
|
5520
|
+
const batchTiming = timing.beginBatch();
|
|
5072
5521
|
const record = entry.prepared.record;
|
|
5073
5522
|
const installedRecord = record
|
|
5074
5523
|
&& this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(target)) === record
|
|
@@ -5083,21 +5532,27 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5083
5532
|
// uncredited, but consume this target's turn so another missing KA gets
|
|
5084
5533
|
// the next physical peer slot in the same bounded pass.
|
|
5085
5534
|
let peerId;
|
|
5086
|
-
|
|
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);
|
|
5087
5550
|
if (candidatePeerId) {
|
|
5088
|
-
attemptedOrdinals.add(target.ordinal);
|
|
5089
|
-
if (installedRecord) {
|
|
5090
|
-
installedRecord.lastAttemptedPeerId = candidatePeerId;
|
|
5091
|
-
installedRecord.attemptedPeerIds.add(candidatePeerId);
|
|
5092
|
-
installedRecord.collectionDeadlineAt = this.vmReconcileRotationNow()
|
|
5093
|
-
+ DKGAgentBase.VM_RECONCILE_NEGATIVE_BACKOFF_MAX_MS;
|
|
5094
|
-
this.touchVmReconcileRotationRecord(this.vmReconcileRotationSlotKey(target), installedRecord);
|
|
5095
|
-
}
|
|
5096
5551
|
let connectedPeer = connectedByPeerId.get(candidatePeerId);
|
|
5097
5552
|
if (!connectedPeer) {
|
|
5098
|
-
await this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
|
|
5553
|
+
await phases.measure('peer-ready', () => this.ensurePeerConnected(candidatePeerId, { signal }).catch((error) => {
|
|
5099
5554
|
this.log.info(ctx, `VM exact fetch could not connect candidate peer ${candidatePeerId.slice(-8)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5100
|
-
});
|
|
5555
|
+
}));
|
|
5101
5556
|
if (!isRecoveryCurrent())
|
|
5102
5557
|
return staleRecovery();
|
|
5103
5558
|
const connection = this.node.libp2p.getConnections()
|
|
@@ -5108,7 +5563,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5108
5563
|
}
|
|
5109
5564
|
recoveryWorkRan = true;
|
|
5110
5565
|
const protocolReady = connectedPeer
|
|
5111
|
-
? await this.waitForSyncProtocol(connectedPeer, signal)
|
|
5566
|
+
? await phases.measure('peer-ready', () => this.waitForSyncProtocol(connectedPeer, signal))
|
|
5112
5567
|
: false;
|
|
5113
5568
|
if (!isRecoveryCurrent())
|
|
5114
5569
|
return staleRecovery();
|
|
@@ -5119,7 +5574,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5119
5574
|
// Network boundary: a merely-connected peer is not necessarily
|
|
5120
5575
|
// admitted to this DKG network. Never send an authenticated exact
|
|
5121
5576
|
// request to an unverified or rejected peer.
|
|
5122
|
-
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));
|
|
5123
5578
|
if (!isRecoveryCurrent())
|
|
5124
5579
|
return staleRecovery();
|
|
5125
5580
|
if (!peerAdmitted)
|
|
@@ -5129,25 +5584,41 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5129
5584
|
}
|
|
5130
5585
|
}
|
|
5131
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
|
+
}
|
|
5132
5603
|
if (installedRecord) {
|
|
5133
5604
|
this.settleVmReconcileRotationAttempt(target, undefined, 'incomplete', candidatePeerIds, installedRecord, providerPolicy.unavailablePeerIds());
|
|
5134
5605
|
}
|
|
5135
5606
|
continue;
|
|
5136
5607
|
}
|
|
5608
|
+
const carriedHolder = this.vmReconcilePublicCoreTransportPreferencePolicy.canReuse(preferenceAttempt, peerId, candidatePeerIds);
|
|
5609
|
+
if (carriedHolder)
|
|
5610
|
+
providerPolicy.seedProvenHolder(peerId);
|
|
5137
5611
|
const providerAttempt = providerPolicy.beginAttempt(peerId);
|
|
5138
5612
|
if (!providerAttempt)
|
|
5139
5613
|
continue;
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
// exact-sync protocol's ten-UAL cap remains the hard upper bound.
|
|
5149
|
-
if (providerAttempt.kind === 'proven-holder-reuse') {
|
|
5150
|
-
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 {
|
|
5151
5622
|
for (let candidateIndex = eligibleIndex; candidateIndex < eligible.length; candidateIndex += 1) {
|
|
5152
5623
|
const candidateEntry = eligible[candidateIndex];
|
|
5153
5624
|
const candidateTarget = candidateEntry.target;
|
|
@@ -5158,8 +5629,8 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5158
5629
|
&& this.vmReconcileRotationState.get(this.vmReconcileRotationSlotKey(candidateTarget)) === candidateRecord
|
|
5159
5630
|
? candidateRecord
|
|
5160
5631
|
: undefined;
|
|
5161
|
-
// The current target
|
|
5162
|
-
//
|
|
5632
|
+
// The current target already passed connection/network admission.
|
|
5633
|
+
// Later candidates must still prove
|
|
5163
5634
|
// this peer remains uncredited in their independent rotation record.
|
|
5164
5635
|
const peerEligible = candidateIndex === eligibleIndex
|
|
5165
5636
|
|| (candidateInstalledRecord
|
|
@@ -5167,7 +5638,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5167
5638
|
: orderedPeerIds.includes(peerId));
|
|
5168
5639
|
if (!peerEligible)
|
|
5169
5640
|
break;
|
|
5170
|
-
|
|
5641
|
+
candidateAttempts.push({
|
|
5171
5642
|
entry: candidateEntry,
|
|
5172
5643
|
installedRecord: candidateInstalledRecord,
|
|
5173
5644
|
candidatePeerIds: candidateInstalledRecord
|
|
@@ -5175,53 +5646,85 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5175
5646
|
: orderedPeerIds,
|
|
5176
5647
|
});
|
|
5177
5648
|
}
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
attempt,
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
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());
|
|
5218
5699
|
}
|
|
5219
|
-
|
|
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) {
|
|
5220
5708
|
this.log.info(ctx, `VM exact recovery plan for "${localCgId}" from ${peerId.slice(-8)}: `
|
|
5221
|
-
+ `assets=${batchAttempts.length} estimatedBytes=${
|
|
5222
|
-
+ `estimatedLeaves=${
|
|
5223
|
-
+ `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
|
+
};
|
|
5224
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;
|
|
5225
5728
|
const execution = await this.executeVmRecoveryBatch({
|
|
5226
5729
|
localCgId,
|
|
5227
5730
|
onChainCgId,
|
|
@@ -5233,16 +5736,52 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5233
5736
|
isRecoveryCurrent,
|
|
5234
5737
|
revalidateTarget,
|
|
5235
5738
|
ctx,
|
|
5236
|
-
|
|
5237
|
-
|
|
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())
|
|
5238
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 });
|
|
5239
5757
|
for (const [ordinal, outcome] of execution.outcomes)
|
|
5240
5758
|
outcomes.set(ordinal, outcome);
|
|
5241
5759
|
for (const ordinal of execution.handledOrdinals)
|
|
5242
5760
|
handledBatchOrdinals.add(ordinal);
|
|
5243
5761
|
for (const ordinal of execution.attemptedOrdinals)
|
|
5244
5762
|
attemptedOrdinals.add(ordinal);
|
|
5245
|
-
|
|
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
|
+
}
|
|
5246
5785
|
}
|
|
5247
5786
|
const eligibleOrdinals = new Set(eligible.map(({ target }) => target.ordinal));
|
|
5248
5787
|
const unattemptedContinuationOrdinal = currentTargets.find((target) => eligibleOrdinals.has(target.ordinal) && !attemptedOrdinals.has(target.ordinal))?.ordinal;
|
|
@@ -5273,6 +5812,19 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5273
5812
|
// negative backoff applies exactly as before.
|
|
5274
5813
|
if (!isRecoveryCurrent())
|
|
5275
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
|
+
}
|
|
5276
5828
|
const recoveredAny = [...outcomes.values()]
|
|
5277
5829
|
.some((outcome) => outcome.status === 'reconciled' || outcome.status === 'already');
|
|
5278
5830
|
if (!recoveryWorkRan
|
|
@@ -5289,6 +5841,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5289
5841
|
this.installVmReconcileActiveFetchCooldown(localCgId, Date.now());
|
|
5290
5842
|
}
|
|
5291
5843
|
}
|
|
5844
|
+
timing.finish(eligible.length, preparation ? () => preparation.stats() : undefined);
|
|
5292
5845
|
return {
|
|
5293
5846
|
outcomes,
|
|
5294
5847
|
attemptedOrdinals: [...attemptedOrdinals],
|
|
@@ -5306,7 +5859,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5306
5859
|
* latest on-chain merkle root + publisher, build the UAL, and ask the
|
|
5307
5860
|
* finalization handler to promote the matching local SWM snapshot to VM
|
|
5308
5861
|
* (verifying the CG binding from chain). When no local SWM matches, run an
|
|
5309
|
-
*
|
|
5862
|
+
* queue the exact target for bounded batch recovery. A successful result is
|
|
5310
5863
|
* validated against one coherent pinned version snapshot before it earns a
|
|
5311
5864
|
* same-finalized-block shortcut; a new block always reads again. `headBlock`
|
|
5312
5865
|
* remains the independent cursor observation for the reorg-depth gate. See
|
|
@@ -5314,7 +5867,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5314
5867
|
*
|
|
5315
5868
|
* Local first: before any root, publisher or version read, the handler
|
|
5316
5869
|
* classifies what the store holds for the KA. A confirmed VM copy with
|
|
5317
|
-
* nothing else to promote settles as `already`, and
|
|
5870
|
+
* nothing else to promote settles as `already`, and an
|
|
5318
5871
|
* asset held nowhere locally goes straight to the exact-recovery queue, both
|
|
5319
5872
|
* without chain reads. Only local state whose outcome depends on the root
|
|
5320
5873
|
* takes the chain-backed path below.
|
|
@@ -5375,8 +5928,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5375
5928
|
}, ctx)
|
|
5376
5929
|
: { kind: 'present' };
|
|
5377
5930
|
if (localCandidate.kind === 'confirmed-vm') {
|
|
5378
|
-
// Trusted at the root the copy was confirmed at
|
|
5379
|
-
//
|
|
5931
|
+
// Trusted at the root the copy was confirmed at, with no chain read. An
|
|
5932
|
+
// update to it reaches VM through the `KnowledgeAssetUpdated` refresh
|
|
5933
|
+
// (`handleKAUpdatedNudge`), not through this walk: a settled ordinal is
|
|
5934
|
+
// never visited again.
|
|
5380
5935
|
if (!(await targetMayMaterialize()))
|
|
5381
5936
|
return { status: 'skip' };
|
|
5382
5937
|
this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
|
|
@@ -5386,11 +5941,10 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5386
5941
|
});
|
|
5387
5942
|
return { status: 'already', blockNumber: headBlock ?? 0 };
|
|
5388
5943
|
}
|
|
5389
|
-
if (localCandidate.kind === 'none'
|
|
5944
|
+
if (localCandidate.kind === 'none') {
|
|
5390
5945
|
// Nothing local can match any root, so the chain-backed path could only
|
|
5391
5946
|
// answer `no-swm`. Queue the same exact-recovery target it would; the
|
|
5392
5947
|
// recovery batch keeps its rotation backoff, cooldown and peer gating.
|
|
5393
|
-
// The inline mode keys its negative cache by the root and keeps reading it.
|
|
5394
5948
|
if (!(await targetMayMaterialize()))
|
|
5395
5949
|
return { status: 'skip' };
|
|
5396
5950
|
this.emitReplication({
|
|
@@ -5452,18 +6006,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5452
6006
|
}
|
|
5453
6007
|
merkleRoot = await this.chain.getLatestMerkleRoot(kaId);
|
|
5454
6008
|
cacheKey = this.vmReconcileCacheKey(localCgId, ual, merkleRoot);
|
|
5455
|
-
if (!options.deferActiveFetch && await this.shouldDeferVmReconcileByNegativeCache(cacheKey, localCgId)) {
|
|
5456
|
-
this.emitReplication({
|
|
5457
|
-
contextGraphId: localCgId,
|
|
5458
|
-
onChainCgId: onChainCgId.toString(),
|
|
5459
|
-
action: 'defer',
|
|
5460
|
-
ordinal,
|
|
5461
|
-
kaId: kaId.toString(),
|
|
5462
|
-
ual,
|
|
5463
|
-
detail: 'negative-cache',
|
|
5464
|
-
});
|
|
5465
|
-
return { status: 'pending' };
|
|
5466
|
-
}
|
|
5467
6009
|
publisherAddress = (this.chain.getLatestMerkleRootPublisher
|
|
5468
6010
|
? await this.chain.getLatestMerkleRootPublisher(kaId)
|
|
5469
6011
|
: '') ?? '';
|
|
@@ -5510,119 +6052,33 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5510
6052
|
return undefined;
|
|
5511
6053
|
}
|
|
5512
6054
|
};
|
|
5513
|
-
let swmState;
|
|
5514
|
-
let activeFetchRan = false;
|
|
5515
|
-
let activeFetchHadUsableResponse = false;
|
|
5516
|
-
const cleanMissPeerIds = new Set();
|
|
5517
6055
|
if (!(await targetMayMaterialize()))
|
|
5518
6056
|
return { status: 'skip' };
|
|
5519
|
-
|
|
6057
|
+
const outcome = await reconcileKnowledgeAsset();
|
|
5520
6058
|
if (outcome === undefined)
|
|
5521
6059
|
return { status: 'pending' };
|
|
5522
6060
|
if (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending') {
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
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,
|
|
5526
6074
|
onChainCgId: onChainCgId.toString(),
|
|
5527
|
-
action: 'defer',
|
|
5528
6075
|
ordinal,
|
|
5529
|
-
kaId: kaId.toString(),
|
|
5530
6076
|
ual,
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
localCgId,
|
|
5537
|
-
onChainCgId: onChainCgId.toString(),
|
|
5538
|
-
ordinal,
|
|
5539
|
-
ual,
|
|
5540
|
-
merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
|
|
5541
|
-
kaId: kaId.toString(),
|
|
5542
|
-
reason: outcome,
|
|
5543
|
-
},
|
|
5544
|
-
};
|
|
5545
|
-
}
|
|
5546
|
-
if (outcome === 'no-swm') {
|
|
5547
|
-
swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
|
|
5548
|
-
}
|
|
5549
|
-
// Active fetch: pull the missing snapshot core-first (selectCatchupPeers
|
|
5550
|
-
// already prioritises known cores + the preferred sync peer), then retry.
|
|
5551
|
-
// Metadata-pending exact VM content needs the same recovery: a durable
|
|
5552
|
-
// sync can supply the missing provenance-bearing assertion metadata even
|
|
5553
|
-
// when no content triples need to move.
|
|
5554
|
-
const batchAllowsFetch = options.acquireActiveFetchPermit?.() ?? true;
|
|
5555
|
-
const cooldownAllowsFetch = batchAllowsFetch
|
|
5556
|
-
&& this.shouldRunVmReconcileActiveFetch(localCgId);
|
|
5557
|
-
if (batchAllowsFetch && cooldownAllowsFetch) {
|
|
5558
|
-
activeFetchRan = true;
|
|
5559
|
-
this.emitReplication({
|
|
5560
|
-
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
5561
|
-
action: 'fetch', ordinal, kaId: kaId.toString(), ual,
|
|
5562
|
-
});
|
|
5563
|
-
let maxAttempts = 1;
|
|
5564
|
-
const fixedMaxAttempts = options.maxPeerAttempts === undefined
|
|
5565
|
-
? undefined
|
|
5566
|
-
: Math.max(1, Math.floor(options.maxPeerAttempts));
|
|
5567
|
-
if (fixedMaxAttempts !== undefined)
|
|
5568
|
-
maxAttempts = fixedMaxAttempts;
|
|
5569
|
-
for (let attempt = 0; attempt < maxAttempts
|
|
5570
|
-
&& (outcome === 'no-swm' || outcome === 'verified-vm-metadata-pending'); attempt += 1) {
|
|
5571
|
-
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
5572
|
-
break;
|
|
5573
|
-
}
|
|
5574
|
-
try {
|
|
5575
|
-
const recovery = await this.syncVmRecoveryFromConnectedPeers(localCgId, {
|
|
5576
|
-
includeSharedMemory: true,
|
|
5577
|
-
maxPeers: 1,
|
|
5578
|
-
peerRotationKey: localCgId,
|
|
5579
|
-
// This is recovery, not routine background catch-up. Without the
|
|
5580
|
-
// override it would enter through the default-background path and
|
|
5581
|
-
// be reported as `catchup-background`, merging repair traffic into
|
|
5582
|
-
// the background lane. Attribution only — mode, priority, peer
|
|
5583
|
-
// selection and the coalescing key are all unchanged.
|
|
5584
|
-
sourceOverride: 'vm-recovery',
|
|
5585
|
-
});
|
|
5586
|
-
const fetchResult = recovery.catchup;
|
|
5587
|
-
if (fixedMaxAttempts === undefined) {
|
|
5588
|
-
maxAttempts = Math.max(maxAttempts, fetchResult.totalPeers ?? fetchResult.connectedPeers ?? 0, this.vmReconcileConnectedPeerCount());
|
|
5589
|
-
}
|
|
5590
|
-
if ((fetchResult.peersTried ?? 0) === 0 && (fetchResult.syncCapablePeers ?? 0) === 0) {
|
|
5591
|
-
continue;
|
|
5592
|
-
}
|
|
5593
|
-
if (!this.vmReconcileActiveFetchHadUsableResponse(fetchResult)) {
|
|
5594
|
-
continue;
|
|
5595
|
-
}
|
|
5596
|
-
activeFetchHadUsableResponse = true;
|
|
5597
|
-
for (const peerId of recovery.cleanMissPeerIds) {
|
|
5598
|
-
cleanMissPeerIds.add(peerId);
|
|
5599
|
-
}
|
|
5600
|
-
}
|
|
5601
|
-
catch (err) {
|
|
5602
|
-
this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5603
|
-
if (fixedMaxAttempts === undefined) {
|
|
5604
|
-
maxAttempts = Math.max(maxAttempts, this.vmReconcileConnectedPeerCount());
|
|
5605
|
-
}
|
|
5606
|
-
continue;
|
|
5607
|
-
}
|
|
5608
|
-
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
5609
|
-
break;
|
|
5610
|
-
}
|
|
5611
|
-
if (!(await targetMayMaterialize()))
|
|
5612
|
-
return { status: 'skip' };
|
|
5613
|
-
outcome = await reconcileKnowledgeAsset();
|
|
5614
|
-
if (outcome === undefined)
|
|
5615
|
-
return { status: 'pending' };
|
|
5616
|
-
}
|
|
5617
|
-
if (outcome === 'no-swm') {
|
|
5618
|
-
swmState = await this.collectVmReconcileSwmCandidateState(localCgId);
|
|
5619
|
-
swmState.cleanMissPeerIds = createVmReconcileCleanMissPeerIds(swmState.peerTopology, [...cleanMissPeerIds]);
|
|
5620
|
-
}
|
|
5621
|
-
}
|
|
5622
|
-
else {
|
|
5623
|
-
const reason = batchAllowsFetch ? 'per-CG cooldown' : 'per-batch fetch budget';
|
|
5624
|
-
this.log.info(ctx, `Phase B: active fetch for "${localCgId}" (ordinal ${ordinal}) skipped by ${reason}`);
|
|
5625
|
-
}
|
|
6077
|
+
merkleRoot: Array.from(merkleRoot, (byte) => byte.toString(16).padStart(2, '0')).join(''),
|
|
6078
|
+
kaId: kaId.toString(),
|
|
6079
|
+
reason: outcome,
|
|
6080
|
+
},
|
|
6081
|
+
};
|
|
5626
6082
|
}
|
|
5627
6083
|
if (options.isTargetCurrent && !options.isTargetCurrent()) {
|
|
5628
6084
|
return { status: 'skip' };
|
|
@@ -5638,7 +6094,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5638
6094
|
case 'promoted':
|
|
5639
6095
|
this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
|
|
5640
6096
|
this.pruneVmReconcileCacheKeySiblings(cacheKey);
|
|
5641
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
5642
6097
|
this.recentReconciledUals.add(cacheKey);
|
|
5643
6098
|
await this.confirmAndRememberVmReconcileFinalizedSlot(localCgId, onChainCgId, ordinal, rememberSlotBlock, kaId, merkleRoot, publisherAddress);
|
|
5644
6099
|
this.emitReplication({
|
|
@@ -5655,7 +6110,6 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5655
6110
|
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
5656
6111
|
action: 'already', ordinal, kaId: kaId.toString(), ual,
|
|
5657
6112
|
});
|
|
5658
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
5659
6113
|
return { status: 'already', blockNumber: completionBlock };
|
|
5660
6114
|
case 'stale-target':
|
|
5661
6115
|
this.clearVmReconcileRotationStateForSlot(localCgId, onChainCgId, ordinal);
|
|
@@ -5666,20 +6120,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5666
6120
|
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
5667
6121
|
action: 'already', ordinal, kaId: kaId.toString(), ual,
|
|
5668
6122
|
});
|
|
5669
|
-
this.deleteVmReconcileNegativeCacheEntry(cacheKey);
|
|
5670
6123
|
return { status: 'already', blockNumber: completionBlock };
|
|
5671
|
-
case 'no-swm':
|
|
5672
|
-
if (activeFetchRan && !activeFetchHadUsableResponse) {
|
|
5673
|
-
this.clearVmReconcileActiveFetchCooldown(localCgId);
|
|
5674
|
-
}
|
|
5675
|
-
else {
|
|
5676
|
-
this.recordVmReconcileNegativeCache(cacheKey, localCgId, swmState ?? await this.collectVmReconcileSwmCandidateState(localCgId));
|
|
5677
|
-
}
|
|
5678
|
-
this.emitReplication({
|
|
5679
|
-
contextGraphId: localCgId, onChainCgId: onChainCgId.toString(),
|
|
5680
|
-
action: 'defer', ordinal, kaId: kaId.toString(), ual, detail: activeFetchRan && !activeFetchHadUsableResponse ? 'network-unavailable' : outcome,
|
|
5681
|
-
});
|
|
5682
|
-
return { status: 'pending' };
|
|
5683
6124
|
case 'receipt-revalidation-pending':
|
|
5684
6125
|
case 'unverified':
|
|
5685
6126
|
default:
|
|
@@ -5899,7 +6340,7 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
5899
6340
|
// on nodes that have persisted the CG's `allowedPeers`; host-only
|
|
5900
6341
|
// cores never see it.
|
|
5901
6342
|
try {
|
|
5902
|
-
const allowedPeers = await this.
|
|
6343
|
+
const allowedPeers = await this.resolveSwmAllowedPeersForCurrentAuthority(req.contextGraphId);
|
|
5903
6344
|
if (allowedPeers !== null) {
|
|
5904
6345
|
anyAuthoritySourceFound = true;
|
|
5905
6346
|
if (allowedPeers.includes(fromPeerId)) {
|
|
@@ -6229,6 +6670,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6229
6670
|
* two would race / duplicate every apply (Codex PR #610 R4).
|
|
6230
6671
|
*/
|
|
6231
6672
|
async enableSwmHostModeFor(contextGraphId) {
|
|
6673
|
+
const session = this.gossipSession;
|
|
6674
|
+
if (!session.active)
|
|
6675
|
+
return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
|
|
6232
6676
|
if (!this.swmHostModeStore) {
|
|
6233
6677
|
return { subscribed: false, alreadySubscribed: false, hostingEnabled: false };
|
|
6234
6678
|
}
|
|
@@ -6252,6 +6696,9 @@ export class SwmHostModeMethods extends DKGAgentBase {
|
|
|
6252
6696
|
}
|
|
6253
6697
|
const hostKey = this.canonicalSwmHostModeKey(contextGraphId);
|
|
6254
6698
|
const curated = await this.isCuratedForHostMode(contextGraphId);
|
|
6699
|
+
if (!session.active || this.gossipSession !== session) {
|
|
6700
|
+
return { subscribed: false, alreadySubscribed: false, hostingEnabled: true };
|
|
6701
|
+
}
|
|
6255
6702
|
if (this.swmHostModeSubscribed.has(hostKey)) {
|
|
6256
6703
|
// Idempotent re-entry: even when the subscription is already
|
|
6257
6704
|
// active, re-probe registration state. This handles the
|