refactor!: rename the trust ref refs/meta/auth to refs/meta/members
commit
b096979refactor!: rename the trust ref refs/meta/auth to refs/meta/members
The push trust root is the repository’s member set. Rename the ref, the
Auth/Members document and its signers/→members/ subtree, and the
git ents auth subcommand group to git ents members. Hard break: the
live trust list is discarded and re-pushed after redeploy, no fallback.
refactor: rename signers AUTH_REF/Auth to MEMBERS_REF/Members
refactor: rename git ents auth subcommand group to git ents members
Assisted-by: Claude:claude-opus-4-8
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
crates/git-ents-server/src/verify.rs
@@ -1,9 +1,9 @@
//! The `pre-receive` verifier: a git hook that gates pushes on a signature from
-//! an authorized signer.
+//! a member.
//!
-//! When the trust list at `refs/meta/auth` is empty the server is still in its
-//! open bootstrap window and every push is allowed, so the first signer can be
-//! pushed in. Once any signer is listed, a push must carry a signed-push
+//! When the trust list at `refs/meta/members` is empty the server is still in
+//! its open bootstrap window and every push is allowed, so the first member can
+//! be pushed in. Once any member is listed, a push must carry a signed-push
//! certificate (`git push --signed`) whose anti-replay nonce git accepted and
//! whose signature verifies against one of those keys.
crates/git-ents-server/tests/pre_receive.rs
@@ -95,10 +95,10 @@
let oid = hash_object(&repo, key.as_bytes());
tree_entries.push_str(&format!("100644 blob {oid}\tkey-{index}\n"));
}
- let signers_tree = mktree(&repo, &tree_entries);
- let root_tree = mktree(&repo, &format!("040000 tree {signers_tree}\tsigners\n"));
- let commit = ok(&repo, "git", &["commit-tree", &root_tree, "-m", "auth"]);
- ok(&repo, "git", &["update-ref", "refs/meta/auth", &commit]);
+ let members_tree = mktree(&repo, &tree_entries);
+ let root_tree = mktree(&repo, &format!("040000 tree {members_tree}\tmembers\n"));
+ let commit = ok(&repo, "git", &["commit-tree", &root_tree, "-m", "members"]);
+ ok(&repo, "git", &["update-ref", "refs/meta/members", &commit]);
}
repo
}
crates/git-ents/src/main.rs
@@ -1,18 +1,17 @@
//! `git ents` — the git-ents command-line porcelain.
//!
-//! Today it carries a single command, `git ents auth`, for managing the
-//! authorized push signers recorded at `refs/meta/auth` and for configuring
-//! this client to produce the signed pushes the server requires. The signer
-//! commands read and write a remote's set by fetching `refs/meta/auth` into the
-//! local repository, editing it through [`git_ents::signers`], and pushing it
-//! back.
+//! Today it carries a single command, `git ents members`, for managing the
+//! repository members recorded at `refs/meta/members` and for configuring this
+//! client to produce the signed pushes the server requires. The member commands
+//! read and write a remote's set by fetching `refs/meta/members` into the local
+//! repository, editing it through [`git_ents::signers`], and pushing it back.
use std::path::{Path, PathBuf};
use std::process::{Command, ExitCode, Stdio};
use clap::{Parser, Subcommand};
use git_ents::checks::{self, CHECKS_REF, Check};
-use git_ents::signers::{self, AUTH_REF, Signer};
+use git_ents::signers::{self, MEMBERS_REF, Signer};
#[derive(Parser)]
#[command(name = "git-ents", about = "Helpful guardians of your git trees.")]
@@ -23,8 +22,8 @@
#[derive(Subcommand)]
enum Top {
- /// Manage the authorized push signers at `refs/meta/auth`.
- Auth {
+ /// Manage the repository members at `refs/meta/members`.
+ Members {
#[command(subcommand)]
action: Action,
},
@@ -47,32 +46,32 @@
#[arg(long)]
local: bool,
},
- /// List the authorized signers on a remote.
+ /// List the members on a remote.
List {
- /// Remote to read `refs/meta/auth` from.
+ /// Remote to read `refs/meta/members` from.
#[arg(default_value = "origin")]
remote: String,
},
- /// Add a signer to a remote's set and push the update.
+ /// Add a member to a remote's set and push the update.
Add {
- /// Remote whose `refs/meta/auth` to update.
+ /// Remote whose `refs/meta/members` to update.
#[arg(default_value = "origin")]
remote: String,
/// Key to authorize; defaults to `user.signingkey`.
#[arg(long)]
key: Option<PathBuf>,
},
- /// Remove a signer from a remote's set and push the update.
+ /// Remove a member from a remote's set and push the update.
Remove {
- /// Fingerprint (`signers/<name>`) to drop.
+ /// Fingerprint (`members/<name>`) to drop.
fingerprint: String,
- /// Remote whose `refs/meta/auth` to update.
+ /// Remote whose `refs/meta/members` to update.
#[arg(default_value = "origin")]
remote: String,
},
- /// Report whether a key is authorized and the client is configured.
+ /// Report whether a key is a member and the client is configured.
Check {
- /// Remote to read `refs/meta/auth` from.
+ /// Remote to read `refs/meta/members` from.
#[arg(default_value = "origin")]
remote: String,
/// Key to look for; defaults to `user.signingkey`.
@@ -112,7 +111,7 @@
fn main() -> ExitCode {
let cli = Cli::parse();
let result = match cli.command {
- Top::Auth { action } => run_auth(action),
+ Top::Members { action } => run_members(action),
Top::Checks { action } => run_checks(action),
};
match result {
@@ -124,7 +123,7 @@
}
}
-fn run_auth(action: Action) -> Result<(), String> {
+fn run_members(action: Action) -> Result<(), String> {
match action {
Action::Setup { key, local } => setup(key.as_deref(), local),
Action::List { remote } => list::<Signers>(&remote),
@@ -171,12 +170,12 @@
fn row_value(key: &str, value: &str) -> String;
}
-/// The authorized signer set at `refs/meta/auth`.
+/// The repository member set at `refs/meta/members`.
struct Signers;
impl Set for Signers {
- const REF: &'static str = AUTH_REF;
- const NOUN: &'static str = "signer";
+ const REF: &'static str = MEMBERS_REF;
+ const NOUN: &'static str = "member";
fn load(repo: &Path) -> Result<Vec<(String, String)>, String> {
Ok(signers::load(repo)
@@ -198,7 +197,7 @@
}
fn empty_listing(remote: &str) -> String {
- format!("no authorized signers on {remote} (open bootstrap window)")
+ format!("no members on {remote} (open bootstrap window)")
}
fn row_value(_key: &str, value: &str) -> String {
@@ -314,7 +313,7 @@
scope.trim_start_matches('-')
);
println!("signing key: {signing_key} ({fingerprint})");
- println!("authorize it on a server with `git ents auth add <remote>`");
+ println!("authorize it on a server with `git ents members add <remote>`");
Ok(())
}
@@ -440,13 +439,13 @@
let repo = repo()?;
let public_key = public_key(key)?;
let fingerprint = fingerprint(&public_key)?;
- let expected = sync(remote, AUTH_REF)?;
+ let expected = sync(remote, MEMBERS_REF)?;
let mut signers = signers::load(&repo).map_err(|error| error.to_string())?;
if signers
.iter()
.any(|signer| same_key(&signer.key, &public_key))
{
- println!("{fingerprint} is already authorized");
+ println!("{fingerprint} is already a member");
return Ok(());
}
signers.push(Signer {
@@ -454,7 +453,7 @@
key: public_key,
});
signers::store(&repo, &signers).map_err(|error| error.to_string())?;
- push_signed(remote, AUTH_REF, expected.as_deref())?;
+ push_signed(remote, MEMBERS_REF, expected.as_deref())?;
println!("authorized {fingerprint}");
Ok(())
}
@@ -464,17 +463,17 @@
let repo = repo()?;
let public_key = public_key(key)?;
let fingerprint = fingerprint(&public_key)?;
- sync(remote, AUTH_REF)?;
+ sync(remote, MEMBERS_REF)?;
let signers = signers::load(&repo).map_err(|error| error.to_string())?;
if signers.is_empty() {
- println!("{remote}: open bootstrap window (no signers yet)");
+ println!("{remote}: open bootstrap window (no members yet)");
} else if signers
.iter()
.any(|signer| same_key(&signer.key, &public_key))
{
- println!("{remote}: {fingerprint} is authorized");
+ println!("{remote}: {fingerprint} is a member");
} else {
- println!("{remote}: {fingerprint} is NOT authorized");
+ println!("{remote}: {fingerprint} is NOT a member");
}
println!(
"client: gpg.format={}, user.signingkey={}, push.gpgSign={}",
@@ -587,7 +586,7 @@
/// The key's MD5 fingerprint in colon form (`aa:bb:…`). Colon-separated pairs
/// are filesystem-safe, unlike the slashes in a base64 SHA256 fingerprint that
-/// would split the `signers/<name>` entry into a subtree.
+/// would split the `members/<name>` entry into a subtree.
fn fingerprint(public_key: &str) -> Result<String, String> {
let scratch =
tempfile::tempdir().map_err(|error| format!("could not create temp dir: {error}"))?;
crates/git-ents/src/signers.rs
@@ -1,37 +1,37 @@
-//! The authorized signer set, sourced from the `refs/meta/auth` ref.
+//! The repository's members, sourced from the `refs/meta/members` ref.
//!
-//! Push authentication trusts exactly one place: the `refs/meta/auth` ref. Its
-//! tree is an [`Auth`] document mapping each fingerprint to its OpenSSH public
-//! key. The document is read and written through [`git_store`], so the trust
-//! list is a typed value that lives in git — versioned, auditable, and itself
-//! pushable.
+//! Push authentication trusts exactly one place: the `refs/meta/members` ref.
+//! Its tree is a [`Members`] document mapping each fingerprint to its OpenSSH
+//! public key. A member *is* one or more keys whose signed pushes are accepted.
+//! The document is read and written through [`git_store`], so the trust list is
+//! a typed value that lives in git — versioned, auditable, and itself pushable.
use std::collections::BTreeMap;
use std::path::Path;
use facet::Facet;
-/// The ref whose tree holds the authorized signer set.
-pub const AUTH_REF: &str = "refs/meta/auth";
+/// The ref whose tree holds the member set — the push trust root.
+pub const MEMBERS_REF: &str = "refs/meta/members";
-/// The authorization document stored at [`AUTH_REF`]: its `signers/` subtree
+/// The membership document stored at [`MEMBERS_REF`]: its `members/` subtree
/// maps each fingerprint to the OpenSSH public key held there.
#[derive(Debug, Clone, PartialEq, Eq, Facet)]
-struct Auth {
- signers: BTreeMap<String, String>,
+struct Members {
+ members: BTreeMap<String, String>,
}
-impl git_store::MapDoc for Auth {
+impl git_store::MapDoc for Members {
fn from_entries(entries: BTreeMap<String, String>) -> Self {
- Self { signers: entries }
+ Self { members: entries }
}
fn into_entries(self) -> BTreeMap<String, String> {
- self.signers
+ self.members
}
}
-/// One authorized signer recorded in [`AUTH_REF`].
+/// One member's authorized signing key recorded in [`MEMBERS_REF`].
#[derive(Debug, Clone, PartialEq, Eq, Facet)]
pub struct Signer {
/// The key it is stored under — its fingerprint.
@@ -40,22 +40,22 @@
pub key: String,
}
-/// A failure reading or writing the signer set.
+/// A failure reading or writing the member set.
#[derive(Debug, thiserror::Error)]
pub enum Error {
- /// The signer set could not be read from or written to its ref.
+ /// The member set could not be read from or written to its ref.
#[error(transparent)]
Store(#[from] git_store::Error),
}
-/// Load the authorized signers recorded at [`AUTH_REF`] in `repo`.
+/// Load the members recorded at [`MEMBERS_REF`] in `repo`.
///
/// An absent ref yields an empty set, as on a fresh server whose trust list has
/// not been pushed yet. A present but unreadable ref is an error so callers can
-/// fail closed rather than mistake corruption for "no signers".
+/// fail closed rather than mistake corruption for "no members".
pub fn load(repo: &Path) -> Result<Vec<Signer>, Error> {
Ok(git_store::Store::open(repo)?
- .load_entries::<Auth>(AUTH_REF)?
+ .load_entries::<Members>(MEMBERS_REF)?
.into_iter()
.map(|(fingerprint, key)| Signer {
fingerprint,
@@ -64,16 +64,17 @@
.collect())
}
-/// Write `signers` to [`AUTH_REF`], replacing any existing set, as a new commit.
+/// Write `signers` to [`MEMBERS_REF`], replacing any existing set, as a new
+/// commit.
pub fn store(repo: &Path, signers: &[Signer]) -> Result<(), Error> {
let entries = signers
.iter()
.map(|signer| (signer.fingerprint.clone(), signer.key.clone()))
.collect();
- git_store::Store::open(repo)?.store_entries::<Auth>(
- AUTH_REF,
+ git_store::Store::open(repo)?.store_entries::<Members>(
+ MEMBERS_REF,
entries,
- "Update authorized signers",
+ "Update members",
)?;
Ok(())
}
@@ -142,22 +143,22 @@
}
#[test]
- fn empty_when_the_auth_ref_is_absent() {
+ fn empty_when_the_members_ref_is_absent() {
let repo = unique_repo();
assert!(load(&repo).unwrap().is_empty());
let _ = std::fs::remove_dir_all(&repo);
}
#[test]
- fn loads_the_on_disk_signers_format() {
- // A fixture written as the real `signers/<fingerprint>` blob layout must
- // keep loading; this fails if the Auth document's shape changes
+ fn loads_the_on_disk_members_format() {
+ // A fixture written as the real `members/<fingerprint>` blob layout must
+ // keep loading; this fails if the Members document's shape changes
// incompatibly with data already on a ref.
let repo = unique_repo();
write_meta_doc(
&repo,
- AUTH_REF,
- "signers",
+ MEMBERS_REF,
+ "members",
&[("aa:bb:cc", KEY_A), ("dd:ee:ff", KEY_B)],
);
let mut loaded = load(&repo).unwrap();
crates/git-ents-server/src/web/pages.rs
@@ -814,18 +814,18 @@
div.card {
div.card-header {
- "Authorized signers"
+ "Members"
@if let Ok(signers) = &signers { span.count { (signers.len()) } }
}
p.shell-note {
- "Keys on " code { "refs/meta/auth" } " whose signed pushes are accepted "
- "(" code { "git ents auth list" } ")."
+ "Keys on " code { "refs/meta/members" } " whose signed pushes are accepted "
+ "(" code { "git ents members list" } ")."
}
@match &signers {
- Err(err) => div.card-row.muted { "Could not read signers: " (err) }
+ Err(err) => div.card-row.muted { "Could not read members: " (err) }
Ok(signers) if signers.is_empty() => {
div.card-row.muted {
- "No authorized signers — pushes are open until the first key is added."
+ "No members — pushes are open until the first key is added."
}
}
Ok(signers) => {