Store records
The records a store keeps beside its objects, such as aliases, bundles, revisions, policies and releases, with their fields and IDs.
A store holds objects, which are identified by their content and accepted by kernel replay. It also holds records above object identity: names, selections, history, decisions and releases. No object cites a record, so a record never changes an object’s ID, and records are outside the identity rules that crates/lema-core/tests/identity.rs pins. None of them is a kernel check. The rules below are store rules, tested without a runtime; the code is in crates/lema-store/src/.
Overview
| Record | Schema | Identified by | Stored in | Written by |
|---|---|---|---|---|
| Alias | none | its name | aliases.json | alias, submit/import, publish, replace |
| Bundle | lema.bundle/v1 | content ID | bundles/<ab>/<id>.json | bundle.create |
| Binding map | lema.binding-map/v1 | content ID | records/binding-map/<ab>/<id>.json | binding-map.create, merge.create |
| Authoring bundle | lema.authoring/v1 | content ID | records/authoring/… | authoring.create, publish |
| Snapshot | lema.snapshot/v1 | content ID | records/snapshot/… | snapshot.create, merge.create |
| Revision | lema.revision/v1 | content ID | records/revision/… | revision.create |
| Policy | lema.policy/v1 | content ID | records/policy/… | policy.create |
| Guarantee record | lema.guarantee/v1 | content ID | records/guarantee/… | executable |
| Merge record | lema.merge/v1 | content ID | records/merge/… | merge.create |
| Review | lema.review/v1 | content ID | records/review/… | review.create |
| Project reference | none | its name | refs.json | ref.create, ref.publish, ref.adopt |
| Change report | lema.change-report/v1 | content ID | not stored | revision.report, snapshot.diff, ref.report |
| Release | lema.release/v1 | content ID | releases/<ab>/<id>.json | release.create |
| Actor | none | its actor ID | actors.json (lema.actors/v1) | lema actor add |
<ab> is the first two hex digits of the ID.
Content IDs
A record’s content ID is the SHA-256 of a hash domain of its own followed by the record’s canonical JSON:
id = sha256("LEMA-<KIND>-v1\0" ‖ canonical(record))The domains are LEMA-BUNDLE-v1, LEMA-BINDING-MAP-v1, LEMA-AUTHORING-v1, LEMA-SNAPSHOT-v1, LEMA-REVISION-v1, LEMA-POLICY-v1, LEMA-GUARANTEE-v1, LEMA-MERGE-v1, LEMA-REVIEW-v1, LEMA-CHANGE-REPORT-v1 and LEMA-RELEASE-v1, each followed by a zero byte. They differ from each other and from the objects’ LEMA-TYPED-CORE-v3, so a record can never have an object’s ID.
These rules hold for the stored records with a content ID: bundles, project records and releases. A change report shares the canonical form and the bounds.
- Fields are exact, and unknown fields are refused. Lists are sorted with no repeats, except the
parentsof a revision or a merge record, whose order is meaningful. Empty optional fields are absent, except a policy’srequire.axioms, where[]means no axiom and is kept. A record in any other form is refused, never repaired. - Every list, every text and the whole record have a maximum size.
- A record is written once. Its creation is a
history.jsonlline:"event": "record"withkindandidfor project records,"bundle"for bundles,"release"for releases. Every read re-checks the bytes, the ID and the rules. - A creation request’s lists are sorted before hashing, and a repeated item is refused, never merged. The same content in any order gives the same record.
- Every object a project record names must be accepted; an obligation’s statement, clients and dependencies need only be stored. Every record it names must already be stored, so the revision graph has no cycles.
- Every record but a policy carries
namespace, the namespace object’s ID. A policy carries one only when it hasprotectgates.
Two kinds of text recur. A path is relative: /-separated components of ASCII letters, digits and ._-+@, none empty, . or .., at most 256 bytes. An actor ID is 1 to 128 bytes of ASCII letters, digits and ._@-, starting with a letter or digit.
Aliases
aliases.json maps a name to {id, role?, generation}.
| Field | Meaning |
|---|---|
id | An accepted object. |
role | Optional: one role of that object, such as a structure’s constructor. |
generation | 1 when the alias is created, one more at each move. Re-asserting the current target moves nothing. |
A name is 1 to 512 bytes with no control characters. Aliases are never removed, so a generation is never reused, and an expectation with a generation detects a move from A to B and back to A. Every move names what it expects the alias to hold, or overwrite: true (server methods). Each request’s changes are one history line, {"event", "changes": [{name, target, generation, previous, overwrite?}], …}, written before aliases.json is replaced. Moving an alias safely is the move an alias guide.
Bundles
A bundle names a set of accepted objects once. elaborate’s receive binds its exports as Received.* names, which builds exactly the objects that the same bindings written by hand build.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.bundle/v1 |
namespace | yes | The namespace object’s ID. |
exports | yes | 1 to 4,096 {name, id, role?}, sorted by name. A name is a dotted Lean identifier of at most 256 bytes; the role is one its object has. |
helpers | no | Export names hidden from default listings. |
depends | no | 1 to 64 bundle IDs it re-exports from. |
doc | no | Text, at most 4,096 bytes. |
A bundle record is at most 4 MiB. bundle.create resolves from_aliases once: the record holds the IDs the aliases named then, and later alias moves do not change it. Using bundles is the name a set of objects with a bundle guide.
Binding maps
A binding map selects a project’s roots and exported names.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.binding-map/v1 |
namespace | yes | The namespace object’s ID. |
roots | no | Up to 16,384 accepted object IDs. |
exports | no | Up to 16,384 {name, id, role?}, sorted by name. |
At most 8 MiB. Renaming an export changes the binding map, not the objects. binding-map.create also takes from_aliases and alias_prefix, read once.
Authoring bundles
An authoring bundle keeps original source, source-to-object mappings and notes.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.authoring/v1 |
namespace | yes | The namespace object’s ID. |
sources | yes | 1 to 256 {path, text}, each text at most 4 MiB without NUL. |
mappings | no | Up to 16,384 {source, name, id, role?}, sorted by source and then name. |
notes | no | Up to 16,384 {id, text}, one per object, each text at most 16,384 bytes. |
validation | yes | {status: "unchecked"}, or {status: "elaborated", call, tooling_sha256, inputs_sha256}. |
At most 16 MiB. authoring.create always writes unchecked: the stored source is not claimed to reconstruct anything. Only publish with authoring writes elaborated, which records that this server elaborated exactly these inputs into the mapped objects in that call. inputs_sha256 is sha256("LEMA-ELABORATION-INPUTS-v1\0" ‖ canonical JSON) of the request’s source, roots, prefix, bindings, receive, seeds and seed_salt. It is a record of what happened, not a re-check.
Snapshots
A snapshot is a binding map plus separately referenced authoring material and assets.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.snapshot/v1 |
namespace | yes | The namespace object’s ID. |
bindings | yes | A binding map’s ID. |
authoring | no | Up to 64 authoring bundle IDs. |
assets | no | Up to 4,096 {path, kind, sha256, size}, kind being doc, config or asset. |
At most 4 MiB. The store holds only an asset’s digest and size, never its bytes. Editing documentation changes the snapshot and not the binding map, and two histories that reach one state share its snapshot ID.
Revisions
A revision goes from its first parent’s after-snapshot, or from the empty state, to after.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.revision/v1 |
namespace | yes | The namespace object’s ID. |
parents | no | 1 to 16 revision IDs, in order. Absent for an initial revision. |
base | for merges | The common base: present exactly when there are two or more parents. |
before | with parents | The first parent’s after, filled in by revision.create. Absent for an initial revision. |
after | yes | A snapshot ID. |
intent | yes | 1 to 4,096 bytes. |
evidence | no | Up to 1,024 {kind, id}: accepted (an accepted object) or guarantee (a guarantee record). |
obligations | no | Up to 256 obligations: all those open after the revision. |
discharged | with parents | Up to 256 {obligation, by} (an accepted theorem’s ID) or {obligation, waiver} (text). |
At most 1 MiB. An obligation is {claim, statement?, clients?, assumptions?, depends?}: a claim of 1 to 4,096 bytes, the statement object it names (if any), and up to 256 client IDs, assumption texts (each at most 1,024 bytes) and dependency IDs.
Obligations are inherited. Each obligation open in any parent is either listed again, exactly, or discharged. by must be an accepted theorem whose statement is the obligation’s statement, matched by identity; an obligation without a statement can only be waived. An obligation cannot be both open and discharged, and only one some parent has open can be discharged. Bringing a waiver into a project reference needs an actor in the active policy’s waive list.
Policies
A policy decides who may move a project reference and what a revision must meet.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.policy/v1 |
version | yes | A positive integer. |
previous | from version 2 | The policy it follows, of the version before; present exactly when version is 2 or more. |
publish | yes | Actor IDs that may publish a revision (0 to 256). |
adopt | yes | Actor IDs that may adopt the next policy (0 to 256). |
reset | no | Actor IDs that may move the reference back to an ancestor. |
waive | no | Actor IDs that may bring waived obligations into the reference. |
require | no | {axioms?, profiles?, no_obligations?}, below. |
doc | no | Text. |
namespace | with protect | The namespace object’s ID. |
protect | no | 1 to 16 gates, sorted by export. |
review | no | {reviewers, adopt?, distinct?, display?}, below. |
At most 256 KiB. An absent or empty reset or waive means nobody.
require field | Meaning |
|---|---|
axioms | Up to 64 axiom names that every kernel receipt in the revision’s closure, and in the closure of the objects it cites, must stay within. [] allows none. A name below the fixed floor is refused. This is on top of the store’s own axiom policy. |
profiles | 1 to 16 {profile, exports}: each named export must be covered by a cited guarantee record of that profile whose verdict is holds and whose checker is the current one. |
no_obligations | true: the revision has no open obligation, inherited ones included. |
A gate protects an implementation export by identity.
| Gate field | Required | Meaning |
|---|---|---|
claim | yes | implements, refines or replaces. |
export | yes | The implementation export it protects. |
state | yes | The export binding its start state. |
contract | yes | The contract declaration’s ID. |
start | yes | The start state’s ID. |
contract_export | refines only | The export binding the reference’s contract. |
parameters | no | replaces only: 1 to 64, the contract binders the claim quantifies over. |
representations | no | Allowed relation IDs, up to 64. Not for implements. |
clients | no | Accepted client theorem IDs, up to 64. Not with parameters. |
review | no | delegated or steward: the review a move needs. |
review lists reviewers (1 to 256 {actor, authority}, authority delegated or steward, sorted by actor), and optionally adopt (the authority an adoption needs), distinct: true (a reviewer’s approval does not count for its own move) and display: true (only approvals bound to the approval view they read count). A steward’s review meets a delegated requirement.
Project references
refs.json maps a reference name to {policy, revision, generation}.
| Field | Meaning |
|---|---|
policy | The reference’s active policy. |
revision | The revision the project’s line is at, or null at the explicit empty initial state. |
generation | Counts every change of either part, never reused. |
A name follows the alias rules. A reference moves only through ref.create, ref.publish and ref.adopt, through the same engine as aliases: one history line per request, with the actor and the decision. It never takes an overwrite, and every move names the revision, generation and policy version it expects. Setting one up is the set up a project reference guide.
Actors
actors.json (lema.actors/v1) maps an actor ID to {operator, token_sha256}. lema actor add writes it with the server stopped and shows the token once. An operator may create project references. A token authenticates the bearer of a token the operator issued, and nothing more: every client of a local store runs as the store’s OS user, who can also read and write the store’s files. Tokens are not rotated or revoked in this version.
Reviews
A review records one reviewer’s decision on one move of a project reference.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.review/v1 |
namespace | yes | The namespace object’s ID. |
reviewer | yes | An actor the active policy lists in review.reviewers. |
authority | yes | delegated or steward, as that policy grants. |
policy | yes | {id, version} of the active policy. |
ref | yes | The reference name. |
base | no | The reference’s revision when the review was made. |
subject | yes | {revision} for a publication or reset, or {policy} for an adoption. |
decision | yes | approve or reject. |
declarations, closure | no | What the subject binds: the revision’s gated exports, or the policy’s pins. |
note | no | Text. |
display | no | {renderer, sha256}: the approval view the reviewer read. |
At most 256 KiB. Each review.create also appends the decision to the history, even when the record exists already, so a reviewer’s latest decision on a transition is defined. An approval never stands in for missing evidence.
Guarantee records
A guarantee record stores one Std.Guarantee.IR receipt that executable computed with profiles, for a revision to cite as evidence. No method takes one from a client.
| Field | Meaning |
|---|---|
schema | lema.guarantee/v1 |
namespace | The namespace object’s ID. |
profile | NoCopySliceCall, NoBigIntPrimitives or NoBoxOrCtorInCycles. |
verdict | holds, unsupported or violated. |
identities | 1 to 64 root names mapped to accepted object IDs. |
checker | The guarantee checker’s digest. A record from another checker no longer meets a policy’s profiles. |
inventory | The SHA-256 of the captured IR inventory. |
call | The native call that captured it. |
At most 64 KiB. Its receipt’s own claim is “tested, not proved”: a static check of the captured Lean.IR closure of the roots, which reads what the elaborating process reported.
Merge records
A merge record stores the three-way comparison of two or more lines against their common base.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.merge/v1 |
namespace | yes | The namespace object’s ID. |
base | yes | The base revision: an ancestor of every parent, and a nearest common one when merge.create writes it. |
parents | yes | 2 to 16 revision IDs, in order. |
conflicts | no | Each conflict, with its resolution when one was given. |
bindings, after | no | The merged binding map and snapshot, present together exactly when every conflict is resolved. |
reuse | no | Resolved merges only: each evidence item cited on a merged line, its premises, and whether the merged target still selects them. |
At most 16 MiB. A conflict is on an export, asset, source or note, and is a collision, delete-update, update-update, rename-rename or bundle conflict. Immutable objects are never merged; a conflict is between bindings. The merge revision itself is a revision.create with the merged snapshot, the same parents in the same order, and the base. Merging is the report on a change and merge two lines guide.
Change reports
A change report says what moving from one snapshot to another changes, computed by identity from the two snapshots’ binding maps, assets and authoring bundles, never from source text. It is a computed view and is not stored; the same store state gives the same report and the same ID. It is at most 16 MiB.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.change-report/v1 |
namespace | yes | The namespace object’s ID. |
summary | yes | The count of changes per category, and obligations and uncarried. |
conservative | yes | true when every change adds an export or root, or is material that needs only its digest, and a merge revision follows its three-way merge. |
revision, before, after | no | What was compared. |
ref | no | {name, generation, event, reset?}, for a reference’s move. |
changes | no | The changes, sorted by category and then by name, path or ID. |
merge | no | For a merge revision: the comparison with its base. |
decision | no | For a reference’s move: its recorded decision. |
uncarried | no | The obligations the changes need that the revision does not carry. |
| Category | A change of | Requires |
|---|---|---|
rename | an object and role exported under another name | identity |
exports | an export or root added or removed | receipt when added, deprecation when removed |
rebind | a name, to a theorem of the same statement or a definition of the same template | receipt |
substitute | a name, to another implementation | replacement, a replace witness |
evolve | a name, to a refined or corrected contract | refinement, a refine witness |
policy | a reference’s active policy | decision |
material | sources, notes, docs, configuration, assets, authoring bundles | digest, or mapping for sources and authoring bundles |
Missing evidence becomes an open obligation in the revision format. The store has no deprecation record, so a removal always stays open until an authorized waiver closes it.
Releases
A release is an immutable record of what a deployment runs: one artifact pinned to the store’s accepted closure and to a deployment configuration’s digest. release.create checks the artifact against the tool’s rules and pins before it writes the record. A release adds no claim of its own: its proof receipts are the store’s kernel receipts, and the artifact and its IR receipts are tested.
| Field | Required | Meaning |
|---|---|---|
schema | yes | lema.release/v1 |
namespace | yes | The namespace object’s ID. |
program | yes | {kind, artifact, key, binary, provenance, exec?, runner?}, below. |
closure | exec, exec-app | {roots, objects, sha256}: the roots (name to ID), the closure’s size and its closure record’s digest. |
receipts | yes | {proof?, ir?, capability?}: the Std.Guarantee.Proof, Std.Guarantee.IR and Std.Guarantee.Capability receipts. |
bundles | no | Up to 64 bundle IDs. |
deployment | yes | {sha256}: the deployment configuration’s digest. |
doc | no | Text, at most 4,096 bytes. |
program.kind | Artifact | What the release holds |
|---|---|---|
exec | an execution artifact (lema.exec-artifact/v1) | program.exec, the closure, the proof and IR receipts |
cap-app | a capability application (lema.cap-app/v1) | the capability receipt only; its C is not bound to accepted objects |
exec-app | an app artifact (lema.exec-app-artifact/v1) | all of the above and program.runner, the reviewed runner |
program.artifact is the artifact’s ID, the SHA-256 of its canonical key. program.binary is {backend, file, sha256}. A record is at most 8 MiB.
The deployment digest is sha256("LEMA-DEPLOYMENT-v1\0" ‖ canonical(input)), over an input with schema lema.release-deployment/v1: the capability configuration as given and the digest of each trust anchor it names. Credential files are never opened, so no secret value enters the input or the release.
A release is never changed. A withdrawal is a record releases/<ab>/<id>.withdrawn.json (lema.release-withdrawal/v1) and a history line, and either one withdraws it; a withdrawn release cannot be selected. A supersession is a history line, and a superseded release can still be selected, as a rollback. Selection takes a release ID, never a name, and needs the release’s creation line in this store’s history. Withdrawal and supersession stay local to one store. Releasing is the release an artifact guide.
Written against lema-lang at commit 5a94951, 17 Oct 2026.