docs/development-plan.adoc
Development Plan
Build order, complexity, and agent assignment for the thirteen crates in
docs/spec/overview.adoc.
Phases follow the dependency graph; crates within a phase have no edges
between them and build in parallel.
The pre-redo tag is the salvage path throughout: correctness that was
hard-won once (CAS discipline, anchor projection, docker readiness,
sprite quirks, backend conformance, Postgres/Tigris store code) is ported
and re-verified, not rediscovered.
Tools and abstractions before scale-out govern the ordering below: a
phase earns its place by exercising a real abstraction (the gate, the
query engine, receive, sync) against stock git, not by anticipating
load git itself would have to buckle under. gix-receive is the clearest
instance — a custom wire-framing crate structurally required only once
Postgres-backed refs make git’s own receive-pack impossible to run
directly, so it moves out of the foundation and into the scale-out phase
alongside the Postgres RefStore, Tigris objects, and the durable queue
it ships with. Until that phase, git’s own receive-pack is the one
hosted and local serving transport; the single-node root at
git.ents.cloud (Phase 6) is stock git wearing the same gate everything
else runs, not a bespoke protocol.
Agent scheme
-
sonnet — bulk and medium-complexity implementation, ports from
pre-redo, template/CLI surface breadth. -
opus — high-complexity subsystems: wire protocols, merge algorithms, store backends with subtle atomicity.
-
fable — the two crates where a quiet bug is a security hole or a wrong design (
ents-gate,ents-querycore semantics), public-API review of every crate at its phase boundary, and integration.
Every crate lands with Tracey annotations mapping its spec rules
(.config/tracey/config.styx already covers docs/spec/), tests run
via cargo nextest run, and a fable review of the public API before
anything in the next phase depends on it.
Phase 0: bootstrap
Before any crate lands, redeploy the existing Fly app
(.config/fly.toml) as a headless machine serving two things behind
one host — no gix-receive, no gate, no CI or Sprite executor:
-
stock git’s smart-HTTP backend (
git http-backend) against a bare repo on theodbvolume, sogit.ents.cloudis a realoriginfrom day one; and -
tracey web, reading a plain working tree a post-receive hook checks out on every push (the sameupdateInsteadtrick Phase 6 formalizes for real), so the same host shows live spec coverage on HEAD.
Every phase number below is one higher than in any prior ordering of
this document.
Nothing here is salvaged or reusable code — the single-node hosted
root in Phase 6 (git-ents, the CLI-complete milestone) replaces the
git backend wholesale, at the same hostname; whether it keeps serving
the coverage dashboard is a later call.
Crate schedule
| Crate | Phase | Complexity | Agent | Notes |
|---|---|---|---|---|
|
1 |
Low |
sonnet |
Exists (external repo). Stays domain-blind. Land the
|
|
1 |
Medium |
sonnet |
Read/CAS trait split per |
|
2 |
Low |
sonnet |
Entity structs, namespaces, trailers, taxonomy — declarative, but it is
the vocabulary every crate imports, so spec fidelity and the public API
review matter more than the code. Trailer parsing is new work, not a
port: no |
|
3 |
Medium |
fable |
Small in lines, security-critical in every line: tip signature,
refname binding, DAG-FF, epoch, bootstrap fail-closed. Pure over the
read half of |
|
3 |
High |
fable |
The critical path: grammar/parser (easy), static footprint extraction,
incremental set-entry via generation numbers, work set
|
|
3 |
Medium |
sonnet |
Mostly a port: projection (blame + fuzzy context match) survives at
|
|
4 |
Medium |
sonnet |
Orchestration above traits that already exist by now: gate
policy (mandatory/advisory), footprint matching, enqueue, |
|
4 |
High |
opus |
Fetch/push plumbing is routine; the schema-aware three-way merge over typed trees (divergence + adoption on one machinery) is the hard part. Absorbs git-store’s merge. |
|
5 |
Medium |
sonnet |
|
|
6 |
Medium |
sonnet |
Local root wiring (~50 lines) plus the real cost: subcommand surface
and the |
|
7 |
Medium |
sonnet |
Broad surface, low algorithmic depth. Lands |
|
8 |
High |
opus |
Scale-out only: structurally necessary once the Postgres |
|
8 |
High |
opus |
Postgres |
|
9 |
Medium |
fable |
Comments become the universal conversational primitive ( |
|
9 |
High |
opus |
The LSP frontend ( |
|
9 |
Low |
sonnet |
Zed extension at |
|
9 |
Medium |
opus |
Issues index and detail with threads as context queries
( |
kernel identity binding ( |
10 |
High |
fable |
One atomic migration — the kernel cannot build half-converted. Retire
|
forge and surface identity migration ( |
10 |
Medium |
sonnet |
|
Phase gates
-
Phase 0 exit:
git.ents.cloudresolves over HTTPS;git cloneandgit pushround-trip against it using stock git; this repo’soriginpoints at it; thetracey webdashboard at the same host reflects HEAD’s coverage after each push. -
Phase 1 → 2:
gix-ref-storepasses a CAS conformance suite (concurrent writers, crash injection). -
Phase 3 → 4: gate verdicts proven identical across call sites; query evaluator handles the staged-pipeline and fan-in idioms incrementally on a synthetic repo.
-
Phase 4 → 5: end-to-end local write path (
receivein-process, advisory gate, nullEventSink) green; sync merge fuzz-tested against divergent typed trees. -
Phase 6 exit: the CLI-complete milestone — every porcelain command green against the single-node hosted root; the boot-time reconciliation scan regenerates obligations correctly after a
kill -9of the in-memory queue. The two gaps this row’s own notes name (gate.epoch-bootstrap; `meta-ref.tip-invariant’s missing reader surface) are known exceptions, not silent ones. -
Phase 7 exit:
git ents serve(roots.local) is loopback-only with no smart-HTTP surface added, drives every state-changing route through a per-session CSRF check (roots.web-session), and signs mutations through an identity the composition root injects, never one this crate resolves itself (roots.web-signing,roots.web-agnostic); the same router is provably reachable with no bound socket, driven in-process viatower::ServiceExt::oneshot. This row’s gap — no hosted deployment mounting the web UI, the server-key half proven only against fixtures — closed after phase 10:git ents serve --hostedwires it onto the single-node hosted root atgit.ents.cloud, with member sign-in (roots.web-signin) gating mutations. -
Phase 8 exit: the honesty test (
roots.honesty-test) — the Postgres/Tigris/durable-queue root, now served bygix-receivein place of git’s ownreceive-pack, is wired as a new composition root with zero library-crate modification;gix-receiveround-trips a push from stock git, and a diff of library-crate contents between phase entry and phase exit MUST be empty. -
Phase 9 exit: the comment loop — a comment composed through the lens’s editor-file flow against a dirty working tree (
lens.compose,anchor.working-tree) is listed open by the machine-readablegit ents comment list --worktreeform (lens.parity), resolved through the CLI (model.comment-state), and gone from the lens’s next publish; an issue and a review each round-trip CLI ↔ web with their threads rendered as context queries (model.comment-context,model.review). This being an experimental repository with no on-disk data to preserve, a struct change is a clean break: entities read only as their current shape, and no back-compat reader is kept — `meta-ref.migration’s forward rewrite applies whenever data actually needs carrying, not a permanent legacy code path. -
Phase 10 exit: identity is derivation — no minted id and no binding trailer exists anywhere in the workspace. The doppelgänger replay (a signed mutation commit proposed as the genesis of a new entity) and the result replay (a signed
passproposed for a different effect or commit) are refused by tests drivingreceive(gate.identity-binding,model.result-identity); entity structs prove pairwise disjoint under strict decode; a comment created, replied to, and resolved, and a commit reviewed then re-reviewed (the pin advancing fast-forward), round-trip through CLI and web under genesis-oid and composite ids (model.comment,model.review). Same clean-break rule as phase 9: no legacy reader, ids regenerate with the data. The two gaps this row’s own notes name (gate.non-kernel-strict-decode;gate.redaction-vouching-undefined) are known exceptions, not silent ones: a comment or issue genesis is not yet refused from double-admission across namespaces, and the binding’s redaction-vouching clause is unimplemented.
Backend conformance tests are shared suites written once against each
trait (RefStore, EventSink, Executor) and run per implementation —
the pre-redo backend-conformance pattern, resurrected.