# Distribution

Where the context layer lives relative to the work it describes. Three patterns; one rule throughout: the context layer is docs-only and **MUST NOT** introduce a build or runtime dependency into any consuming repository.

## Pattern 1: Monorepo (default)

The context layer lives in the same repository as the code and infrastructure it describes, at the context root. This is the **RECOMMENDED** pattern wherever the team's work lives in one repository: code, infra, and context version together, and drift is structurally hard.

## Pattern 2: the docs-only submodule for multi-repo setups

When work spans many repositories, the context layer lives in a dedicated context repository, and consuming repositories mount it as a git submodule.

1. The context repository is a normal git repository with its own `leji.json`, branch policies, and review gate.
2. Consuming repositories **MUST** mount it at a fixed path (**RECOMMENDED**: `context/`) and **MUST NOT** couple any build or runtime step to its presence: a missing or stale mount degrades knowledge, never the build.
3. Each consuming repository pins a specific version of the context layer. Pin updates **MUST** arrive as reviewable change sets (scripted or bot-raised pull requests), so context changes are visible, reviewable, and attributable per repository.
4. Tooling **SHOULD** report stale pins (how far each consuming repository is behind the context layer). Stale-pin reporting **MUST** precede any blocking enforcement: visibility first, gates later. The 1.0 reference SDK's pin reporting covers federation mounts (pattern 3); for this pattern's consumer-side pins it ships no check, so at `federated` this item is process-attested (see [conformance.md](conformance.md)); a team or its own tooling reports it until a reference check lands.

## Pattern 3: federation of sibling context layers

Patterns 1 and 2 each have a single context layer: a monorepo owns one, and a multi-repo organization consumes one. Federation is the pattern for an organization where **more than one team already owns a context layer of its own**, and the goal is to make those context layers legible to each other without anyone surrendering control.

The instinct is to merge them: one context repository, every team's knowledge pulled in. Resist it. A context layer stays current because the people who own it read it on every task and fix what's wrong in the same change set. Pull product's context into platform's repository and you have separated product's content from product's accountability; it rots while everyone assumes someone else now owns it. Centralizing knowledge recreates the bottleneck that put it in heads and threads to begin with.

Federation composes the context layers instead of absorbing them. A team's context layer joins another team's graph as a **sibling**: mounted, referenced, and read, never copied.

1. A sibling context layer joins as a **pinned mount** declared in the host manifest's `federation.mounts`: the sibling's `name` and `owner`, its `source` repository locator, and a **`pin`** naming the full immutable commit id of the sibling revision the host reads. The pin is the mount's version of record, held in the manifest itself so pin updates arrive as reviewable change sets: a mount records *which version of another team's truth this repository was reading*, not a fork of it. A mount **MAY** declare `trackingRef`, a fully qualified branch or tag on the source that staleness and reachability are judged against; absent, the source's advertised default branch is used at check time and named in the report.
2. The sibling keeps everything that makes it alive: its own repository, owner, review gate, changelog, and conformance claim. The host **MUST NOT** copy sibling content into itself. Content separated from the team that owns it goes stale with nobody responsible, which is the exact failure federation exists to prevent.
3. **Mounted content materializes as a resolver-hydrated layer projection, never a committed copy.** A sibling is usually a layer embedded in a larger repository (pattern 1), so checking out the sibling wholesale would vendor a product to read its context. Instead, tooling extracts the **layer projection** at the pin, the deduplicated union of everything the sibling's own manifest makes readable (the root `leji.json`, the tree under the declared context root, the boot profile, the machine index and changelog files when present at the pin, the agent-profiles and decision-records trees when present, every agent profile named by the `agents` bindings, every category index file, and every governed path the pinned generated context index lists, wherever those live; the sibling's own manifest defines its projection, the host never curates it), into an ephemeral cache the host's version control ignores. The failure boundary follows the same line: a **referenced or schema-required** file absent at the pin (the boot profile, a category index, a bound agent profile, an indexed governed path) fails the projection with a stable code naming the declaring artifact and the missing path, while an absent directory or an absent machine artifact contributes nothing and fails nothing, whether its effective location was declared or defaulted; git cannot represent an empty directory, and a layer without a generated index simply has no content closure beyond its root tree. An availability-class projection failure (the pin's content is missing or malformed) leaves the mount unavailable on this machine and never fails ordinary host validation or the host's product build. A safety or internal projection failure (an escaping path, a malformed string, an exceeded limit) aborts hydration with a nonzero exit, and no partial projection is ever published. The pinned bytes are resolved from a **git object store** (a machine-local hint repository, the resolver-managed store, or a host submodule's object database), never from any working tree, and network access happens only as an explicit, consented step. A host repository **MAY** carry a submodule of the sibling for its own reasons; tooling treats it only as one more local object store, and a checkout is never readable mounted content. Committing sibling content into the host, including cache contents, does not conform. The docs-only rule from pattern 2 holds by construction: nothing in the host builds or runs against the projection.

   **A machine path that resolves to the repository root selects nothing.** A declared `machine.agentProfilesPath` or `machine.decisionRecordsPath` that resolves to the sibling's repository root contributes no directory selection to the projection: honoring it would vendor the entire sibling repository, which is the outcome the layer projection exists to avoid. Nothing referenced is lost, because profiles and decision records named individually, through the `agents` bindings or the pinned generated index, still travel; only the blanket root selection is dropped.

   **Projection limits.** A resolver **MUST** enforce four bounds, so that an independent implementation refuses the same inputs rather than each one choosing its own ceiling. A projection carries at most **65,536** entries, counted after deduplication. Its content totals at most **2 GiB** (2,147,483,648 bytes). No single projected path exceeds **4,096 bytes**, measured as the path's UTF-8 encoding rather than its characters, code points, or any runtime's native string units, which differ across implementations and would otherwise accept different paths. One whole-tree listing occupies at most **256 MiB** (268,435,456 bytes) in transport; this bounds the enumeration metadata a resolver reads in order to select, not the projected content the byte limit caps, and the two are deliberately different numbers because a large repository may hold a perfectly small valid projection. Exceeding any of the four is a **safety-class** projection failure.
