Share a Slice of Your Chain โ and Have That Boundary Actually Hold
Partial-access trust routing: gating every read where the bytes leave
When you grant someone access to part of your chain โ a branch, a handful of files โ you want one thing to be true: they read what you granted and nothing else, and that limit holds no matter how they ask. This piece is about how NAOMS makes that guarantee real for cross-peer reads.
For builders
Most access-control bugs are not bugs of logic. They're bugs of location โ the check lives one layer above the place where the bytes actually move, so there's a gap underneath it where data can leave without ever passing the gate. This piece is about closing that gap for cross-peer reads in NAOMS: the partial-access trust routing work that shipped this week.
The problem statement is simple to say and hard to honor: a peer should be able to read the parts of a chain it has been granted, and nothing else โ and the system must enforce that at the point where the data leaves, not merely where the request arrives.
Two read planes, one rule
A chain in NAOMS is two things at once. There's the graph โ the structured, queryable nodes and edges. And there's the blob/file plane โ the content-addressed bytes that the graph references but does not inline (per the storage design, gossip carries graph state but never moves your big files; blobs travel on a separate fetch path).
Partial access has to mean the same thing on both planes, or it means nothing. If we can be denied a node in the graph but still pull the blob it points to by its hash, the graph gate is theater. So the rule is one rule applied in two places:
- Graph reads go through a read-gate before any node is returned.
- Blob/file reads go through the same gate at the transport boundary: every blob read is funneled through a read-capability check before a single byte is handed back.
The shape that matters: the file plane defers to the same authorization decision as the graph plane. There is exactly one answer to "is this peer allowed to read this hash on this branch," and both planes ask it.
Default-deny, decided where the bytes leave
The location principle is the whole point. The control plane โ the handlers that field a cross-peer read request โ does not get to assume that because a request was well-formed, the data may flow. Each one gates the read through the read-capability check before resolving. The check is keyed on four things: the requesting peer's identity, the chain, the branch, and the specific content hash being asked for. If there's no read capability that admits this peer to this hash on this branch, the read is refused. Default-deny isn't a policy banner at the front door; it's the absence of an affirmative grant at the exact moment the bytes would otherwise leave.
This is also why the gate has to live at the transport boundary and not in the application logic above it. An application-layer check can be bypassed by any code path that reaches the bytes a different way. A boundary check cannot โ because every path to the bytes goes through the boundary. That's the difference between "we check permissions" and "permission is a property of the read itself."
The lazy-download path is where it gets real
The honest reason this is advanced and not merely tidy: the gate has to hold on the lazy path, where a peer is streaming content it doesn't have locally yet. When a peer asks for a branch it's only partially entitled to, the system can't just ship the whole chain and trust the reader to look away from the parts it shouldn't see. It has to resolve, per hash, whether this fetch is admitted โ during the fetch, not before it.
That's why the capability check runs inside the byte-handing path rather than as a one-time admission ticket. The cross-peer fetch carries the requester's identity all the way down to the byte-gate, so the gate can make a per-hash decision against a live set of capabilities. A peer with read access to branch A and none to branch B fetching from the same chain gets exactly branch A's bytes and a refusal on branch B's โ decided at the fetch, hash by hash.
This is the same architectural instinct that the broader blob-access-control work hardened later: a blob read must be gated at the bytes plane, with the authorization decision made on the very layer that owns the bytes โ never assumed by a layer that merely orchestrates. The partial-read shape came first, on the chain-branch path; the later work generalized "gate at the bytes" across the whole blob substrate. Read together, they say one thing: authorization is a property of the read, enforced where the read happens.
How it's proven
A partial-access claim is only as good as the test that a denied peer is actually denied while a granted peer is actually served โ both, at once, against real daemons. The verification drives this with a two-device read-gate scenario: separate peers, separate capability sets, real cross-peer fetches over the transport, asserting that the granted read returns the bytes and the ungranted read is refused โ not that "a read happened," but that the right read happened and the wrong one didn't. (The failure-mode test โ a non-admitted peer is refused โ is paired with its success twin โ an admitted peer is served โ so neither can pass for the wrong reason.)
That pairing is the non-negotiable part. A read-gate that only ever ran the "denied" case could be green because the whole path is broken โ nobody gets anything. The success twin is what proves the gate is a gate and not a wall.
What this buys a user
Concretely: you can share a slice of a chain โ a branch, a subset of files โ with a peer, and the system enforces that boundary at the protocol level, on the lazy-streamed path, default-deny, with the decision made where the bytes leave. The peer reads exactly what you granted and is refused the rest, and that refusal isn't a UI nicety you could route around; it's a property of the transport.
It's a small-sounding feature with a large honesty cost if you get the location wrong. This work got the location right: one authorization decision, asked on both planes, enforced at the byte-gate.
Written by AI agents from real project logs; owned and edited by Mujo.