Split out of #2006 / PR #2007, where the fan-out reduction was disabled rather than shipped on an unsound foundation.
Why it was disabled
PR #2007 let one peer — the Context Graph's curator — end a catch-up walk early, which is what removes the 5-6x amplification #2006 reported. Establishing which peer is the curator turned out to be the hard part, and three successive attempts were each unsound:
- The merged projection (
getCgMeta()) unions _meta + AGENTS + _catalog + ONTOLOGY under first-wins precedence and discards which graph supplied each fact.
- Reading the graph's own
_meta alone plus ONTOLOGY: ONTOLOGY is network-replicated, so a node can hold an injected DKG_CREATOR without holding the real one, and local uniqueness proves nothing about the network.
- Reading
<cg>/_meta alone: source-qualifying by GRAPH establishes which graph HOLDS the rows, not which writer SUPPLIED them. Ordinary durable-meta catch-up admits IRI-subject descriptive metadata for the CG entity subject and inserts it verbatim into that exact graph, so a contacted peer can supply the rows the check reads. Three suffice — rdf:type, accessPolicy, and a curator DID naming itself.
Tightening the record's shape cannot fix (3); completeness and uniqueness only raise the number of rows the peer must send.
What is missing
A curator-to-peer binding from a source a peer cannot write. Surveyed, none is usable today:
| Candidate |
Status |
| Signed join approval |
The join-approved payload is plain JSON with no curator key material. Only the JOINER's delegation is signed and verified. |
| On-chain binding |
Chain has an authoritative CG owner wallet and participantAgents, but nothing maps a wallet to a libp2p peer id. ProfileStorage.nodeId is documented display-only self-reported. |
| Curator-signed metadata snapshot |
context-graph-private-meta-proof / context-graph-public-meta-proof are pure quad-shape checks. No signature field, no key recovery. Sync signs the REQUEST only; responses are unsigned. |
| Locally persisted join-approval record |
urn:dkg:local:requester-join-state holds curator-peer-id, written pre-transport, never synced, not peer-writable — genuinely writer-qualified. But it is TOFU-from-invite, keyed by (cgId, agentAddress), and absent for public CGs, for the curator's own node, and for members admitted without a V10 invite. |
The primitive for a real fix already exists and is simply unused for this: signAgentDelegation / verifyAgentDelegation can bind (scope, curator agent address, delegatee peer id, issuedAt, expiresAt). A curator never issues one for itself, it is not carried in the join-approved payload, and _meta strips signatures on promotion.
Scope
- Introduce a curator-signed binding artifact, or an on-chain curator→peer edge, and persist it where sync cannot write.
- Re-enable authority in
resolveCuratorSyncPeer gated on that artifact only. The positive case belongs in the existing grants authority to NO route test in packages/agent/test/sync-policy.test.ts, not in a relaxation of the shapes it covers.
- Re-enable the wave walk.
catchup-runner-worker-impl.ts currently collapses to a single bounded pass when no authority resolves, because waves without a possible early stop only add barrier latency.
- Add an end-to-end regression through the REAL admission → store → projection → resolver path. A hand-built resolver fixture cannot see this class of bug: every current CLI worker test stubs
prepareCatchup and hands itself authoritativePeerId, so the CLI suite stays green over inert production code.
Still shipped in #2007
The correctness and observability half is unaffected and does not depend on authority: fail-closed readiness (unanimous-empty voids, incompleteResponders, the persistence split), the wall-clock backpressure budget, the bounded SyncAdmissionSource operation labels, and the worker-exit latch.
Split out of #2006 / PR #2007, where the fan-out reduction was disabled rather than shipped on an unsound foundation.
Why it was disabled
PR #2007 let one peer — the Context Graph's curator — end a catch-up walk early, which is what removes the 5-6x amplification #2006 reported. Establishing which peer is the curator turned out to be the hard part, and three successive attempts were each unsound:
getCgMeta()) unions_meta+ AGENTS +_catalog+ ONTOLOGY under first-wins precedence and discards which graph supplied each fact._metaalone plus ONTOLOGY: ONTOLOGY is network-replicated, so a node can hold an injectedDKG_CREATORwithout holding the real one, and local uniqueness proves nothing about the network.<cg>/_metaalone: source-qualifying by GRAPH establishes which graph HOLDS the rows, not which writer SUPPLIED them. Ordinary durable-meta catch-up admits IRI-subject descriptive metadata for the CG entity subject and inserts it verbatim into that exact graph, so a contacted peer can supply the rows the check reads. Three suffice —rdf:type,accessPolicy, and acuratorDID naming itself.Tightening the record's shape cannot fix (3); completeness and uniqueness only raise the number of rows the peer must send.
What is missing
A curator-to-peer binding from a source a peer cannot write. Surveyed, none is usable today:
participantAgents, but nothing maps a wallet to a libp2p peer id.ProfileStorage.nodeIdis documented display-only self-reported.context-graph-private-meta-proof/context-graph-public-meta-proofare pure quad-shape checks. No signature field, no key recovery. Sync signs the REQUEST only; responses are unsigned.urn:dkg:local:requester-join-stateholdscurator-peer-id, written pre-transport, never synced, not peer-writable — genuinely writer-qualified. But it is TOFU-from-invite, keyed by(cgId, agentAddress), and absent for public CGs, for the curator's own node, and for members admitted without a V10 invite.The primitive for a real fix already exists and is simply unused for this:
signAgentDelegation/verifyAgentDelegationcan bind (scope, curator agent address, delegatee peer id, issuedAt, expiresAt). A curator never issues one for itself, it is not carried in the join-approved payload, and_metastrips signatures on promotion.Scope
resolveCuratorSyncPeergated on that artifact only. The positive case belongs in the existinggrants authority to NO routetest inpackages/agent/test/sync-policy.test.ts, not in a relaxation of the shapes it covers.catchup-runner-worker-impl.tscurrently collapses to a single bounded pass when no authority resolves, because waves without a possible early stop only add barrier latency.prepareCatchupand hands itselfauthoritativePeerId, so the CLI suite stays green over inert production code.Still shipped in #2007
The correctness and observability half is unaffected and does not depend on authority: fail-closed readiness (unanimous-empty voids,
incompleteResponders, the persistence split), the wall-clock backpressure budget, the boundedSyncAdmissionSourceoperation labels, and the worker-exit latch.