git-ents.gitmain
⌘K
foforge
commit db98a25
spec: verdict reasons, advisory prediction, auth-layer vocabulary

gate.adoc: add gate.verdict-reason requiring the gate to return the failed rule and subject refname on failure, rendered by every advisory call site; cross-reference the inbox offer from gate.advisory-local. sync.adoc: widen sync.inbox-routing so the offer fires on any negative advisory verdict, including the local UI verdict at commit time, not only pre-flight or an actual rejection. receive.adoc: state that a proposal’s transport-auth evidence is a connection-level ACL input for refs/heads/* only, and that gate evaluation for meta-ref admission MUST ignore it. roots.adoc: fix the ambiguous "smart-HTTP frontend" wording in roots.local (git ents serve exposes the ents-web HTTP UI, never git’s wire protocol); reframe roots.worktree-update as integration-test- harness capability rather than a local-serving feature; add a non-normative example of the canonical advisory-verdict story next to the web-UI rules.

Joseph D. Carpinelli · 1 month ago

Reviews

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

Start a review

verdict

docs/spec/gate.adoc @@ -89,6 +89,10 @@ annotating the result; a failing verdict MUST NOT block the write. Enforcing the gate locally would destroy offline-first: a user could not author while unenrolled, or work against an unfetched member list. +A failing local verdict, computed entirely offline against last-fetched +policy (<<gate.policy-as-state>>), MUST surface the inbox alternative +(<<sync.inbox-routing>>) at verdict time, not only once a push is +actually rejected. -- [role="requirement", id="gate.call-sites"] @@ -101,6 +105,18 @@ diverge from the rules the hosted store will actually apply. -- +[role="requirement", id="gate.verdict-reason"] +.Verdict Carries a Machine-Readable Reason +-- +On failure the gate MUST return which requirement +(<<gate.tip-signed>> through <<gate.atomic-cas>>) failed and the subject +refname, not a bare pass/fail. +Every advisory call site (<<gate.advisory-local>>, <<sync.pre-flight>>) +MUST render this reason to the user — for example, "your signing key is +not authorized for `refs/heads/main`" — so a negative verdict is +actionable before a push is ever attempted. +-- + === Adoption [role="requirement", id="gate.adoption-merge"]
docs/spec/receive.adoc @@ -28,6 +28,10 @@ authentication. Gate evaluation (<<receive.unit>>) MUST be checkable against exactly this shape, independent of which frontend constructed the proposal. +This transport-auth evidence is a connection-level ACL input for +`refs/heads/*` (<<gate.principled-split>>) only; gate evaluation for +meta-ref admission MUST ignore it entirely and MUST NOT consult it in +place of the tip invariant (<<gate.signature-artifact>>). -- [role="requirement", id="receive.shared-path"]
docs/spec/roots.adoc @@ -23,8 +23,9 @@ The `git-ents` CLI's composition root MUST wire: a loose-ref `RefStore`, the local odb as object store, a Docker `Executor`, a null `EventSink`, and the advisory gate (<<gate.advisory-local>>). -`git ents serve` MUST reuse this same wiring, adding only the smart-HTTP -frontend on loopback. +`git ents serve` MUST reuse this same wiring, adding only the `ents-web` +HTTP frontend on loopback — the web UI, never git's smart-HTTP wire +protocol, which the local root MUST NOT expose. Local effect execution MUST be pull, via `git effect run`, never a daemon watching refs; the queue is the only component this root skips (<<effect.local-run>>). @@ -79,16 +80,35 @@ remain a supported deployment alongside hosted and local-loopback serving. -- +[NOTE] +==== +Example: policy lets Joey write `joey/*` and `refs/meta/issue/*`. He +starts the local UI (<<roots.web-signing>>). A commit to an issue, or +to `joey/*`, shows green. A commit to `main` shows orange or red — +"when you push, the server will reject this: your signing key is not +authorized for `refs/heads/main`" (<<gate.verdict-reason>>) — with an +offer to send it to his inbox instead (<<sync.inbox-routing>>). +Nothing was ever blocked locally: every verdict is a prediction of the +gate, evaluated against policy as of Joey's last fetch +(<<gate.policy-as-state>>), rendered by the same in-process signing +this section specifies — never a separate git-serving transport. +==== + [role="requirement", id="roots.worktree-update"] -.Local Serve and the Working Tree +.Test-Harness Pushes Into a Working Tree -- -The `git-ents` local root MUST set `receive.denyCurrentBranch=updateInstead` -so an accepted branch push also updates the working tree. +Pushing into a local repository's checked-out branch from outside is +integration-test-harness capability, not a product-facing feature: the +local root's user-facing serving surface is the web UI +(<<roots.web-signing>>), never a git transport. +For that harness case, the `git-ents` local root MUST set +`receive.denyCurrentBranch=updateInstead` so an accepted branch push +also updates the working tree. Worktree update MUST happen only after `receive` accepts the push, as frontend business; core code MUST NOT touch a worktree. -A dirty worktree is a known edge where `updateInstead` fails, so -branch-push behavior differs from hosted while metadata behavior, which -never touches a worktree, stays identical. +A dirty worktree is a known edge where `updateInstead` fails, so this +harness path differs from hosted while metadata behavior, which never +touches a worktree, stays identical. -- [role="requirement", id="roots.web-session"]
docs/spec/sync.adoc @@ -29,10 +29,13 @@ [role="requirement", id="sync.inbox-routing"] .Rejection Routes to the Inbox -- -When pre-flight predicts, or the canonical store actually returns, a -rejection of a push to a canonical meta-ref, sync MUST offer to route the -same commit to the author's `refs/meta/inbox/*` ref instead of discarding -it. +Any negative advisory verdict against a canonical meta-ref — the local UI +verdict at commit time (<<gate.advisory-local>>), push pre-flight +(<<sync.pre-flight>>), or the canonical store's actual rejection — MUST +cause sync to offer routing the same commit to the author's +`refs/meta/inbox/*` ref instead of discarding it. +The offer MUST appear the moment the verdict goes negative, not only +after a push is actually attempted and refused. -- [role="requirement", id="sync.divergence-merge"]