# The Machine-Readable Surface

Five artifacts make the context layer legible to tooling. Everything else in the context layer is prose for humans that agents happen to read; these five are the contract tools build against.

| Artifact | Default location | Schema |
|---|---|---|
| Manifest | `leji.json` (repository root, fixed) | [`context-manifest.schema.json`](../schemas/context-manifest.schema.json) |
| Context index | `<root>/context-index.json` | [`context-index.schema.json`](../schemas/context-index.schema.json) |
| Context changelog | `<root>/context-changelog.json` | [`context-changelog.schema.json`](../schemas/context-changelog.schema.json) |
| Agent profiles | `<root>/agents/*.md` (frontmatter) | [`agent-profile.schema.json`](../schemas/agent-profile.schema.json) |
| Decision records | `<root>/decisions/*.md` (frontmatter) | [`decision-record.schema.json`](../schemas/decision-record.schema.json) |

All locations except the manifest are manifest-declared; the table shows defaults.

## Requirements

1. **Manifest.** `leji.json` **MUST** exist at the repository root and validate against its schema. It is the only fixed filename in Leji: the file tooling reliably looks for.
2. **Index.** A context layer claiming `indexed` conformance or above **MUST** carry a context index that is **generated, never hand-maintained**: tooling resolves the category index files (`categories.<id>.indexes`, per [content-categories.md](content-categories.md)) to the documents they list and writes one entry per governed document. Each entry carries a stable `id`, a `path`, a `title`, and a `category` identifier. A generator **SHOULD** also emit the document's `kind` (`intent` or `record`); it is optional in the schema so an index written before kinds existed stays valid, and a consumer treats an absent value as `intent`. A record's entry additionally carries its `date` when the document declares a valid frontmatter `date`; the generator sources dates from frontmatter only, never from prose or filename conventions. A stale index (one that no longer matches what the index files resolve to) **MUST** be treated as a validation failure. A host that declares `federation.mounts` also carries, in the same index, a top-level `mounts` array: one routing record per mount (`name`, `source`, `pin`, `trackingRef` when declared, `owner`, `role` when declared, and the routing metadata `categories` / `topics` / `requiredWhen`). These are routing records only; tooling **MUST NOT** copy a sibling's entries or prose into the host index, and a mount record carries nothing the host's audience may not see (per [distribution.md](distribution.md), Restricted mounts).
3. **Changelog.** A context layer claiming `indexed` conformance or above **MUST** carry a machine-readable changelog of context layer changes. Entries carry a stable `id`, a UTC `date`, a `type`, a one-line `summary`, and the affected `paths`. Canonical order is **derived, not positional**: tooling **MUST** order entries by `(date, id)` ascending, and array position carries no meaning. Because `id` is unique within the changelog (Identifiers), `(date, id)` is a total order even when two changes share a `date`. Surviving entries are immutable: tooling **MUST** treat modification of a published entry as a validation failure wherever it can establish the prior state, and reordering the array is not a modification. Establishing that state needs a distinct baseline to compare against; where the reference tooling has only the current revision, as in an ordinary continuous-integration checkout, the modification is not visible to it and the review of the change set is what catches it (see [conformance.md](conformance.md)). The changelog is a **recency surface, not an archive**: a long-lived context layer **SHOULD** compact it rather than let it grow without bound, and **MAY** compact it at any time by removing entries from the oldest end of that order, provided the same change set appends an entry of type `compaction` whose `compacted` field records the count and the first and last removed ids. Removal of anything but the oldest entries, removal without a compaction entry, and compaction to an empty file are validation failures. Append-only discipline is **set-keyed by `id`** and checked against the prior committed state, so it needs git at authoring time; the file itself stays git-free for consumers, and git history holds the full record. A change set that touches **governed documents** (those the category index files resolve to) **MUST** append an entry whose `paths` cover the governed paths it changed, so every changed governed document falls under some appended entry: append-only discipline keeps published entries immutable, and this coverage rule keeps the record complete. A human-readable changelog **MAY** exist alongside; the JSON record is the one tooling reads.
4. **Frontmatter artifacts.** Agent profiles and decision records are markdown documents whose YAML frontmatter validates against their schemas. The prose body stays free-form; the frontmatter is the machine contract. Pure-JSON profiles or decisions **MUST NOT** be required: people read these documents.
5. **Identifiers.** All `id` values **MUST** be stable once published: renames and moves update `path`, never `id`. Identifiers are lowercase, hyphen-separated, unique within their artifact type. A generated index entry's `id` is **derived** in priority order: the document's frontmatter `id` if it declares one; else the `id` the stored index already carries for that same path, or, for a pure content-preserving move, for that same content; else a slug of the filename, de-collided against its parent directory. The first that exists wins, so a published `id` survives a rename or a move and only a brand-new document mints a fresh one. A document that might be moved **and** edited in one change set **SHOULD** declare a frontmatter `id`: only frontmatter pins the id through a simultaneous path and content change (the carry-by-path and carry-by-hash fallbacks both miss it), and tooling warns (`id-vanished`) when a stored id disappears so the dangling references it leaves are caught.
6. **Timestamps.** Changelog `date` values are ISO 8601 in **UTC**: either a calendar date `YYYY-MM-DD` (ordered as that day's start, `T00:00:00Z`) or a whole-second timestamp ending in `Z` (for example `2026-06-13T15:04:05Z`). Zoneless times, non-UTC offsets and fractional seconds are **not** permitted: fractional seconds would break the guarantee that a lexical sort of `date` is a chronological sort, since `…05.1Z` sorts before `…05Z` while being later. Every date field in every artifact is calendar-ranged, so a month of `13` or a day of `99` is invalid. Other artifacts' dates follow ISO 8601 and **MAY** be date-only. **Paths** are POSIX-style, relative to the repository root, no leading `./`.
7. Every JSON artifact except the manifest **MUST** declare the schema line it was written against (`schemaVersion`), per [versioning.md](versioning.md); the manifest declares its target spec line with the self-naming `leji` key.
8. **Derived surfaces inherit access constraints.** The index, the changelog, the generated viewer, and any compiled or exported view built from context layer content are *derived surfaces*, as is the output an agent produces from that content. A derived surface carries the access constraints of the most restricted content it draws from. A derived surface **MUST NOT** be written or copied to a location with a broader audience than that content without an explicit, reviewed redaction step that produces a separate surface for that audience, and an agent **MUST NOT** quote or summarize restricted context into a broader-audience or less-restricted surface (a pull request, ticket, chat, commit message, or public context layer). The index of a restricted context layer can be as sensitive as its prose: titles, paths, and summaries all describe it. This is a constraint on the people and agents who operate the tooling, not a check the tooling performs: Leji defines no audience model a tool can read to compute "broader audience" (access is the version control system's, per [governance.md](governance.md), Access boundary), so the reference SDK does not enforce it and the most any tool does is warn (the viewer export warns to host it privately).

## Task routing

The index, the category assignments, and the decision records exist so an agent can load the slice of context a task needs instead of the whole tree. This section defines, normatively, how a task's scope selects that slice. It is the one routing algorithm the rest of the specification refers to: the boot profile's Loading section ([boot-profile.md](boot-profile.md)) points an agent here in task language, decision-record scope ([decisions.md](decisions.md)) is matched by it, and federated reading ([distribution.md](distribution.md)) reuses it to decide which siblings a task touches. Routing reads context; it is not a task envelope or an execution protocol, which stay outside 1.0 (see [README.md](README.md), Extension boundary).

1. **Input.** A task's scope is the set of repository-root-relative POSIX paths the task reads or changes (normalized per Requirement 6: POSIX-style, root-relative, no leading `./`), together with any categories the task explicitly names and any topics the task explicitly names. Topics are explicit inputs: the algorithm never derives them from paths, categories, prose, or content. How an agent or tool derives the scope from the task is out of normative scope; the matching below is not.
2. **Path match (lexical, bidirectional).** A declared path and a task path match when, after normalization (POSIX-style, root-relative, no leading `./`, any trailing `/` removed), the two strings are equal, or one is a path-prefix ancestor of the other: the shorter equals the longer truncated at a `/` boundary. Matching is **purely lexical**: it never consults the filesystem, and it draws no distinction between a path that names a file and one that names a directory, because after normalization the two are indistinguishable. This is containment in either direction (the `underPath` relation the reference implementations share), so a broadly-scoped task and a narrowly-declared selector find each other whichever side is broader.
3. **Category match (narrow), and the two category sets.** A task's categories divide into **expanded** and **signalled**. A category the task **explicitly names** enters both sets. A task path that is **itself a governed document** (matching its generated index entry by *exact equality*, never by containment) contributes that entry's category to the **signalled** set only. **Expanded** categories load their intent documents and record candidates; **signalled** categories are a matching signal for decisions and federation mounts and load nothing on their own. A category selector **MUST NOT** infer a category for an arbitrary repository file, and a task path that is not itself a governed document, including any ancestor directory of one, contributes no category at all. Path scope reaches files; category expansion does not follow it.
4. **Topic match (exact, mount-only).** A topic is a non-empty string of Unicode scalar values, compared by its UTF-8 encoding; a lone surrogate is not a valid topic. Both sides are held to that rule: a task topic or a mount `topics` entry that is not a non-empty string of Unicode scalar values is an input error, and an implementation **MUST** reject it rather than return it as a silent non-match. A task topic matches a declared topic when the two decoded strings are **exactly** equal. Implementations **MUST NOT** case-convert, Unicode-normalize, locale-compare, trim, tokenize, substring-match, or fuzzy-match either side, so canonically equivalent spellings that differ in bytes do not match; this equality rule is separate from the bytewise **ordering** of results below. Duplicate task topics form one signal, so naming a topic twice matches exactly as naming it once does. A federation mount matches when any task topic equals any topic the mount declares. A topic match selects the **mount only**: it **MUST NOT** enter the expanded or signalled category sets, load any document or record, route any decision, evaluate `requiredWhen`, or make a mount required.
5. **Status filter.** Only a decision record whose `status` binds is routed as current guidance. `accepted` and `deprecated` bind; a `deprecated` record binds with a stale posture, and an agent **MUST** treat it as guidance on its way out rather than settled current practice. A `superseded` record **MUST NOT** bind except as history and **MUST** carry `supersededBy`; `proposed` and `rejected` records **MUST NOT** bind. A record that binds is *live*.
6. **Unscoped decisions.** A live decision record that declares neither `affectedPaths` nor `affectedCategories` is org-wide: it is routed for every task, whatever the task's scope. Scoped live decisions are routed only when the task matches them by path (2) or category (3).
7. **Empty path scope, and empty scope.** When the task's **path set** is empty, path matching contributes nothing and an agent **MUST** state that path-scoped routing was not evaluated; explicitly named categories are still honored and still expand, and explicitly named topics are still matched. Named categories and named topics both count toward a non-empty scope. The task's **whole scope** is empty only when it names no paths, no categories, and no topics; then an agent routes only the unconditional boot-profile and agent-profile context plus the org-wide unscoped live decisions. An agent **MUST NOT** present an unrouted load as though it were scoped.
8. **Records route as candidates.** An **expanded** category routes its **intent** documents as required context; that category's **records** are returned separately, each with its kind and date, as candidates the reader loads by judgment. A record becomes **required** only when the task's paths select it directly under item 2, an agent or boot profile names it, or a person asks for it; being a category match, or carrying the newest date, never makes a record required. Where a record is both a category candidate and directly path-selected, the direct selection wins and it is required. Routing **MUST NOT** certify any record as "the latest" or "the current one": 1.0 defines no record-series identity or ordering guarantee, so recency judgments belong to the reader, made against the dates the index surfaces. Decision records keep their own routing (items 5 and 6) and never route as generic records. Naming a decision file as a task path does not route that decision; its declared scope does.
9. **Citation.** An agent that loads routed decision records **MUST** cite which matched records it loaded, so a reader can see which guidance the agent applied and infer what it did not.

**The routed slice** is what an agent loads for a task. It is the union of: the boot profile's unconditional load set and the active agent profile's `requiredRead`, which the agent holds as its **baseline** independent of any scope; every governed intent document in an **expanded** category; every governed entry the task's paths select under item 2; every record directly path-selected under item 8; and every live decision the task matches by path or by a **signalled** or expanded category, plus the org-wide unscoped live decisions.

**Signalled** categories contribute matching only, to decisions and to federation mounts, and never expand a corpus.

**The slice is not the envelope.** A tool that computes routing returns the slice alongside material the agent must **not** load unprompted: record candidates, and the routed-candidate metadata around them. Loading the envelope wholesale defeats the point of routing.

**Result ordering is normative** where a tool emits one, so independent implementations agree byte for byte: categories in the canonical category order of this specification; documents, records and decisions by ascending path; mounts by ascending name. String comparison is **bytewise over UTF-8**, not locale- or codepoint-collation dependent.

Tooling **MAY** offer a helper that computes this slice from a path set; the reference implementations expose one (`route`). Such a helper computes the **scope-dependent portion** and is not required to emit the baseline, which the caller already holds; the agent's obligation to load that baseline is unchanged. Routing conforms whenever a raw reader follows this algorithm.

## Notes (non-normative)

The reference tooling checks the changelog's schema and append-only discipline today (`leji validate` runs both); verifying changelog *coverage* against a base revision, that every changed governed path appears in an appended entry, is a reported check on the roadmap, not yet a blocking gate. Until it ships, coverage rides the process-attested review and CI discipline (see [conformance.md](conformance.md)).

The index is the navigation source for the governed context: `leji viewer` renders that governed spine from the index, and below it the repository's own directory tree as a browsable reference zone, so one view surfaces both the governed context and the team's existing navigation. Any docs tool can project the index the same way. Presentation is non-normative. The surface is deliberately small. Five shapes are enough for tooling to validate a context layer, diff it, score its freshness, and route an agent to the right slice, and few enough that a team can hold the whole surface in mind. Anything beyond these five is post-1.0 territory, gated on lived practice.