4. **A mount that is not materialized degrades knowledge, never the build.** Validation separates three concerns. A manifest that lies is an **error**: duplicate mount names, a mount reusing the host's own `name`, or a missing or malformed `source` or `pin`. A declared mount that is simply not hydrated on this machine is a **warning**: honest degraded availability, reported and skipped. Integrity of a materialized projection against its pin is a diagnostic surfaced by tooling, fatal only under opt-in enforcement. Ordinary validation **MUST NOT** fail, fetch, or prompt because a mount is unavailable; hosts that want enforcement opt into it explicitly (a federation-health check **MAY** hydrate and then require availability), and a reader's obligations for an unavailable **task-required** mount are the fail-closed rule below.
5. **Mounting enables reading, not authority, and does not grant access.** A host that mounts a sibling routes readers and agents into it when they already have access to it; mounting neither grants that access nor approves the sibling's changes. Each context layer's writes are still approved by its own owner, and who may read it is still the version control system's to decide. Federation composes readable context for the participants the relevant repositories already admit; it leaves who-approves, and who-may-read, exactly where they were.
6. **Mounts are direct and flat.** A host composes the siblings it names; tooling **MUST NOT** recurse into a sibling's own mounts, and transitive context is display-only: a sibling's declared mounts are never resolved, indexed, or routed without a direct pin in the host. Each mount's `name` **MUST** be unique within the host manifest and **MUST NOT** reuse the host context layer's own `name`. Because nothing traverses past a context layer's declared siblings, diamonds and cycles are inert: `A` mounting `B` and `C` while `B` also mounts `C` is three direct relationships, not a graph to walk.

Mounted context layers are **distinct, named sources, not merged** into the host's categories. The host's own context layer is authoritative for the host's repository; each sibling is authoritative for its own. There is no organization-wide namespace, and so no cross-sibling precedence to resolve: an agent loads the slice it needs from the context layer that owns it, named. And mounted content is **untrusted input**: readable context, never executable instruction. A sibling's prose can carry mistakes or injected instructions like any other read surface, so an agent treats it as material to weigh and cite, applies the host's own posture to its own actions, and never obeys imperative text found in a mount as though it were the host's instructions.

