Architecture
This page says what this repository holds, how its domain stays current, and how the workspace is built from the members. It is for anyone deciding whether to join a bundle to the Knowledge Trust Ladder (KTL) federation, and for anyone who keeps its domain.
Where the design comes from
Section titled “Where the design comes from”KTL sets out the design on two pages. The curated context layer says what a context layer is, why one bundle is already one repository’s layer, and why KTL federates bundles through domains. A federation of bundles says how a domain names bundles and other domains, how the workspace is built and kept current, and what an adopter decides. Section 11 of the LOKF specification, “Federation: registries of bundles”, supplies the registry and the routing.
This repository is the first domain host. Its members are KTL’s bundle, the bundles of the KTL Curator and KTL Registrar plugins for Obsidian, the bundle of KTL’s reference host ai-linkmo, the bundle of lmodel’s gist, and the bundle of the linkml-sidecar skills.
The figure reads from the bottom up. Each member releases its bundle from its own repository. This domain names one release of each, and its builds turn those releases into the workspace, the domain’s context layer, which readers query.
As a flow diagram, drawn with Archify under the MIT License: light, dark.
The domain
Section titled “The domain”A domain is a named set of bundles with one accountable owner, kept as a thin LOKF bundle in a repository of its own. The bundle under .lokf/knowledge/ is the domain of the KTL federation. Every domain bounds a workspace of its own. This domain’s builds start here, so it is the top domain of the workspace they build. Another domain may still name it as a member.
domains/ktl-federation.mdis the domain concept. Itsunowned_targetsisfail: a typed relation whose target lies in no member’s namespace stops the build. It sets nomax_depth, so the build follows member domains three tiers deep.members/holds one concept per member: aMemberBundle, or aMemberDomainwhen the member’s bundle is itself a domain. Each names the member’s namespace, one release, the release zip as its distribution with that zip’s SHA-256 digest, and the schema released beside the zip when the member publishes one. A member names the newest release that carries a bundle zip, not the newest release.policies/federation.mdis the federation policy: what admits a bundle, what a member adds, which release a member names, and what the build does with a target no member owns or a bundle two domains name. A bundle is admitted when it is released with the KTL sidecar’s release workflow, validates on LOKF core or on a schema it publishes, has a stable namespace under an authority its publisher controls, and has one accountable owner.
A person’s confirmation of a member attests two things only: that the member belongs, and that the domain’s cross-bundle claims hold. For a MemberDomain, it attests that the domain belongs and who answers for it, not what that domain’s members say. It is the curator’s ordinary confirm, recorded as a verified event in that person’s name and backed by a signed commit, which the registrar’s provenance job checks.
The domain’s base_iri is https://w3id.org/ktl/federation/knowledge/. It sits beside KTL’s own namespace, never above it, since the registry refuses a base_iri nested inside another and routes an IRI to its owner by the longest matching namespace. https://w3id.org/ktl/federation/ itself is the site, with the workspace under it.
.lokf/domain.yaml is the domain schema. It declares five classes over LOKF core: Domain, which carries unowned_targets, max_depth, sensitivity and private_addresses; MemberBundle; MemberDomain; ReleaseDistribution, which requires the checksum of the file it names; and ReleaseChecksum, LOKF core’s checksum with sha256 as its only algorithm. So lokf validate checks a member’s namespace, release and checksum. The schema’s namespace is KTL’s, so every domain host shares one meaning for those classes. A host that needs more extends it with a schema that imports this one. The curator’s domain bundle page describes each class.
What is here
Section titled “What is here”| Path | Holds |
|---|---|
.lokf/knowledge/ |
The domain bundle, and the index.md a reader opens first. |
.lokf/domain.yaml |
The domain schema, named by the schema := line of .lokf/justfile. |
.lokf/scripts/, .github/workflows/ |
The KTL sidecar’s registrar gate, librarian wrapper, and its release, federation and sync workflows, as copies of its templates. knowledge-sync.yaml moves them to KTL’s newest release. |
.lokf/curators/ |
The public keys the provenance check verifies a curator’s signed commit against. |
website/ |
The site, from the ktl-website templates: a page per concept with its trust label, the bundle’s health, and the four lokf export files under /knowledge/. |
knowledge_bundle |
A link to .lokf/knowledge/, the doorway for a file manager or Obsidian. |
How it stays current
Section titled “How it stays current”A member’s new release changes nothing here until a person confirms a member concept that names it. Until then the build keeps the release a person last confirmed, leaves out a new member, and lists both as waiting in the workspace index. The librarian for this bundle is a script, not a model: knowledge-federate.py members, run by the sidecar’s librarian wrapper in place of an agent. It lists each member’s releases, picks the newest that carries a bundle zip, verifies its checksum and attestation, and proposes the change as a pull request through the sidecar’s pen. Until a person confirms that change, the member reads as edited since a person last confirmed it, so the health line shows which members have moved. members --add <owner>/<repo> drafts a new member the same way, as a MemberDomain when its release is a domain. Confirming a member change gives the steps for both.
The scheduled librarian always reads a bundle whose sources are release URLs as quiet, with nothing to do. So the federation workflow dispatches the librarian once a week instead.
A merged member change is a release of this domain: the release rules treat chore(lokf) as a minor version. Each release carries the bundle as a zip with its checksum, and rebuilds the workspace.
The workspace
Section titled “The workspace”The workspace is the graph built from this domain’s members, one named graph per bundle. A job that runs no agent builds it on each release of this domain: the release job dispatches knowledge-federation.yaml, which runs .lokf/scripts/knowledge-federate.py build from the tag. In order, as How the workspace stays current describes, the build:
- resolves the members into a lock, one entry per release with its URL, checksum, tier and the domains that named it, following each member that is a domain;
- verifies each zip against its checksum and its build attestation, refusing a zip with a symbolic link or a path outside its root;
- checks that each bundle’s
index.mdnames the namespace its member concept declares, then registers and exports it with thelokftoolkit; - loads each bundle’s graph into a named graph whose IRI is its namespace, with one more graph holding the lock as DCAT and VoID;
- gates every typed relation whose target lies outside the asserting bundle against the registry, as the domain’s
unowned_targetssays; - swaps the result in whole, so a failed build leaves the previous workspace in place.
The release carries the workspace as workspace-<tag>.zip. The site serves it under /workspace/, starting at index.md, which lists each member’s version, namespace, the domain that named it, licence and health line at the release. Then come the lock, the registry, graph.nq, each bundle’s exports, the gate report and the stored queries. labels.rq computes every trust label at the moment it runs, moved.rq lists the members edited since the domain’s last confirmation, and unowned.rq is the gate. No label is stored.
Reading the workspace
Section titled “Reading the workspace”Build and read it locally:
cd .lokf && just lokf-install && cd ..uv run --project .lokf .lokf/scripts/knowledge-federate.py build --tag v0.1.0 --release-base https://github.com/noelmcloughlin/ktl-federation/releases/taguv run --project .lokf .lokf/scripts/knowledge-federate.py query snapshot/queries/labels.rquv run --project .lokf .lokf/scripts/knowledge-federate.py serveuv run --project .lokf .lokf/scripts/knowledge-federate.py mcpserve is the toolkit’s SPARQL endpoint and graph page over the union of the graphs. mcp is the lokf-registry Model Context Protocol (MCP) server that section 11.4 of the LOKF specification describes, with the workspace index as its instructions, so an agent reads the index first. Register it with your agent as a stdio server: that command, run from this repository. Each one takes --snapshot <folder>, so it reads an unpacked workspace-<tag>.zip too. For a team, the Oxigraph server image serves graph.nq read-only from a container, as the curator’s domain bundle page shows.
What remains a design
Section titled “What remains a design”This host serves the static workspace and hosts no endpoint. A team that wants one runs the container. One part of section 11 remains a design: fetching a member from a private repository, which section 11.5 of the LOKF specification leaves to a later version. Per-query permissions stay with the store’s access control. What exists, and what remains a design lists each part and where it lives.
Decisions this domain has made
Section titled “Decisions this domain has made”KTL’s federation page lists the decisions an adopter must make. This domain’s answers:
- Pin. Each member concept names one release, and a person confirms each move. A domain that names this one adds a pin of its own, so a member’s release reaches that domain’s workspace only after a confirmation and a release here, then a confirmation and a release there.
- Who arbitrates. This domain’s owner, by a confirmed concept in this bundle, never by a rewrite of a member. There is no cross-bundle claim yet.
- What a member adds. Members are admitted on the policy’s four tests. The stricter value test asks that readers query across a member or that other members link into it. No member links into another yet, so this federation has proved its mechanism, not yet its worth.
- Permissions. Every member is a public repository. A bundle that must not be seen whole does not join.