git-ents.gitmain
⌘K
foforge
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.

Assisted-by: Claude:claude-sonnet-5

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

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"]