Expand description
Crossing a workspace boundary — opening a peer a PeerResolver located,
and descending the spanning tree into it. The layer above
prov_graph::peer, which stops at an address on purpose.
Crossing a workspace boundary — opening a peer, and descending into it.
prov_graph::peer declares where the other workspaces are and stops
there, in so many words: “nothing here opens a workspace, and nothing here
can: reading the peer would need a second
ReadStorage and a second
IdIndex, which only the host has.” That is
true of prov-graph, and it stays true — it is why Graph
keeps two type parameters and why no method on it takes a resolver.
It is not true one layer up. discovery::build already
opens a second Workspace at an arbitrary directory out of the same
storage handle, because the storage seam is device-wide rather than
root-scoped: a crate that can walk up the filesystem to find a root it was
not given can also open a root a resolver hands it. So the descent lives
here, beside discovery, and prov-graph learns nothing.
This keeps the promise peer makes rather than breaking
it. Following a foreign reference is still “a second step after
resolution, taken by a caller that wants it”; all that changes is that prov
ships that second step once instead of each host writing it.
§What stays refused
- A
PeerLocation::Urlis never opened. prov does no network I/O, and this is the one absolute in the module: a URL peer is an address to render, not a root to read. It isRefusal::Urlunder every trust level, including one a resolver has confirmed by its own means. - No writes across a boundary. Everything here reads. Registration stays
a publish-time contract — prov never reaches into another workspace to
register on its behalf — so a reference to an unpublished foreign document
can still dangle, and a
mvstill rewrites no peer’s inbound references. - No peer table in the config. A name is a fact about an archive, a
location is a fact about a disk; being able to follow a map does not make
the map the archive’s business. Every location here comes from a
PeerResolverthe host supplied. - No resolver on
Graph, and no cost to a traversal that never crosses. - No following by default.
NoPeersis what a consumer that was given no resolver uses, and under itdescendis exactlytreewith every foreign leaf carryingRefusal::Unknown.
§Every refusal is an answer
peer has no error case, on purpose: “a peer that cannot
be found, cannot be read, or turns out to be someone else is never a failure,
because a foreign reference is carried whether or not it resolves.” That does
not change because someone asked to follow it, so Refusal is never an
Err — it is the Boundary a leaf carries, and it is what lets a followed
tree say why (“the peer notes is on record at a directory that calls
itself journal”) rather than silently showing what an unfollowed tree
shows.
§Termination
Two workspaces may list each other, and an org root plus N repositories that
each link back is the expected shape rather than an exotic one. So descend
keeps a trail of the workspaces on the current path from the origin, and
refuses a peer already on it with Refusal::Cycle — a leaf, not an error.
A trail rather than a global visited set, for the reason
tree gives for the same choice one level down:
revisiting a workspace from another branch is fine, because each branch
materializes its own subtree, and that is what makes this a tree rather than
a DAG rendered flat. An org root that names two documents of one repository —
its README and its docs/tasks index, say — renders both, exactly as
Graph::tree renders a document reached from two branches. Only a back-edge
to a workspace on the current path is a cycle. NodeKind::Cycle stays a
statement about one workspace’s own spanning tree; this is the same shape one
level up.
The trail is keyed on both halves, because both failures are reachable:
two names for one directory (a symlinked checkout) and two directories
claiming one name (the failure PeerLookup::confirm exists to catch,
arriving one hop later). The directory half is the lexically normalized
root directory — neither storage port offers a canonicalize, so a symlink
is not resolved away and it is the declared workspace_id that catches that
case. The origin is seeded onto the trail, so a peer that lists it back
stops there.
Cost stays one open per peer whatever the trail does: opened peers are
memoized by name for the walk’s duration, so an org root with many links into
one repository opens it once and walks it as many times as it is named.
Federation::workspaces therefore lists every peer opened, once, in
first-reached order.
Structs§
- Descent
- How far a
descendgoes, and on whose say-so. - Federation
- A spanning tree that crossed one or more workspace boundaries, and the workspaces it crossed into.
- Node
- One node of a federated spanning tree.
- Peer
- A peer workspace the reader opened for reading — read-only, always.
- Reached
- One workspace a
descendentered.
Enums§
- Boundary
- What a node’s position says about the boundary it sits on.
- Crossing
- The outcome of
open_peer: a workspace, or the reason there is not one. - Refusal
- Why a boundary was not crossed. Every variant is an answer, never an error.
- Trust
- How much doubt a reader will accept about which workspace it is opening.
Constants§
- DEFAULT_
DEPTH - How many boundaries a
descendcrosses before it stops, when the caller names no bound of its own. Crossings, not tree levels: a workspace’s own depth is bounded by its spanning tree and needs no help.
Functions§
- descend
- Materialize the spanning tree from
start, following every confirmed foreign spanning leaf into the peer it names and continuing there. - open_
discovered - Open the read-only
WorkspaceaDiscovereddescribes. - open_
peer - Open the workspace named
workspace, if the host knows where it is andtrustallows following what it says.