The library MUST NOT define its own ObjectStore trait.
gitoxide’s gix_object::Find, Exists, and Write traits ARE the
object-store seam.
A new trait MUST be introduced only where gitoxide is silent — the
pluggable ref store, server-side receive framing, and reachability
artifacts are the seams that qualify.
docs/spec/overview.adoc
Specification
git-ents is a Git forge: membership, issues, anchored code comments,
code review, and CI-style effects, with a browser UI and a CLI.
Every piece of state — members, configuration, effects, results, issues,
comments — lives in the repository itself as typed documents on git
meta-refs, versioned, signed, and auditable, with no external database.
A hosted server is only custody of the canonical refs: a store that runs
the same verification gate any clone can run, plus a queue that
dispatches effects.
The spec is organized around the load-bearing abstractions (rationale in
docs/abstractions.adoc), each a parent section with its dependent entities
as children:
-
meta-ref.adoc— the meta-ref and the typed tree. -
model.adoc— the entities stored on meta-refs: members, comments, effects, results, toolchains, and accounts. -
anchor.adoc— durable pointers into content, and their projection. -
gate.adoc— the pure verify function and its three call sites. -
query.adoc— theCommitQueryalgebra that triggers effects. -
receive.adoc— the one write path every frontend shares. -
effect.adoc— effect execution, results, and toolchains at run time. -
sync.adoc— meta-ref fetch/push, pre-flight, and divergence merge. -
roots.adoc— the composition roots, the only place deployment exists.
This specification is not yet stable and will grow as the project develops.
''
The Six Abstractions
Everything else in this specification is an instance or a consequence of six load-bearing abstractions. This section orients; the normative requirements for each abstraction live in the file named alongside it, where one exists yet.
1. Meta-Ref
A ref under refs/meta/* is simultaneously the unit of storage (the ref
points at a commit whose tree is the entity), synchronization (fetch or
push only the entities a client cares about), authorization (refname-keyed
rules gate who may advance the ref), and history (its commit chain is the
audit trail).
One ref holds exactly one independently-authored entity, or one piece of
repository-global state; see meta-ref.adoc.
2. Typed Tree
A Rust struct annotated #[derive(Facet)] is the storage schema: no
serialization format sits between the struct and the git tree
facet-git-tree maps it to, and changing the struct is a storage
migration — a signed commit, not a silent break; see meta-ref.adoc.
3. Anchor
A durable pointer into source — a blob, an optional line range, and the
commit it was taken against — embedded in the anchoring document’s own
tree so it survives force-push, branch deletion, and gc, and projected
onto newer commits at read time by blame plus fuzzy matching, never
mutated; see anchor.adoc.
4. Signed Commit
Every meta-ref mutation is an author-signed commit whose signature replicates with the repository and verifies offline in any clone. A signature proves authorship, not placement, so the refname is bound by recomputation from the commit’s own signed content — a genesis oid, a natural-key tree field, a composite of fields and signer — and adopting someone else’s commit onto a canonical ref is always a merge, never a cherry-pick, so the author’s signature survives intact in ancestry.
5. Gate
Verification is a pure function over ref-store reads — is the new tip signed by a member authorized for this refname, does the refname recompute from its signed content, does it descend from the old tip, does the update commit via atomic CAS — evaluated identically at three call sites: hosted CAS (mandatory, failure aborts the write), local UI verdict (advisory), and push pre-flight (advisory).
6. Effect
A declarative, content-addressed subscription to a commit-set query, whose execution is sandboxed and whose output re-enters the repository only as signed commits on a results ref. The trigger fires once per commit entering the query’s set, work is monotone and entry-only, and the runner is a member, not an ambient authority, so an official result is a refname rule on canonical results refs rather than a runtime property of a blessed machine.
''
Architecture
The abstractions above compose into a crate graph with a small number of hard boundaries. Violating one of them is the design failing, not a detail; this section states them as requirements.
Crate Graph
|
Note
|
Crate names below are working names, not commitments.
A |
| Crate | Responsibility | Depends on |
|---|---|---|
|
Typed tree: struct ↔ git tree mapping, domain-blind. |
|
|
|
|
|
Smart-HTTP wire framing and pack ingestion, zero forge policy. |
|
|
Entity structs, refname namespaces, trailers, status taxonomy. |
|
|
|
|
|
The pure verify function. |
|
|
|
|
|
Anchor storage and projection. |
|
|
|
|
|
Meta-ref fetch/push and own-heads merge. |
|
|
The local and hosted web UI. |
|
|
The editor surface: comments projected over open buffers as a language server. |
|
|
Local composition root. |
|
|
Hosted composition root. |
|
Boundary Rules
The gate’s pure verify function MUST live in a crate separate from
receive.
The gate has three call sites — hosted CAS, local UI verdict, and push
pre-flight — and the latter two MUST be answerable without linking
`receive’s effect-matching and enqueue logic.
The CommitQuery algebra MUST live in a crate separate from executor and
run-loop code.
receive depends on the query crate for footprint matching on every push
and MUST NOT depend on the effect crate, so no push path links executor
code.
A concrete store implementation — Postgres, Tigris, SQLite, or any other backing store — MUST be wired only inside a composition root, never inside a library crate. It MUST NOT be promoted into a shared library crate until a second composition root consumes it.
A library crate MUST NOT contain a branch on deployment mode — an
if hosted-shaped check, or equivalent.
Deployment MUST exist only in composition roots, which wire trait
implementations together.
The RefStore trait MUST separate its read operations from its atomic
multi-ref compare-and-swap operation.
The gate MUST depend on only the read half, since verification never
performs a write.
A loose-ref RefStore implementation MUST write refs through its own
compare-and-swap discipline and MUST NOT shell out to git update-ref,
so local mutations honor the same CAS guarantee hosted storage provides.
The honesty test for these boundaries — a new composition root
constructible from library crates alone, no library modification — is a
deployment requirement and lives in roots.adoc as
<<roots.honesty-test>>.