Core
The core schema holds the values every surface shares: the shape of a projection, the structure of source, the semantic facts providers resolve, and the changes the server applies.
Projection state
A change names its target with a
ProjectionId, or targets the workspace tree by
naming none. ProjectionState exposes the
conditions a caller checks before paging through a changeset:
{
"dirty": true,
"unaccepted": false
}dirty indicates that the projection differs from its workspace base. unaccepted indicates
that its changeset contains a confirmation requirement.
The server MUST refuse publish unless accept
names every such change; path and dependency conflicts remain visible only in the publication
result. The changes come from the
rift://changes resource.
Paths and files
ProjectPath is a normalized UTF-8 path relative
to the workspace root. It uses /, contains no . or .. component, and cannot name .rift -
projections and the server lock live under there, and a path that could address it would let a
change reach the server's own state. Normalized means Unicode NFC: the server normalizes what it
emits and accepts, then compares byte-for-byte. On a case-insensitive filesystem, the server
MUST refuse a create or rename whose destination case-folds onto a different existing path.
Every identifier that embeds a path - FileId, a node
id, the rift://fs form - carries a ProjectPath in
canonical percent-encoding. The generated schema patterns do not carry the exclusions above, so
the server re-validates each decoded value.
ProjectEntry is a visible directory, regular
file or symlink. Visible means below the workspace root, outside .rift, and not excluded by
the workspace's VCS ignore rules. Visibility governs reads; publication carries every changed
path, visible or not.
Each variant carries what a consumer can act on:
- a regular file - its size and executable bit
- a symlink - its target, as base64, because the server never follows it and cannot assume the bytes are text
- a directory - only its path, so an empty one is still listed once something creates it
A symlink is listed and never followed, in every traversal:
- reads
- materialization
- the execution copy
executeruns in - publication
- prune
A copy of a symlink reproduces the link. The server MUST reject a path that addresses
through a symlink component, because following it could leave the workspace.
Source locations
TextRange is a pair of half-open UTF-8 byte
offsets, and SourceSpan puts one against a file.
Rift uses byte offsets rather than line and column because every provider converts from whatever
its own toolchain counts in exactly once, at its own boundary; after that, two providers'
offsets are on one scale.
A NodeId is positional and witnessed:
rift://node/python/pkg/util.py@1204-1266#3f9a1c2eThe byte range names where the node stands; the fragment is the witness - the first eight
hex characters of the SHA-256 of the node's source bytes, minted when a listing returned the
identity. Resolution recomputes the witness before acting on the address, so the server refuses
an identity minted before the file changed with a failed source_unchanged precondition rather
than splicing into bytes that moved. The witness makes a node address safe to hold across calls: the
address carries its own proof of freshness without depending on connection-scoped state.
Source discovery
Source resolvers run before syntax or semantic analysis. They inspect workspace files and resolved
toolchain state, then publish a source catalog made of
SourceUnit records. A resolver discovers installed
dependencies; it does not download packages or change the environment.
SourceLocation names a unit's owner: project,
dependency, standard library, or external source.
SourceKind names how the source came to exist:
authored or generated. The two stay separate because generated code can belong to the project or
to a dependency.
A generated unit names its generator and can carry
SourceMapping records back to its inputs. A
synthetic declaration has no source unit.
{
"id": "rift://source/pypi/pydantic/main.py",
"location": {
"kind": "dependency",
"package": { "manager": "pypi", "name": "pydantic", "version": "2.8.2" }
},
"path": "pydantic/main.py",
"source_kind": "authored",
"languages": [{ "name": "python", "dialect": null }],
"digest": "6f2a9c31",
"generator": null,
"mappings": []
}An id is two parts: the resolver identity (pypi), then that resolver's own human-readable key
for the unit (pydantic/main.py). digest is the short form every content digest takes on the
wire - the first eight hex characters of a SHA-256, the same convention NodeId's witness uses;
the server computes and compares the full digest internally, so it never travels whole.
SourceResolverSummary reports each resolver's
identity, locations, catalog revision, and lifecycle state. Resolver revisions change with files,
manifests, lockfiles, toolchains, installed packages, or generated-source metadata. Fact-provider
revisions change after providers analyze a catalog revision.
Semantic facts
Providers return facts in families: symbols, the syntax nodes that write them, relationships
between symbols, resolved types, diagnostics, and history. A family no provider can produce
refuses with capability_unavailable. Signatures and documentation travel on the symbol itself,
covered under the types family.
A read result carries the answer, pagination, and warnings. A condition the caller must
weigh - an index that lags the tree - arrives as a typed entry in warnings; the
Reads section on the MCP page states the paging and warning
contract.
SymbolOrigin keeps source location separate from
source kind. A synthetic symbol carries neither a location nor a package; dependency and
standard-library symbols can carry facts even when their provider cannot return source bytes.
The declaration's own source-catalog unit, where its location differs from the project, rides on
the get_symbol hit itself, addressed by unit beside the
path a project declaration carries.
History facts
The history family is resolved from the workspace's version-control history rather than from
the current tree. A SymbolHistory is one
symbol's timeline - each SymbolVersion names
the RevisionId that touched it and what the
revision did: introduced it, changed its body, changed its signature, moved it, removed it.
The history provider correlates declarations across revisions using their parsed structure and
surrounding changes. A SymbolId can change when a
language includes a module path in its qualified name; the timeline still records the move.
The walk is bounded by the configured history depth, and the timeline rides a
get_symbol hit when the request asks for history.
Changes
An Edit is one filesystem effect: replace these bytes,
create this file, delete it, move it, copy it, flip its executable bit. Edits in one set share
one input state, cannot overlap, and apply atomically.
The change tools resolve down to a set of those, plus the preconditions the server checked, the
effects the change had, the diagnostics it produced, and
GuaranteeEvidence for every property a
hook established.
When resolution yields no edits, the server returns a
RefusalReason - unmet_precondition or
unsupported - and leaves the targeted tree unchanged. When it yields edits, the
change lands carrying its Advisory list - the
concerns providers and hooks attached to exactly this change. A checked advisory is a verdict
the emitter verified itself; an open warning carries the instruction that settles it.
In a projection, an edit with an unresolved concern - an open warning advisory, a hook that
failed or did not finish, a deletion - carries a
ConfirmationRequirement per problem,
and the caller settles them by naming each in accept at
publish. A change applied directly to the workspace
has no publication to gate, so its confirmations list stays empty and its advisories are the
caller's to act on.
A process can write directly into a projection directory without producing an Edit. The server
reconciles that filesystem delta into a change whose origin is filesystem; its paths are
authoritative when a binary file or symlink has no portable edit form. The imported change
carries an external confirmation, so the server withholds publication until the caller reviews
writes that bypassed a change tool.
Transport and infrastructure failures use
ErrorData, with a code, a phase, and a retry
directive saying whether the caller may resend the same request.
The full field definitions are in the generated reference.