spec: name the single-node hosted root and its epoch-bootstrap gap
commit 638371a
spec: name the single-node hosted root and its epoch-bootstrap gap
Phase 6 built a real composition root (git.ents.cloud: loose refs, a
real odb, git’s own receive-pack, in-memory EventSink, Sprite Executor,
Mandatory gate) that docs/spec/roots.adoc never actually defined —
roots.hosted’s own text is git-ents-server (Postgres/Tigris, phase 8),
so the phase-6 root the CLI-complete milestone hinges on had no rule id
to satisfy or cite, forcing the implementer to justify Mandatory mode
from design.adoc prose instead of a requirement. Add
roots.single-node-hosted and thread it through the existing
cross-references (roots.honesty-test, effect.execution) that already
meant it.
Chasing that gap into ents-gate turned up a second one: gate.epoch
describes what the epoch-setting commit must be, but no composition
root or porcelain command is specified to ever produce one, confirmed
by zero non-test call sites in the landed code. A Mandatory-mode root
that never receives it runs with verification permanently off — the
opposite of what gate.mandatory-hosted promises. Add gate.epoch-bootstrap
(Deferred), the same acknowledged-gap shape as gate.bootstrap.
Also flag, in the phase-6 row and exit criterion, that
meta-ref.tip-invariant’s generic escape-hatch display and anchor
resolution have no command anywhere in git-ents, despite ents-model’s
own phase-2 doc comment forward-referencing them to this binary by
name — a plan omission, not a considered deferral.
No reviews of this commit yet — record a verdict below.
Start a review
docs/development-plan.adoc
@@ -152,11 +152,19 @@
| Local root wiring (~50 lines) plus the real cost: subcommand surface
and the `denyCurrentBranch=updateInstead` worktree edge. Headless — no
`serve` subcommand yet (arrives Phase 7 with `ents-web`). Doubles as
- the single-node hosted root: loose refs and a real odb on a Fly volume,
- served behind git's own `receive-pack` — the same stock-git transport
- Phase 0 bootstraps, now invoking `receive()` from a hook — with an
- in-memory `EventSink` and a boot-time reconciliation scan, and the
- Sprite executor, deployed at `git.ents.cloud`. Milestone: CLI-complete.
+ the single-node hosted root (`roots.single-node-hosted`): loose refs
+ and a real odb on a Fly volume, served behind git's own `receive-pack`
+ — the same stock-git transport Phase 0 bootstraps, now invoking the
+ gate from a hook — with an in-memory `EventSink` and a boot-time
+ reconciliation scan, and the Sprite executor, deployed at
+ `git.ents.cloud`. Milestone: CLI-complete. Two gaps ship unclosed,
+ neither assigned a phase: no porcelain command or root bootstrap sets
+ `refs/meta/config`'s verification epoch, so the hosted root's Mandatory
+ gate stays archival until one is designed (`gate.epoch-bootstrap`); and
+ `meta-ref.tip-invariant`'s generic escape-hatch display for an
+ unrecognized meta-ref, and anchor resolution, have no command anywhere
+ in this binary, despite `ents-model` (phase 2) forward-referencing them
+ to "the `git-ents` binary" by name.
| `ents-web`
| 7
@@ -211,7 +219,9 @@
* *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 -9` of the in-memory queue.
+ `kill -9` of 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 8 exit*: the honesty test (`roots.honesty-test`) — the
Postgres/Tigris/durable-queue root, now served by `gix-receive` in
place of git's own `receive-pack`, is wired as a new composition root
docs/spec/effect.adoc
@@ -60,7 +60,8 @@
effect's command inside a sandbox.
Sandboxed execution MUST sit behind one `Executor` trait with multiple
backends, selected only at a composition root (<<roots.local>>,
-<<roots.hosted>>), so no execution logic is duplicated per backend.
+<<roots.single-node-hosted>>, <<roots.hosted>>), so no execution logic is
+duplicated per backend.
Host-direct execution, with no sandbox, MUST require an explicit
`--unsandboxed` flag and MUST be available only locally, never on canonical
hosted infrastructure.
docs/spec/gate.adoc
@@ -180,3 +180,21 @@
Closing "first push owns the repo" beyond this bootstrap admission is a
hosted-deployment concern, deferred to <<roots.bootstrap>>.
--
+
+[role="requirement", id="gate.epoch-bootstrap"]
+.Epoch Establishment Has No Assigned Trigger (Deferred)
+--
+<<gate.epoch>> defines what the epoch-setting commit to `refs/meta/config`
+must be, but no composition root or porcelain command is specified to
+produce one.
+A Mandatory-mode root (<<gate.mandatory-hosted>>, <<roots.single-node-hosted>>,
+<<roots.hosted>>) that never receives that commit runs with verification
+permanently off: every `refs/meta/*` push stays archival, indistinguishable
+from pre-epoch history, and the "abort before any ref is updated" guarantee
+never actually engages.
+A root establishing its own epoch as part of first boot — before any
+member exists to author a porcelain command — is the likely shape,
+mirroring this section's own self-admitting first enrollment, but this is
+chosen direction, not yet designed or enforced, the same acknowledged gap
+as <<gate.bootstrap>>.
+--
docs/spec/roots.adoc
@@ -31,6 +31,27 @@
(<<effect.local-run>>).
--
+[role="requirement", id="roots.single-node-hosted"]
+.git-ents Single-Node Hosted Root
+--
+The `git-ents` binary's second composition root (the CLI-complete
+milestone deployed at `git.ents.cloud`) MUST wire: a loose-ref `RefStore`,
+a real on-disk object database, an in-memory `EventSink` scoped to one
+hook invocation and reconciled at boot (<<receive.reconstructible>>), a
+Fly.io Sprite `Executor`, and the mandatory gate (<<gate.mandatory-hosted>>).
+Unlike <<roots.local>>, git's own `receive-pack` is the transport and
+performs the actual object unpack and ref update; this root's
+`pre-receive`/`post-receive` hooks call the gate and reconcile obligations
+around that write, never through this crate's own `RefStore::transaction`,
+to avoid a double-write race against `receive-pack`'s internal ref update.
+This is not <<roots.hosted>>: that root replaces the `RefStore` and object
+store themselves with Postgres and Tigris once scale forces `receive-pack`
+out entirely (<<roots.honesty-test>>); this root keeps a real on-disk
+repository and stock git as the one hosted and local serving transport
+until that phase — "stock git wearing the same gate everything else runs,
+not a bespoke protocol."
+--
+
[role="requirement", id="roots.hosted"]
.git-ents-server Hosted Root
--
@@ -49,8 +70,9 @@
`EventSink` (<<roots.hosted>>) as a new composition root MUST require
zero modification to any library crate.
This is the project's honesty test for the seam design: the proof is an
-actual production migration off the single-node hosted root, not a
-synthetic root built to demonstrate the seams.
+actual production migration off the single-node hosted root
+(<<roots.single-node-hosted>>), not a synthetic root built to demonstrate
+the seams.
--
[role="requirement", id="roots.config-isolation"]