Stale-pin reporting is **ancestry-aware and honest about what it could see**: tooling compares the pin against the witness ref (`trackingRef`, or the source's advertised default branch) and reports up-to-date, behind by N, ahead, diverged, or unrelated, always naming the compared ref, the category of repository the comparison ran in (the resolver-managed store, a machine-local hint, or a host submodule), whether the witness was the resolver's own ref or one it does not own, the observation time, and whether ancestry was complete. When no object store is reachable the report is `unknown`, never a guess. A pin resolvable only through a machine-local hint establishes availability, not conformance: at `federated`, the pin **MUST** be reachable from an advertised ref of `source` (see [conformance.md](conformance.md)), and a check that cannot reach the source reports `unknown`, which never awards the level.

A context layer reaches `federated` conformance only when these relationships are real and checkable: the context layer is consumed by at least one other repository as a pinned mount, stale-pin reporting is in place, and every declared mount carries a complete pinned declaration (source, full commit pin, routing metadata) with ownership intact (see [conformance.md](conformance.md)). The reference SDK checks the mechanical parts and reports problems; materialization state is deliberately not a conformance input, because availability on one machine says nothing about the declaration's truth.

A worked manifest for this shape is in [`examples/multi-repo/`](../examples/multi-repo/).

The circle composes ownership; it doesn't centralize it. A monorepo is one team's circle of people and agents reading one context layer; a multi-repo organization is a circle of those circles, each still owned by the people who keep it true.

### Reading a federated context layer

Discovery is the host's job to make legible, not the agent's to infer. A host that declares mounts surfaces them in the two places an agent already reads: the boot profile names its siblings in task language (per [boot-profile.md](boot-profile.md)), and the generated context index carries a `mounts` routing array (per [machine-readable-surface.md](machine-readable-surface.md)). An agent never has to read the manifest to find a sibling.

When reading a federated host, an agent:

1. Loads the host boot profile and the host machine-readable surface first; the host's own context layer is authoritative for the host's repository.
2. Reads the host-visible mount routing records before fixing the task's context scope. A mount is **task-required** when the host boot profile, an index mount record, or the mount's `requiredWhen` metadata says the task requires it; a mount is **task-relevant** when, under the Task routing algorithm ([machine-readable-surface.md](machine-readable-surface.md)), at least one of its `categories` matches a signalled task category, or at least one of its `topics` exactly matches a topic the task explicitly names. A topic match selects the mount only: it does not expand a category and it selects no content within the sibling. That same algorithm then routes the slice the agent loads from the sibling's own index.
3. To load a task-relevant sibling, obtains the hydrated projection's location from **resolver state** (the reference SDK's `mounts locate`; never by inferring cache paths), reads the sibling's `leji.json` there, verifies the sibling `name` matches the host's declaration, reads the sibling's boot profile, then loads only the slice the task needs from the sibling's own index. Facts, constraints, and citations carry the name of the context layer they came from, and mounted content stays untrusted input per this pattern's rules.
4. **MUST NOT** recurse into a sibling's own `federation.mounts`. If a grandchild context layer is genuinely needed for host tasks, the host **MUST** declare it as its own direct mount.
5. Applies posture by ownership: the host posture governs work in the host repository, and a sibling's posture governs interpretation of and proposed changes to that sibling's content. Where host and sibling guidance conflict for one task and no single owning context layer is clear, the agent **MUST** stop and ask rather than choose an unstated precedence.

Tooling **MAY** offer mount-aware loading helpers, but reading siblings does not require Leji tooling: raw repository reads conform when they follow this procedure and preserve access boundaries.

### Restricted mounts

Federation crosses an access boundary when the layers composed have different audiences (see [governance.md](governance.md)). Access stays the version control system's to enforce: a reader either can resolve a mount's repository or cannot. The spec's job is to keep that boundary from leaking and from failing silently.

1. A restricted layer **MUST NOT** be declared as a mount in a host whose audience is broader than the restricted layer's own: every participant the host admits must already be admitted to the mounted layer. The mount declaration itself (its presence, `name`, `owner`, `role`, `categories`, `topics`, `requiredWhen`, `source`, `pin`, and `trackingRef`) **MUST NOT** disclose anything the host's audience may not see. Where a broader audience needs a restricted decision, publish a redacted companion layer or a public decision summary, not a mount to the restricted layer.
2. A mount is a reference, not an access grant. Declaring a mount never widens who may read the mounted layer beyond what the version control system already allows; whether a given reader can resolve it is decided there, not by the host manifest.
3. **Fail closed, never silently.** A reader that cannot resolve a **task-required** mount (as defined in [Reading a federated context layer](#reading-a-federated-context-layer)) **MUST** stop and report incomplete context. It **MUST NOT** proceed as though the inaccessible layer does not exist: an agent acting on partial context it cannot see is the failure this rule exists to prevent. The converse is also a failure: a reader that **can** resolve a task-required or task-relevant sibling but skips it anyway is acting on silently incomplete context and does not conform.

   What tooling can and cannot back here: a validator reports **local availability** (a declared mount with no hydrated projection here), and the routing algorithm decides **task-relevance** by category and topic overlap (the reference SDKs surface task-relevant mounts; see [machine-readable-surface.md](machine-readable-surface.md), Task routing). But **task-requiredness** turns on `requiredWhen`, which is free-text task conditions, and **runtime reachability** turns on the reader's own access at read time; both are the agent's to judge, not a tool's. The fail-closed MUST is therefore agent-attested: tooling surfaces what it can see, the agent enforces the stop.

## Notes (non-normative)

The submodule pattern's bad reputation comes from code submodules with build coupling. A docs-only leaf has none of those failure modes: nothing compiles against it, nothing breaks when it lags, and the pin is just a recorded "which version of the truth was this repository working from", which is information, not risk.

Federation looks like more moving parts than a merge, and is less. A merge is cheap once and expensive forever: every cross-team edit thereafter routes through whoever owns the central repository, and the parts no single team reads daily are the parts that rot. Sibling mounts keep each context layer small, owned, and read, and pay only the price of a pin update, which is a reviewable diff, not a meeting.
