Summary
When a publisher captures with accessPolicy: 'allowList' and allowedPeers: [<peerId>], the listed peer never actually receives the private payload. The publisher correctly authorizes the peer (server-side AccessHandler would respond if asked), but nothing on the receiver's side ever asks.
Surfaced empirically during the EPCIS async-private feature work (feat/epcis-async-private integration branch), where slice/04 and slice/06 had to demote the "allow-list peer query returns private payload" scenario to informational.
What works
- Publisher (N1) captures with
allowedPeers: [N2.peerId] ✓
- KC metadata records the
accessPolicy + allowedPeers correctly (packages/publisher/src/dkg-publisher.ts:984-998, metadata.ts:103)
- Public anchor lands on every CG subscriber via gossip ✓ (N1, N2, N3 all see the anchor)
- N1's
AccessHandler is wired up — when a peer asks for the private payload, it correctly enforces the allow-list (packages/publisher/src/access-handler.ts:98-110)
What's missing
There is no automatic mechanism on the receiver (N2) to fetch the private payload after the anchor arrives. Specifically:
AccessClient class exists (packages/publisher/src/access-client.ts:26) and exposes requestAccess(publisherPeerId, kaUal), but nothing in packages/agent/ or packages/cli/ ever instantiates or calls it.
- There is no
/api/access/request (or equivalent) HTTP endpoint that would let a node operator manually trigger the fetch.
- There is no listener on the receiver's event bus that watches for newly-arrived anchors carrying its peerId in
allowedPeers and triggers a pull.
Net result: accessPolicy: 'allowList' grants permission that is currently impossible to exercise from the receiver side. N2's <cg>/_private partition stays empty after a finalized allow-list capture on N1, even though the peer was explicitly authorized.
Impact on the EPCIS PRD
The EPCIS async-private feature has a user story (PRD #21) that says: "As a node operator on an allowedPeers list, I want private events shared with my node to surface in my events response just like the publisher's, so that allow-list sharing gives me parity with the publisher for query."
That promise is currently structurally unmet. The privacy gate works correctly (non-allowed peers cannot fetch); the privacy delivery does not happen automatically.
Evidence
Suggested fix direction
Two reasonable options:
A. Receiver-side auto-pull. When a public anchor arrives on the gossip topic with allowedPeers matching the local peerId, queue an AccessClient.requestAccess call against the publisher peer. Likely lives in the agent's gossip-publish-handler or a new lightweight subscriber on the access-protocol topic.
B. Receiver-side manual pull API. Expose AccessClient via a new POST /api/access/request daemon route. Node operators trigger fetches explicitly. Smaller scope, but pushes the work onto callers.
A is closer to PRD intent; B is the smaller patch. A new ADR captures the choice.
Out of scope for this issue
- Payment / cost handling on
requestAccess (existing paymentProof argument left untouched)
- Allow-list updates after publish (separate KC metadata mutation question)
Priority
Medium. Not blocking — the EPCIS feature lands without this, and the privacy gate is verifiably correct. But the delivery gap should be closed before users build production allow-list flows on top of EPCIS.
Summary
When a publisher captures with
accessPolicy: 'allowList'andallowedPeers: [<peerId>], the listed peer never actually receives the private payload. The publisher correctly authorizes the peer (server-sideAccessHandlerwould respond if asked), but nothing on the receiver's side ever asks.Surfaced empirically during the EPCIS async-private feature work (
feat/epcis-async-privateintegration branch), where slice/04 and slice/06 had to demote the "allow-list peer query returns private payload" scenario to informational.What works
allowedPeers: [N2.peerId]✓accessPolicy+allowedPeerscorrectly (packages/publisher/src/dkg-publisher.ts:984-998,metadata.ts:103)AccessHandleris wired up — when a peer asks for the private payload, it correctly enforces the allow-list (packages/publisher/src/access-handler.ts:98-110)What's missing
There is no automatic mechanism on the receiver (N2) to fetch the private payload after the anchor arrives. Specifically:
AccessClientclass exists (packages/publisher/src/access-client.ts:26) and exposesrequestAccess(publisherPeerId, kaUal), but nothing inpackages/agent/orpackages/cli/ever instantiates or calls it./api/access/request(or equivalent) HTTP endpoint that would let a node operator manually trigger the fetch.allowedPeersand triggers a pull.Net result:
accessPolicy: 'allowList'grants permission that is currently impossible to exercise from the receiver side. N2's<cg>/_privatepartition stays empty after a finalized allow-list capture on N1, even though the peer was explicitly authorized.Impact on the EPCIS PRD
The EPCIS async-private feature has a user story (PRD #21) that says: "As a node operator on an
allowedPeerslist, I want private events shared with my node to surface in my events response just like the publisher's, so that allow-list sharing gives me parity with the publisher for query."That promise is currently structurally unmet. The privacy gate works correctly (non-allowed peers cannot fetch); the privacy delivery does not happen automatically.
Evidence
docs/epcis/devnet-s4-e2e-2026-05-05.mdcaveat the context graph explodes even after 4-5 rounds in the game #3docs/epcis/devnet-cli-e2e-2026-05-05.mdcaveat the context graph explodes even after 4-5 rounds in the game #3docs/epcis/devnet-results-2026-05-05.mdscenario 8 demoted to informationalSuggested fix direction
Two reasonable options:
A. Receiver-side auto-pull. When a public anchor arrives on the gossip topic with
allowedPeersmatching the local peerId, queue anAccessClient.requestAccesscall against the publisher peer. Likely lives in the agent's gossip-publish-handler or a new lightweight subscriber on the access-protocol topic.B. Receiver-side manual pull API. Expose
AccessClientvia a newPOST /api/access/requestdaemon route. Node operators trigger fetches explicitly. Smaller scope, but pushes the work onto callers.A is closer to PRD intent; B is the smaller patch. A new ADR captures the choice.
Out of scope for this issue
requestAccess(existingpaymentProofargument left untouched)Priority
Medium. Not blocking — the EPCIS feature lands without this, and the privacy gate is verifiably correct. But the delivery gap should be closed before users build production allow-list flows on top of EPCIS.