@@ -388,7 +388,122 @@ graph using the *same* vocabularies the public web already speaks.
388388
389389---
390390
391- # # 11. Versioning
391+ # # 11. Federation: registries of bundles
392+
393+ A single LOKF bundle is self-contained, but knowledge rarely is : one team's
394+ ` Metric` ` dependsOn` another team's `GlossaryTerm`, whose canonical definition
395+ lives in a *different* bundle in a *different* repository. A **registry**
396+ (*meta-lokf*) aggregates a set of independent bundles into one navigable graph
397+ **without a central database**, so an agent can follow a relation out of one
398+ bundle into another and read the target concept's source document. A registry is
399+ itself just linked data — a DCAT catalog of catalogs — so it is built with the
400+ same vocabularies and tooling as the bundles it indexes.
401+
402+ # ## 11.1 The producer contract
403+
404+ A bundle becomes federatable by publishing the artifacts `lokf export` writes to
405+ a location a registry can read (GitHub Pages, a w3id-fronted host, or a sibling
406+ checkout). Its **`base_iri`** (from the root `index.md`, §4) is its identity in
407+ the registry.
408+
409+ | Artifact | Role in federation |
410+ |--------------------|------------------------------------------------------------------------|
411+ | `graph.nt` | The whole bundle as N-Triples — the RDF a registry loads for SPARQL. |
412+ | `concepts.jsonld` | Every concept's frontmatter + body under one `@context`/`@graph` — offline access to a concept's *source document* without re-fetching markdown. |
413+ | `graph.json` | cytoscape.js elements — drives the (multi-bundle) graph explorer. |
414+ | `datasets.jsonld` | schema.org `Dataset` docs — discovery via Google Dataset Search. |
415+
416+ # ## 11.2 The registry manifest
417+
418+ A registry is a git-committed `lokf-registry.yaml` — a `dcat:Catalog` of
419+ ` void:Dataset` entries, one per member bundle, keyed by `base_iri`
420+ (== `void:uriSpace`). Each entry records where the member's artifacts live, a
421+ lightweight `void` planning index (triple and per-type counts, so an agent can
422+ pick a bundle *without* dereferencing it), and harvest `status`. It parses with a
423+ plain YAML reader — no LinkML on the read path — and round-trips to a crawlable
424+ ` registry.jsonld` .
425+
426+ ` ` ` yaml
427+ lokf_registry_version: "0.1"
428+ type: dcat:Catalog
429+ id: https://w3id.org/lokf/registry/example
430+ repos:
431+ - base_iri: https://acme.example/knowledge/ # routing key == void:uriSpace
432+ title: Acme Knowledge Bundle
433+ repo: git+https://github.com/acme/knowledge.git@main
434+ source_base: https://raw.githubusercontent.com/acme/knowledge/main
435+ distribution:
436+ rdf: https://acme.example/knowledge/graph.nt # SPARQL harvest source
437+ concepts: https://acme.example/knowledge/concepts.jsonld # offline document access
438+ void: { triples: 86, class_partition: { Metric: 1, Dataset: 1, GlossaryTerm: 1 } }
439+ id_index: [ ] # explicit ` id:` IRIs outside base_iri
440+ status : ok
441+ ` ` `
442+
443+ ### 11.3 Cross-bundle resolution
444+
445+ The load-bearing operation is **` owner(iri)`**: the registered `base_iri` that is
446+ the **longest string prefix** of the IRI. This is the exact inverse of IRI
447+ minting (§7) : a concept's IRI is `base_iri + concept_id`, so given any IRI,
448+ ` concept_id = iri[len(base_iri):]` and the owning bundle is its longest-prefix
449+ match. Resolution is therefore **pure string arithmetic — no network, no shared
450+ database** — and a cross-bundle relation whose author wrote a full `https://…`
451+ target is *already* a correct triple. Three rules keep it trustworthy :
452+
453+ 1. **Explicit-id index.** A concept's frontmatter `id:` may diverge from
454+ ` base_iri + concept_id` (§5). Such IRIs are harvested into the entry's
455+ ` id_index` and checked as an exact-match fallback before an IRI is declared
456+ external, so they still route.
457+ 2. **Namespace precedence & non-nesting.** The packaged vocabulary namespace
458+ (`https://w3id.org/lokf/`) always resolves to the built-in schema; registered
459+ ` base_iri` s must be strictly longer and may not nest inside one another, so
460+ routing is unambiguous.
461+ 3. **Ownership validation.** At registration a member's sampled concept IRIs must
462+ actually start with its declared `base_iri`, so a bundle cannot claim a
463+ namespace it does not own.
464+
465+ An IRI owned by no entry is returned as a tolerated **dangling link**, not an
466+ error — preserving OKF's permissive stance on broken cross-references.
467+
468+ # ## 11.4 Traversal and access *(informative — delivered in phases)*
469+
470+ The intended runtime model, layered on the primitives above :
471+
472+ - **Federated graph.** A registry loads each member's `graph.nt` into a
473+ **named graph whose IRI is its `base_iri`**. Union queries make a cross-bundle
474+ edge resolve transparently once both members are loaded, while `GRAPH ?g`
475+ recovers *which* bundle asserted a triple for free (`?g` binds the `base_iri`).
476+ Members load **lazily** — only as a walk reaches into their namespace — so an
477+ agent never pays to materialize the whole federation to answer a local
478+ question.
479+ - **Document access.** A concept's source markdown is fetched by
480+ `source_base + concept_id + ".md"` through an offline-first chain : a local
481+ checkout, else the cached `concepts.jsonld`, else a live fetch — every path
482+ yielding the same `{frontmatter, body}` shape.
483+ - **Agent surface.** A `lokf registry` CLI and a `lokf-registry` MCP server
484+ expose `resolve_iri`, `neighbors`, `subgraph`, `federated_sparql`, and
485+ ` read_document` , each depth/breadth-bounded so a cross-bundle hop is a single
486+ token-frugal call rather than a chatty chain.
487+
488+ # ## 11.5 Governance
489+
490+ - **Membership is public-artifact or local-checkout only** in v0.1; credentials
491+ never enter the shared manifest. Federating a private or perimeter-bound bundle
492+ is a future extension, not a v0.1 capability.
493+ - **Metadata only, never row-level data.** A registry aggregates schema- and
494+ concept-level knowledge; it does not move records. A per-entry `sensitivity`
495+ gate lets `harvest` refuse un-cleared members, because metadata each cleared
496+ *individually* can be *jointly* re-identifying and even `void` counts can leak
497+ small cells — so aggregating sensitive-domain bundles requires explicit
498+ clearance from the registry's owner.
499+ - **Freshness is best-effort.** The federated store reflects the last harvest,
500+ guarded by conditional requests and a surfaced per-entry `status`; there is no
501+ live-HEAD guarantee, and an unreachable member degrades to its last-good state
502+ rather than failing a walk.
503+
504+ ---
505+
506+ # # 12. Versioning
392507
393508LOKF versions are `<major>.<minor>`, tracking OKF's scheme. A minor bump adds
394509backward-compatible fields, types, relation predicates, or mappings; a major bump
@@ -419,6 +534,7 @@ README.md How the pieces fit and how to regenerate them.
419534| `lokf` | `https://w3id.org/lokf/` |
420535| `schema` | `http://schema.org/` |
421536| `dcat` | `http://www.w3.org/ns/dcat#` |
537+ | `void` | `http://rdfs.org/ns/void#` |
422538| `dcterms` | `http://purl.org/dc/terms/` |
423539| `prov` | `http://www.w3.org/ns/prov#` |
424540| `skos` | `http://www.w3.org/2004/02/skos/core#` |
0 commit comments