git-ents.gitmain
⌘K
foforge
commit 72c4dbb
refactor: rename the `signers` module to `members`

The push-trust concept is uniformly a Member — the CLI is git ents members, the type is Member, the refs are refs/meta/member/* — but the module kept the signers name from the old auth design, so code read git_ents::signers::Member and held members in signers vars. Make the name match the concept. The OpenSSH allowed_signers term stays, as it is OpenSSH’s own file-format name.

refactor: rename module git_ents::signers to git_ents::members docs: correct git-store MapDoc/Row docs that still cited the signer set

Assisted-by: Claude:claude-opus-4-8

Joseph D. Carpinelli · 1 month ago

Reviews

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

Start a review

verdict

crates/git-ents-server/src/verify.rs @@ -14,8 +14,8 @@ use std::path::Path; use std::process::{Command, Stdio}; +use git_ents::members::{self, Member}; use git_ents::revocations; -use git_ents::signers::{self, Member}; /// Verify the push git is about to apply, returning `Ok(())` to accept it or /// `Err(reason)` to reject it. The push certificate is read from the @@ -23,7 +23,7 @@ pub fn pre_receive() -> Result<(), String> { let repo = std::env::current_dir().map_err(|e| format!("cannot resolve repository: {e}"))?; let members = - signers::load_all(&repo).map_err(|e| format!("could not read authorized signers: {e}"))?; + members::load_all(&repo).map_err(|e| format!("could not read authorized signers: {e}"))?; if members.is_empty() { // No trust list pushed yet: stay open so the first signer can be added. // Revocation is keyed on member refs existing, so revoking every member's @@ -33,7 +33,7 @@ } let revoked = revocations::fingerprints(&repo).map_err(|e| format!("could not read revocations: {e}"))?; - let authorized = signers::without_revoked(members, &revoked); + let authorized = members::without_revoked(members, &revoked); let cert_oid = env("GIT_PUSH_CERT") .filter(|oid| !oid.is_empty()) @@ -64,7 +64,7 @@ let signature_path = workdir.path().join("cert.sig"); write_file( &allowed_path, - signers::allowed_signers(authorized).as_bytes(), + members::allowed_signers(authorized).as_bytes(), )?; write_file(&signature_path, signature.as_bytes())?;
crates/git-ents/src/lib.rs @@ -4,8 +4,8 @@ pub mod checks; pub mod config; pub mod issues; +pub mod members; pub mod revocations; -pub mod signers; #[cfg(test)] mod testutil;
crates/git-ents/src/main.rs @@ -6,7 +6,7 @@ //! set, and the client setup that produces the signed pushes the server //! requires. The member commands read and write a remote's set by fetching the //! `refs/meta/member/*` refs into the local repository, editing them through -//! [`git_ents::signers`], and pushing them back. +//! [`git_ents::members`], and pushing them back. use std::collections::BTreeMap; use std::path::{Path, PathBuf}; @@ -15,8 +15,8 @@ use clap::{Parser, Subcommand}; use git_ents::account::{self, Account}; use git_ents::checks::{self, CHECKS_REF, Check}; +use git_ents::members::{self, MEMBER_NS, Member, Trust, member_ref}; use git_ents::revocations::{self, REVOKED_REF, Revocation}; -use git_ents::signers::{self, MEMBER_NS, Member, Trust, member_ref}; #[derive(Parser)] #[command(name = "git-ents", about = "Helpful guardians of your git trees.")] @@ -523,7 +523,7 @@ let repo = repo()?; sync_namespace(remote, MEMBER_NS)?; sync(remote, REVOKED_REF)?; - let members = signers::load_all(&repo).map_err(|error| error.to_string())?; + let members = members::load_all(&repo).map_err(|error| error.to_string())?; if members.is_empty() { println!("no members on {remote} (open bootstrap window)"); return Ok(()); @@ -625,7 +625,7 @@ let repo = repo()?; let refname = member_ref(username); let expected = sync(remote, &refname)?; - let mut member = signers::load(&repo, username) + let mut member = members::load(&repo, username) .map_err(|error| error.to_string())? .unwrap_or_else(|| Member::with_keys(username.to_owned(), BTreeMap::new())); if valid_after.is_some() { @@ -640,7 +640,7 @@ if let Some(ca_path) = cert_authority { let ca = read_public_key(ca_path)?; member.trust = Trust::CertAuthority(ca); - signers::store(&repo, &member).map_err(|error| error.to_string())?; + members::store(&repo, &member).map_err(|error| error.to_string())?; push_signed(remote, &refname, expected.as_deref())?; println!("pinned a certificate authority for {username}"); return Ok(()); @@ -665,7 +665,7 @@ return Ok(()); } keys.insert(fingerprint.clone(), public_key); - signers::store(&repo, &member).map_err(|error| error.to_string())?; + members::store(&repo, &member).map_err(|error| error.to_string())?; push_signed(remote, &refname, expected.as_deref())?; println!("authorized {fingerprint} for {username}"); Ok(()) @@ -743,7 +743,7 @@ let public_key = public_key(key)?; let fingerprint = fingerprint(&public_key)?; sync_namespace(remote, MEMBER_NS)?; - let members = signers::load_all(&repo).map_err(|error| error.to_string())?; + let members = members::load_all(&repo).map_err(|error| error.to_string())?; if members.is_empty() { println!("{remote}: open bootstrap window (no members yet)"); } else if let Some(member) = members.iter().find(|member| {
crates/git-ents/tests/cert_authority.rs @@ -7,7 +7,7 @@ )] //! The Phase 3 CA-pin gate: a certificate the pinned CA issued verifies against -//! the `allowed_signers` file `git_ents::signers` renders, and a certificate +//! the `allowed_signers` file `git_ents::members` renders, and a certificate //! from an unpinned CA does not. The cert-embedded signature is produced the way //! a real client would — through an `ssh-agent` holding the key and its //! certificate — since `ssh-keygen -Y sign` only embeds a certificate when the @@ -17,7 +17,7 @@ use std::process::{Command, Stdio}; use std::sync::atomic::{AtomicUsize, Ordering}; -use git_ents::signers::{Member, Trust, allowed_signers}; +use git_ents::members::{Member, Trust, allowed_signers}; /// The principal the CA certifies and the verifier checks — the pusher identity. const PRINCIPAL: &str = "tester@example.com"; @@ -60,7 +60,7 @@ std::fs::remove_dir_all(&dir).ok(); } -/// Write the `allowed_signers` file `git_ents::signers` renders for a member +/// Write the `allowed_signers` file `git_ents::members` renders for a member /// whose trust is the CA `ca`, and return its path. fn render_ca_allowed_signers(dir: &Path, name: &str, ca: &Key) -> PathBuf { let ca_pubkey = std::fs::read_to_string(&ca.public)
crates/git-store/src/lib.rs @@ -54,9 +54,9 @@ } /// A meta-ref document that is a single named map of string keys to string -/// values — the shape the signer set, the check set, and a run's outcomes all -/// share. The wrapping struct's one field fixes the on-disk subtree name -/// (`signers/`, `checks/`, `results/`), so each document stays its own type; +/// values — the shape the check set, the revocation list, and a run's outcomes +/// all share. The wrapping struct's one field fixes the on-disk subtree name +/// (`checks/`, `revoked/`, `results/`), so each document stays its own type; /// this trait is only the bridge that lets them share the load/store plumbing /// in [`Store::load_entries`] and [`Store::store_entries`]. pub trait MapDoc: for<'a> Facet<'a> { @@ -67,7 +67,7 @@ } /// One `(key, value)` entry of a [`MapDoc`] presented as a named type. The set -/// documents expose legible structs (`Signer`, `Check`, `RunOutcome`) rather +/// documents expose legible structs (`Check`, `Revocation`, `RunOutcome`) rather /// than bare pairs; this trait is the single bridge between such a struct and /// the `(key, value)` shape stored on disk, so the wrap/unwrap is written once /// here instead of at every load and store.
crates/git-ents-server/src/web/pages.rs @@ -819,7 +819,7 @@ /// derived feature and check status. Editing is a members-gated write path that /// does not exist yet, so the values are presented as the current configuration. pub(super) async fn settings_page(repo: &Path, meta: &RepoMeta) -> Markup { - let signers = load_signers(repo).await; + let members = load_members(repo).await; let checks = load_checks(repo).await; repo_shell( meta, @@ -863,21 +863,21 @@ div.card { div.card-header { "Members" - @if let Ok(signers) = &signers { span.count { (signers.len()) } } + @if let Ok(members) = &members { span.count { (members.len()) } } } p.shell-note { "People on " code { "refs/meta/member/*" } " whose signed pushes are accepted " "(" code { "git ents members list" } ")." } - @match &signers { + @match &members { Err(err) => div.card-row.muted { "Could not read members: " (err) } - Ok(signers) if signers.is_empty() => { + Ok(members) if members.is_empty() => { div.card-row.muted { "No members — pushes are open until the first key is added." } } - Ok(signers) => { - @for member in signers { + Ok(members) => { + @for member in members { (member.render()) } } @@ -910,11 +910,11 @@ ) } -/// Load the member set off the async runtime, since `signers::load_all` shells +/// Load the member set off the async runtime, since `members::load_all` shells /// out to git and reads the object database synchronously. -async fn load_signers(repo: &Path) -> Result<Vec<git_ents::signers::Member>, String> { +async fn load_members(repo: &Path) -> Result<Vec<git_ents::members::Member>, String> { let repo = repo.to_owned(); - tokio::task::spawn_blocking(move || git_ents::signers::load_all(&repo)) + tokio::task::spawn_blocking(move || git_ents::members::load_all(&repo)) .await .map_err(|err| err.to_string())? .map_err(|err| err.to_string())
crates/git-ents-server/src/web/render.rs @@ -14,7 +14,7 @@ use git_ents::checks::{Check, Run}; use git_ents::config::Config; use git_ents::issues::Issue; -use git_ents::signers::Member; +use git_ents::members::Member; /// HTML rendering for a meta-ref value. The default walks the value's [`Facet`] /// shape structurally; a type overrides [`render`](Render::render) when its
crates/git-ents/src/signers.rs → crates/git-ents/src/members.rs @@ -1,5 +1,9 @@ //! The repository's members, sourced from the `refs/meta/member/*` refs. //! +//! A *member* is one person whose signed pushes the repository accepts. The +//! OpenSSH `allowed_signers` file this module renders keeps that name because it +//! is OpenSSH's own format term, but everything else here is framed as members. +//! //! Push authentication trusts exactly one place: the `refs/meta/member/<username>` //! refs. Each is a [`Member`] document — one person, named by the ref's last //! segment — recording the keys whose signed pushes are accepted and the window