zettelkasten.federation¶
zettelkasten.federation ¶
Read-only federation helpers for projecting related Zettelkasten repos.
Mirrors memory.federation: another repo's .zettelkasten/ tree can be
browsed alongside this one as a read-only overlay in the dashboard. Repos are
configured via a federated_repos list in .zettelkasten/config.yaml.
Federated graphs are surfaced with namespaced names (<repo_id>:<graph>) so
they never collide with local graphs. The dashboard routes resolve a namespaced
name back to its repo and build a read-only :class:~zettelkasten.graph.ZettelGraph
pointed at that repo's directory; because federated repos commit their .md
notes but gitignore their .angelo/zettel/*.kgl embedding caches, semantic
search/cluster for a federated graph is served by rebuilding its index locally
from the committed notes (see ZettelGraph._embedding_cache_path).
Everything here is best-effort and read-only: a misconfigured repo yields a non-fatal validation error rather than raising.
FederatedRepo
dataclass
¶
Validated configuration for one related repository.
graphs is an optional source-graph allowlist that FAILS CLOSED:
Nonemeans "expose ALL graphs" — the default, set only when the config OMITSgraphs:or gives it an explicit YAML null. This is the fully backward-compatible behavior.- a non-empty
frozensetexposes exactly those named source graphs (plus the always-shared_crosssynthesis graph). - an EMPTY
frozenset()exposes NOTHING but_cross— this is what a present-but-empty, blank-only, or malformedgraphs:value coerces to, so a misconfiguration hides everything rather than silently exposing all.
Source code in zettelkasten/federation.py
federation_cache_root ¶
Directory holding decrypted bundle caches (one subdir per synced peer).
Anchored at <root>/.angelo/federation-cache where root defaults to
the workspace root — the same anchor :func:configured_repos uses for
relative repo paths. Both the sync writer (CLI) and this reader resolve
the cache to the same place; the .angelo root is shared with the memory
federation cache so a peer can publish both a .memory and a
.zettelkasten cache under one <peer_id>/ dir.
Source code in zettelkasten/federation.py
graph_allowed ¶
graph_allowed(repo: FederatedRepo, local_name: str) -> bool
Whether a federated repo exposes local_name under its allowlist.
Returns True when the repo has no allowlist (repo.graphs is None → all
graphs exposed), when local_name is the shared _cross synthesis graph
(ALWAYS exposed — it is not a source, and projects implicitly append it), or
when local_name is explicitly listed in the allowlist.
Source code in zettelkasten/federation.py
federated_review_allowed ¶
federated_review_allowed(repo: FederatedRepo, manifest: dict[str, Any], *, load_project: Callable[[str], dict[str, Any]] | None = None) -> bool
Whether a federated repo exposes a review under its allowlist (subset rule).
Single source of truth for review visibility so the three seams
(:func:federated_reviews list overlay, _ensure_federated_review_allowed,
and _load_review_or_error in the dashboard routes) cannot diverge.
- No allowlist (
repo.graphs is None) → alwaysTrue(unchanged behavior). - Non-empty
manifest['graph']→ gate on that graph exactly as before (graph_allowed(repo, graph)). - Empty/missing
graph(a PROJECT-SCOPED review) → resolve the review'sprojectmanifest and require ALL of itssourcesallowed, mirroring :func:federated_projects. A review with neither a graph nor a resolvable project, or a project with no sources, is an edge case: hide it under an allowlist (conservative but consistent).
load_project maps a project name to its manifest dict; it defaults to
reading the peer's .zettelkasten/_projects/ via
zettelkasten.graph.load_project. Any resolution failure is treated as
"not resolvable" → hidden.
Source code in zettelkasten/federation.py
federated_project_allowed ¶
federated_project_allowed(repo: FederatedRepo, project: dict[str, Any]) -> bool
Whether a federated repo exposes a project under its allowlist (subset rule).
Single source of truth for project visibility so the two seams
(:func:federated_projects list overlay and _load_project_or_404 in the
dashboard routes) cannot diverge. Mirrors :func:federated_review_allowed:
- No allowlist (
repo.graphs is None) → alwaysTrue(unchanged behavior, INCLUDING a source-less project which stays visible/resolvable). - Under an allowlist, a project with EMPTY or MISSING
sourcesis hidden:all([])is vacuouslyTrue, so without this guard a source-less peer project would leak even under agraphs: []lockdown. - Otherwise, require EVERY source graph allowed (
_crossis always allowed), so a visible project never dangles on a hidden source.
Source code in zettelkasten/federation.py
split_namespace ¶
Split <repo_id>:<local_id> into its parts, or None if not namespaced.
Source code in zettelkasten/federation.py
configured_repos ¶
configured_repos(config: dict[str, Any] | None = None, root: Path | None = None) -> tuple[list[FederatedRepo], list[dict[str, str]]]
Return enabled, valid federated repos plus non-fatal validation errors.
Source code in zettelkasten/federation.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | |
find_repo ¶
find_repo(repo_id: str, config: dict[str, Any] | None = None, root: Path | None = None) -> FederatedRepo | None
Resolve a configured repo by id (validated), or None.
Source code in zettelkasten/federation.py
federated_graph_list ¶
federated_graph_list(repo: FederatedRepo) -> list[dict[str, Any]]
List a federated repo's note-graphs as namespaced, read-only graph metas.
Shape matches the local /graphs rows, plus federation fields so the UI
can group and badge them.
Source code in zettelkasten/federation.py
federated_projects ¶
federated_projects(repo: FederatedRepo) -> list[dict[str, Any]]
List a federated repo's projects as namespaced, read-only manifests.
Source code in zettelkasten/federation.py
federated_reviews ¶
federated_reviews(repo: FederatedRepo) -> list[dict[str, Any]]
List a federated repo's reviews as namespaced, read-only summaries.
Mirrors :func:federated_projects, but a review manifest carries a heavy
editorial overlay block (tiering/exclusions/ordering/renames/scaffolding)
that the picker does not need, so this returns only the manifest's scalar
summary (name/title/project/graph/question) plus the federation fields. The
full reconciled view is served on demand by GET /reviews/{name}.
Federated reviews are strictly READ-ONLY — there is no federated write path.
Source code in zettelkasten/federation.py
latest_mtime ¶
Newest *.md mtime across configured federated repos (for cache keys).