use std::io::{Read, Write};
use std::path::Path;
use clap::ValueEnum;
use pointbreak::crypto::EventSigner;
use pointbreak::keys::{
AgentUnavailable, FileEd25519Signer, KeyHandle, KeyMaterial, KeyName, generate_key,
generate_key_in, load_key_material, load_key_material_in, load_signer, load_signer_from_path,
load_signer_in, preflight_ssh_agent_signer,
};
use pointbreak::model::{ActorId, Side};
use pointbreak::session::{
ActorAttributesMap, AssessmentAddOptions, AssociateCommitOptions, AssociateRefOptions,
BestEffortSkipSink, BodyContentType, CaptureOptions, CurrentAssessmentStatus, DelegationMap,
InputRequestOpenOptions, InputRequestRespondOptions, ObservationAddOptions, RemoveOptions,
TrustSet, ValidationAddOptions, WithdrawCommitOptions, WithdrawRefOptions, is_agent_actor_id,
resolve_writer_actor_id,
};
pub(crate) fn clamp_title(title: &str) -> String {
const MAX: usize = 72;
let flattened = title.split_whitespace().collect::<Vec<_>>().join(" ");
if flattened.chars().count() <= MAX {
return flattened;
}
let clamped: String = flattened.chars().take(MAX - 1).collect();
format!("{clamped}…")
}
pub(crate) fn current_call_line(status: &CurrentAssessmentStatus) -> String {
match status {
CurrentAssessmentStatus::Unassessed => "current call: none recorded".to_owned(),
CurrentAssessmentStatus::Resolved(assessment) => format!(
"current call: {} (advisory — a recorded judgement, not a merge gate)",
assessment.display_label(),
),
CurrentAssessmentStatus::Ambiguous(candidates) => {
format!("current call: ambiguous ({} candidates)", candidates.len())
}
}
}
pub(crate) fn discover_delegation_map(repo: &Path) -> Option<DelegationMap> {
let worktree_root =
pointbreak::git::git_worktree_root(repo).unwrap_or_else(|_| repo.to_path_buf());
let committed = load_optional_delegates(&worktree_root.join(".shore/delegates.json"));
let local = load_optional_delegates(&worktree_root.join(".shore/delegates.local.json"));
match (committed, local) {
(None, None) => None,
(committed, local) => Some(
committed
.unwrap_or_default()
.with_local_override(local.unwrap_or_default()),
),
}
}
fn load_optional_delegates(path: &Path) -> Option<DelegationMap> {
if !path.exists() {
return None;
}
match DelegationMap::from_delegates_file(path) {
Ok(map) => Some(map),
Err(error) => {
eprintln!("warning: ignoring {}: {error}", path.display());
None
}
}
}
pub(crate) fn discover_actor_attributes(repo: &Path) -> Option<ActorAttributesMap> {
let worktree_root =
pointbreak::git::git_worktree_root(repo).unwrap_or_else(|_| repo.to_path_buf());
let committed =
load_optional_actor_attributes(&worktree_root.join(".shore/actor-attributes.json"));
let local =
load_optional_actor_attributes(&worktree_root.join(".shore/actor-attributes.local.json"));
match (committed, local) {
(None, None) => None,
(committed, local) => Some(
committed
.unwrap_or_default()
.with_local_override(local.unwrap_or_default()),
),
}
}
fn load_optional_actor_attributes(path: &Path) -> Option<ActorAttributesMap> {
if !path.exists() {
return None;
}
match ActorAttributesMap::from_attributes_file(path) {
Ok(map) => Some(map),
Err(error) => {
eprintln!("warning: ignoring {}: {error}", path.display());
None
}
}
}
pub(crate) fn discover_trust_set(repo: &Path) -> TrustSet {
let worktree_root =
pointbreak::git::git_worktree_root(repo).unwrap_or_else(|_| repo.to_path_buf());
load_optional_trust_set(&worktree_root.join(".shore/allowed-signers.json")).unwrap_or_default()
}
fn load_optional_trust_set(path: &Path) -> Option<TrustSet> {
if !path.exists() {
return None;
}
match TrustSet::from_allowed_signers_file(path) {
Ok(trust) => Some(trust),
Err(error) => {
eprintln!("warning: ignoring {}: {error}", path.display());
None
}
}
}
const SHORE_SIGNING_ENV: &str = "SHORE_SIGNING";
const SHORE_SIGNING_KEY_ENV: &str = "SHORE_SIGNING_KEY";
const SIGNING_KEY_UNREADABLE: &str = "signing_key_unreadable";
const SIGNING_KEY_UNSUPPORTED_ALGORITHM: &str = "signing_key_unsupported_algorithm";
const SIGNING_KEY_HOME_UNREADABLE: &str = "signing_key_home_unreadable";
const SIGNING_MODE_UNRECOGNIZED: &str = "signing_mode_unrecognized";
const SIGNING_AGENT_UNAVAILABLE: &str = "signing_agent_unavailable";
const SIGNING_AGENT_KEY_ABSENT: &str = "signing_agent_key_absent";
const SIGNING_AGENT_SIGN_FAILED: &str = "signing_agent_sign_failed";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SigningMode {
Strict,
BestEffort,
}
pub(crate) struct SignerResolution {
pub(crate) signer: Option<Box<dyn EventSigner + Send + Sync>>,
pub(crate) mode: SigningMode,
pub(crate) diagnostic: Option<String>,
}
impl SignerResolution {
fn strict(
signer: Option<Box<dyn EventSigner + Send + Sync>>,
diagnostic: Option<String>,
) -> Self {
Self {
signer,
mode: SigningMode::Strict,
diagnostic,
}
}
fn best_effort(signer: Box<dyn EventSigner + Send + Sync>, diagnostic: Option<String>) -> Self {
Self {
signer: Some(signer),
mode: SigningMode::BestEffort,
diagnostic,
}
}
}
pub(crate) struct ResolvedSigner {
pub(crate) signer: Box<dyn EventSigner + Send + Sync>,
pub(crate) mode: SigningMode,
}
pub(crate) fn resolve_signer(
repo: &Path,
actor: &ActorId,
sign_key: Option<&str>,
) -> SignerResolution {
resolve_signer_with_env(
repo,
actor,
sign_key,
std::env::var(SHORE_SIGNING_ENV).ok().as_deref(),
std::env::var(SHORE_SIGNING_KEY_ENV).ok().as_deref(),
None, )
}
pub(crate) fn resolve_and_surface_signer(
repo: &Path,
sign_key: Option<&str>,
stderr: &mut dyn Write,
) -> Option<ResolvedSigner> {
let actor = resolve_writer_actor_id(repo, None);
let resolution = resolve_signer(repo, &actor, sign_key);
if let Some(diagnostic) = resolution.diagnostic.as_deref() {
let _ = writeln!(stderr, "{diagnostic}");
}
let mode = resolution.mode;
resolution
.signer
.map(|signer| ResolvedSigner { signer, mode })
}
pub(crate) type SigningSkip = Option<BestEffortSkipSink>;
pub(crate) fn apply_resolved_signer<O: SignableOptions>(
options: O,
resolved: ResolvedSigner,
) -> (O, SigningSkip) {
match resolved.mode {
SigningMode::Strict => (options.sign_with(resolved.signer), None),
SigningMode::BestEffort => {
let skip: BestEffortSkipSink = std::sync::Arc::new(std::sync::Mutex::new(None));
(
options.sign_with_best_effort(resolved.signer, skip.clone()),
Some(skip),
)
}
}
}
pub(crate) fn surface_best_effort_skip(skip: &SigningSkip, stderr: &mut dyn Write) {
if let Some(skip) = skip
&& let Ok(slot) = skip.lock()
&& let Some(reason) = slot.as_deref()
{
let _ = writeln!(stderr, "{SIGNING_AGENT_SIGN_FAILED}: {reason}");
}
}
pub(crate) trait SignableOptions {
fn sign_with(self, signer: Box<dyn EventSigner + Send + Sync>) -> Self;
fn sign_with_best_effort(
self,
signer: Box<dyn EventSigner + Send + Sync>,
skip: BestEffortSkipSink,
) -> Self;
}
macro_rules! impl_signable_options {
($($ty:ty),+ $(,)?) => {$(
impl SignableOptions for $ty {
fn sign_with(self, signer: Box<dyn EventSigner + Send + Sync>) -> Self {
<$ty>::sign_with(self, signer)
}
fn sign_with_best_effort(
self,
signer: Box<dyn EventSigner + Send + Sync>,
skip: BestEffortSkipSink,
) -> Self {
<$ty>::sign_with_best_effort(self, signer, skip)
}
}
)+};
}
impl_signable_options!(
CaptureOptions,
ObservationAddOptions,
ValidationAddOptions,
InputRequestOpenOptions,
InputRequestRespondOptions,
AssessmentAddOptions,
AssociateCommitOptions,
WithdrawCommitOptions,
AssociateRefOptions,
WithdrawRefOptions,
RemoveOptions,
);
fn resolve_signer_with_env(
repo: &Path,
actor: &ActorId,
sign_key: Option<&str>,
shore_signing: Option<&str>,
shore_signing_key: Option<&str>,
keys_root: Option<&Path>,
) -> SignerResolution {
if let Some(mode) = shore_signing
&& mode.eq_ignore_ascii_case("off")
{
return SignerResolution::strict(None, None);
}
if let Some(candidate) = [sign_key, shore_signing_key].into_iter().flatten().next() {
if let Some(resolution) =
resolve_agent_backed_reference(candidate, keys_root, shore_signing)
{
return resolution;
}
return match load_configured_signer(candidate, keys_root) {
Ok(signer) => {
SignerResolution::strict(Some(Box::new(signer)), mode_note(shore_signing))
}
Err(diagnostic) => SignerResolution::strict(None, Some(diagnostic)),
};
}
if is_agent_actor_id(actor.as_str())
&& let Some(resolution) = resolve_agent_signer(repo, actor, keys_root)
{
return resolution;
}
if let Some(resolution) = resolve_agent_backed_reference("default", keys_root, shore_signing) {
return resolution;
}
SignerResolution::strict(
load_default_signer(keys_root)
.map(|signer| Box::new(signer) as Box<dyn EventSigner + Send + Sync>),
mode_note(shore_signing),
)
}
fn resolve_agent_backed_reference(
candidate: &str,
keys_root: Option<&Path>,
shore_signing: Option<&str>,
) -> Option<SignerResolution> {
if candidate_is_path(candidate) {
return None;
}
match load_key_material_opt(keys_root, candidate)? {
KeyMaterial::AgentBacked { public_key } => {
Some(match resolve_agent_backed_signer(public_key) {
Ok(signer) => SignerResolution::best_effort(signer, mode_note(shore_signing)),
Err(diagnostic) => SignerResolution::strict(None, Some(diagnostic)),
})
}
KeyMaterial::Seed(_) => None,
}
}
fn load_key_material_opt(keys_root: Option<&Path>, name: &str) -> Option<KeyMaterial> {
match keys_root {
Some(root) => load_key_material_in(root, name),
None => load_key_material(name),
}
.ok()
}
fn resolve_agent_backed_signer(
public_key: [u8; 32],
) -> std::result::Result<Box<dyn EventSigner + Send + Sync>, String> {
match preflight_ssh_agent_signer(public_key) {
Ok(signer) => Ok(Box::new(signer)),
Err(unavailable) => Err(map_agent_unavailable(unavailable)),
}
}
fn map_agent_unavailable(unavailable: AgentUnavailable) -> String {
match unavailable {
AgentUnavailable::Socket => {
format!(
"{SIGNING_AGENT_UNAVAILABLE}: ssh-agent unavailable (no/unreachable $SSH_AUTH_SOCK)"
)
}
AgentUnavailable::KeyAbsent => {
format!("{SIGNING_AGENT_KEY_ABSENT}: the configured key is not loaded in ssh-agent")
}
}
}
fn candidate_is_path(candidate: &str) -> bool {
candidate.contains('/')
|| candidate.contains(std::path::MAIN_SEPARATOR)
|| Path::new(candidate).is_file()
}
fn load_configured_signer(
candidate: &str,
keys_root: Option<&Path>,
) -> std::result::Result<FileEd25519Signer, String> {
let path = Path::new(candidate);
let loaded = if candidate_is_path(candidate) {
load_signer_from_path(path)
} else {
match keys_root {
Some(root) => load_signer_in(root, candidate),
None => load_signer(candidate),
}
};
loaded.map_err(|error| classify_signing_key_error(&error.to_string(), candidate))
}
fn classify_signing_key_error(message: &str, candidate: &str) -> String {
if message.contains("unsupported key algorithm") {
format!("{SIGNING_KEY_UNSUPPORTED_ALGORITHM}: signing key {candidate:?}: {message}")
} else {
format!("{SIGNING_KEY_UNREADABLE}: signing key {candidate:?}: {message}")
}
}
fn load_default_signer(keys_root: Option<&Path>) -> Option<FileEd25519Signer> {
match keys_root {
Some(root) => load_signer_in(root, "default").ok(),
None => load_signer("default").ok(),
}
}
fn resolve_agent_signer(
_repo: &Path,
actor: &ActorId,
keys_root: Option<&Path>,
) -> Option<SignerResolution> {
let name = agent_key_name(actor.as_str());
if let Ok(signer) = load_agent_key(keys_root, &name) {
return Some(SignerResolution::strict(Some(Box::new(signer)), None));
}
match generate_agent_key(keys_root, &name) {
Ok(handle) => {
let did = handle.signer_id().as_str().to_owned();
match load_agent_key(keys_root, &name) {
Ok(signer) => Some(SignerResolution::strict(
Some(Box::new(signer)),
Some(format!(
"shore: generated signing key for {} ({did}); \
run `shore key enroll` to stage trust",
actor.as_str()
)),
)),
Err(_) => Some(SignerResolution::strict(
None,
Some(SIGNING_KEY_HOME_UNREADABLE.to_owned()),
)),
}
}
Err(_) => Some(SignerResolution::strict(
None,
Some(SIGNING_KEY_HOME_UNREADABLE.to_owned()),
)),
}
}
fn agent_key_name(actor: &str) -> String {
let slug = actor.strip_prefix("actor:agent:").unwrap_or(actor);
let mut safe = String::with_capacity(slug.len());
let mut last_dash = false;
for character in slug.chars() {
if character.is_ascii_alphanumeric() || character == '_' {
safe.push(character);
last_dash = false;
} else if !last_dash {
safe.push('-');
last_dash = true;
}
}
format!("agent-{}", safe.trim_matches('-'))
}
fn load_agent_key(
keys_root: Option<&Path>,
name: &str,
) -> std::result::Result<FileEd25519Signer, String> {
let loaded = match keys_root {
Some(root) => load_signer_in(root, name),
None => load_signer(name),
};
loaded.map_err(|error| error.to_string())
}
fn generate_agent_key(
keys_root: Option<&Path>,
name: &str,
) -> std::result::Result<KeyHandle, String> {
let key_name = KeyName::parse(name).map_err(|error| error.to_string())?;
let generated = match keys_root {
Some(root) => generate_key_in(root, &key_name),
None => generate_key(name),
};
generated.map_err(|error| error.to_string())
}
fn mode_note(shore_signing: Option<&str>) -> Option<String> {
match shore_signing {
Some(mode) if mode.eq_ignore_ascii_case("auto") || mode.eq_ignore_ascii_case("off") => None,
Some(other) => Some(format!(
"{SIGNING_MODE_UNRECOGNIZED}: {other:?} treated as auto"
)),
None => None,
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub(super) enum SideArg {
Old,
New,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub(super) enum ContentTypeArg {
#[value(name = "text/plain")]
TextPlain,
#[value(name = "text/markdown")]
TextMarkdown,
}
pub(crate) fn read_body_input(
inline: Option<&str>,
file: Option<&Path>,
stdin: bool,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
if let Some(inline) = inline {
return Ok(Some(inline.to_owned()));
}
if let Some(path) = file {
return Ok(Some(std::fs::read_to_string(path)?));
}
if stdin {
let mut body = String::new();
std::io::stdin().read_to_string(&mut body)?;
return Ok(Some(body));
}
Ok(None)
}
impl From<SideArg> for Side {
fn from(value: SideArg) -> Self {
match value {
SideArg::Old => Side::Old,
SideArg::New => Side::New,
}
}
}
impl From<ContentTypeArg> for BodyContentType {
fn from(value: ContentTypeArg) -> Self {
match value {
ContentTypeArg::TextPlain => Self::TextPlain,
ContentTypeArg::TextMarkdown => Self::TextMarkdown,
}
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use std::process::Command;
use pointbreak::model::ActorId;
use pointbreak::session::PrincipalResolution;
const COMMITTED: &str = r#"{"delegates":{"actor:agent:claude-code":[
{"principal":"actor:git-email:kevin@swiber.dev","validFrom":"2026-06-10T00:00:00Z","validUntil":null}]}}"#;
const LOCAL: &str = r#"{"delegates":{"actor:agent:claude-code":[
{"principal":"actor:git-email:alice@example.com","validFrom":"2026-06-10T00:00:00Z","validUntil":null}]}}"#;
#[test]
fn read_body_input_prefers_inline_then_file_then_stdin_false() {
let dir = tempfile::tempdir().expect("create tempdir");
let body_path = dir.path().join("body.txt");
std::fs::write(&body_path, "from file").expect("write body file");
let body = super::read_body_input(Some("from inline"), Some(&body_path), false)
.expect("body input resolves");
assert_eq!(body, Some("from inline".to_string()));
}
#[test]
fn discovers_committed_delegates_json() {
let repo = git_repo();
write(&repo, ".shore/delegates.json", COMMITTED);
let map = super::discover_delegation_map(repo.path()).expect("committed map discovered");
assert!(matches!(
map.resolve(&ActorId::new("actor:agent:claude-code"), "2026-06-12T00:00:00Z"),
PrincipalResolution::Resolved(p) if p.as_str() == "actor:git-email:kevin@swiber.dev"));
}
#[test]
fn local_override_layers_over_committed() {
let repo = git_repo();
write(&repo, ".shore/delegates.json", COMMITTED);
write(&repo, ".shore/delegates.local.json", LOCAL);
let map = super::discover_delegation_map(repo.path()).expect("layered map");
assert!(matches!(
map.resolve(&ActorId::new("actor:agent:claude-code"), "2026-06-12T00:00:00Z"),
PrincipalResolution::Resolved(p) if p.as_str() == "actor:git-email:alice@example.com"));
}
#[test]
fn local_alone_is_used_when_committed_absent() {
let repo = git_repo();
write(&repo, ".shore/delegates.local.json", LOCAL);
assert!(super::discover_delegation_map(repo.path()).is_some());
}
#[test]
fn neither_file_present_returns_none() {
let repo = git_repo();
assert!(super::discover_delegation_map(repo.path()).is_none());
}
const ALLOWED: &str = r#"{"allowedSigners":{
"actor:git-email:alice@example.com":["did:key:z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd"]}}"#;
#[test]
fn discover_trust_set_loads_committed_allowed_signers() {
use pointbreak::crypto::SignerId;
let repo = git_repo();
write(&repo, ".shore/allowed-signers.json", ALLOWED);
let trust = super::discover_trust_set(repo.path());
let actor = ActorId::new("actor:git-email:alice@example.com");
let signer =
SignerId::parse("did:key:z6MkehRgf7yJbgaGfYsdoAsKdBPE3dj2CYhowQdcjqSJgvVd").unwrap();
assert!(trust.authorizes(&actor, &signer, "2026-06-16T00:00:00Z"));
}
#[test]
fn discover_trust_set_absent_file_is_empty_default_without_error() {
let repo = git_repo();
let trust = super::discover_trust_set(repo.path());
assert_eq!(trust, pointbreak::session::TrustSet::default());
}
#[test]
fn discover_trust_set_malformed_is_advisory_and_falls_back_to_default() {
let repo = git_repo();
write(&repo, ".shore/allowed-signers.json", "{ not json");
let trust = super::discover_trust_set(repo.path());
assert_eq!(trust, pointbreak::session::TrustSet::default());
}
#[test]
fn malformed_local_is_advisory_and_falls_back_to_committed() {
let repo = git_repo();
write(&repo, ".shore/delegates.json", COMMITTED);
write(&repo, ".shore/delegates.local.json", "{ not json");
let map =
super::discover_delegation_map(repo.path()).expect("committed survives bad local");
assert!(matches!(
map.resolve(&ActorId::new("actor:agent:claude-code"), "2026-06-12T00:00:00Z"),
PrincipalResolution::Resolved(p) if p.as_str() == "actor:git-email:kevin@swiber.dev"));
}
const ATTRS: &str = r#"{"schema":"shore.actor-attributes.v1","actors":{
"actor:git-email:kevin@swiber.dev":{"kind":"human","roles":["reviewer"]}}}"#;
const ATTRS_LOCAL: &str = r#"{"schema":"shore.actor-attributes.v1","actors":{
"actor:git-email:kevin@swiber.dev":{"kind":"agent","roles":[]}}}"#;
#[test]
fn discovers_committed_actor_attributes() {
let repo = git_repo();
write(&repo, ".shore/actor-attributes.json", ATTRS);
let map = super::discover_actor_attributes(repo.path()).expect("committed map discovered");
assert_eq!(
map.resolve(&ActorId::new("actor:git-email:kevin@swiber.dev"))
.kind(),
Some("human")
);
}
#[test]
fn actor_attributes_local_override_layers_over_committed() {
let repo = git_repo();
write(&repo, ".shore/actor-attributes.json", ATTRS);
write(&repo, ".shore/actor-attributes.local.json", ATTRS_LOCAL);
let map = super::discover_actor_attributes(repo.path()).expect("layered map");
assert_eq!(
map.resolve(&ActorId::new("actor:git-email:kevin@swiber.dev"))
.kind(),
Some("agent")
);
}
#[test]
fn actor_attributes_neither_file_returns_none() {
let repo = git_repo();
assert!(super::discover_actor_attributes(repo.path()).is_none());
}
#[test]
fn malformed_actor_attributes_local_is_advisory_and_falls_back_to_committed() {
let repo = git_repo();
write(&repo, ".shore/actor-attributes.json", ATTRS);
write(&repo, ".shore/actor-attributes.local.json", "{ not json");
let map =
super::discover_actor_attributes(repo.path()).expect("committed survives bad local");
assert_eq!(
map.resolve(&ActorId::new("actor:git-email:kevin@swiber.dev"))
.kind(),
Some("human")
);
}
fn git_repo() -> tempfile::TempDir {
let repo = tempfile::tempdir().expect("create temp git repository directory");
let output = Command::new("git")
.arg("init")
.current_dir(repo.path())
.output()
.expect("run git init");
assert!(output.status.success(), "git init failed");
repo
}
fn write(repo: &tempfile::TempDir, rel: &str, contents: &str) {
let path = repo.path().join(rel);
if let Some(parent) = Path::new(&path).parent() {
std::fs::create_dir_all(parent).expect("create parent dirs");
}
std::fs::write(path, contents).expect("write file");
}
}
#[cfg(test)]
mod resolve_signer_tests {
use std::path::Path;
use pointbreak::crypto::EventSigner as _;
use pointbreak::keys::{KeyName, generate_key_in};
use pointbreak::model::ActorId;
use super::resolve_signer_with_env;
fn human_actor() -> ActorId {
ActorId::new("actor:git-email:dev@example.com")
}
fn agent_actor() -> ActorId {
ActorId::new("actor:agent:claude-code")
}
fn repo() -> &'static Path {
Path::new(".")
}
fn generate_into(root: &Path, name: &str) -> String {
let handle = generate_key_in(root, &KeyName::parse(name).unwrap()).unwrap();
handle.signer_id().as_str().to_owned()
}
#[test]
fn a_boxed_signer_round_trips_through_sign_with_and_signs() {
use pointbreak::crypto::{EventVerificationStatus, verify_ed25519_strict};
use pointbreak::session::EventSigningOptions;
use pointbreak::session::event::{
EVENT_TO_BE_SIGNED_V1_PAYLOAD_TYPE, pre_authentication_encoding,
};
let root = tempfile::tempdir().unwrap();
let did = generate_into(root.path(), "default");
let resolution =
resolve_signer_with_env(repo(), &human_actor(), None, None, None, Some(root.path()));
let boxed: Box<dyn pointbreak::crypto::EventSigner + Send + Sync> = resolution
.signer
.expect("default key resolves as a boxed signer");
assert_eq!(boxed.signer_id().as_str(), did);
let message = pre_authentication_encoding(
EVENT_TO_BE_SIGNED_V1_PAYLOAD_TYPE,
br#"{"schema":"shore.event","version":1}"#,
);
let signature = boxed.sign_event_message(&message).unwrap();
assert_eq!(
verify_ed25519_strict(boxed.signer_id(), &message, signature.as_str()).unwrap(),
EventVerificationStatus::Valid,
"a boxed file signer signs and verifies identically to the bare signer"
);
let _options = EventSigningOptions::sign_with(boxed);
}
fn write_agent_into(root: &Path, name: &str) -> String {
use pointbreak::keys::{KeyName, write_agent_reference_in};
write_agent_reference_in(root, &KeyName::parse(name).unwrap(), [7_u8; 32])
.unwrap()
.signer_id()
.as_str()
.to_owned()
}
fn with_dead_auth_sock(path: &str, body: impl FnOnce()) {
unsafe { std::env::set_var("SSH_AUTH_SOCK", path) };
body();
unsafe { std::env::remove_var("SSH_AUTH_SOCK") };
}
#[test]
fn agent_backed_default_with_no_agent_degrades_to_none_unavailable() {
let root = tempfile::tempdir().unwrap();
write_agent_into(root.path(), "default");
with_dead_auth_sock("/nonexistent/shore-test-default.sock", || {
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
None,
None,
Some(root.path()),
);
assert!(resolution.signer.is_none());
assert!(
resolution
.diagnostic
.as_deref()
.is_some_and(|d| d.contains("signing_agent_unavailable")),
"diagnostic: {:?}",
resolution.diagnostic
);
});
}
#[test]
fn explicit_agent_key_that_fails_preflight_is_terminal_no_fall_through() {
let root = tempfile::tempdir().unwrap();
write_agent_into(root.path(), "agentref");
let default_did = generate_into(root.path(), "default"); with_dead_auth_sock("/nonexistent/shore-test-explicit.sock", || {
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
None,
Some("agentref"),
Some(root.path()),
);
assert!(
resolution.signer.is_none(),
"an explicit agent key that fails pre-flight is terminal"
);
let diagnostic = resolution.diagnostic.expect("a named diagnostic");
assert!(diagnostic.contains("signing_agent_unavailable"));
assert!(
!diagnostic.contains(&default_did),
"the default identity is never substituted"
);
});
}
#[test]
fn agent_unavailable_variants_map_to_their_diagnostics() {
use pointbreak::keys::AgentUnavailable;
assert!(
super::map_agent_unavailable(AgentUnavailable::Socket)
.contains("signing_agent_unavailable")
);
assert!(
super::map_agent_unavailable(AgentUnavailable::KeyAbsent)
.contains("signing_agent_key_absent")
);
}
#[test]
fn sign_key_flag_takes_precedence_over_env_and_default() {
let root = tempfile::tempdir().unwrap();
let flag_did = generate_into(root.path(), "flagkey");
let _env_did = generate_into(root.path(), "envkey");
let _default_did = generate_into(root.path(), "default");
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
Some("flagkey"),
None,
Some("envkey"),
Some(root.path()),
);
let signer = resolution.signer.expect("flag key resolves");
assert_eq!(signer.signer_id().as_str(), flag_did);
}
#[test]
fn env_signing_key_wins_over_default_when_no_flag() {
let root = tempfile::tempdir().unwrap();
let env_did = generate_into(root.path(), "envkey");
let _default_did = generate_into(root.path(), "default");
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
None,
Some("envkey"),
Some(root.path()),
);
let signer = resolution.signer.expect("env key resolves");
assert_eq!(signer.signer_id().as_str(), env_did);
}
#[test]
fn user_default_key_is_used_when_no_flag_and_no_env() {
let root = tempfile::tempdir().unwrap();
let default_did = generate_into(root.path(), "default");
let resolution =
resolve_signer_with_env(repo(), &human_actor(), None, None, None, Some(root.path()));
let signer = resolution.signer.expect("default key resolves");
assert_eq!(signer.signer_id().as_str(), default_did);
}
#[test]
fn no_key_anywhere_resolves_to_none_with_no_diagnostic() {
let root = tempfile::tempdir().unwrap();
let resolution =
resolve_signer_with_env(repo(), &human_actor(), None, None, None, Some(root.path()));
assert!(resolution.signer.is_none());
assert!(resolution.diagnostic.is_none());
}
#[test]
fn signing_off_short_circuits_to_none_even_with_a_default_key() {
let root = tempfile::tempdir().unwrap();
let _default_did = generate_into(root.path(), "default");
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
Some("off"),
None,
Some(root.path()),
);
assert!(resolution.signer.is_none());
}
#[test]
fn signing_off_is_case_insensitive() {
let root = tempfile::tempdir().unwrap();
let _default_did = generate_into(root.path(), "default");
for value in ["off", "OFF", "Off"] {
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
Some(value),
None,
Some(root.path()),
);
assert!(
resolution.signer.is_none(),
"SHORE_SIGNING={value} must disable signing"
);
}
}
#[test]
fn explicitly_selected_broken_key_does_not_fall_through_to_default() {
let root = tempfile::tempdir().unwrap();
let default_did = generate_into(root.path(), "default");
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
None,
Some("nonexistent"),
Some(root.path()),
);
assert!(
resolution.signer.is_none(),
"an explicitly selected broken key must not substitute the default identity"
);
let diagnostic = resolution.diagnostic.expect("a named diagnostic");
assert!(
diagnostic.contains("signing_key_unreadable"),
"diagnostic names the failure: {diagnostic}"
);
assert!(
!diagnostic.contains(&default_did),
"the default identity is never substituted"
);
}
#[test]
fn unsupported_algorithm_key_path_resolves_to_none_with_named_diagnostic() {
let root = tempfile::tempdir().unwrap();
let rsa_path = root.path().join("rsa-key");
std::fs::write(
&rsa_path,
r#"{"version":1,"alg":"rsa","seed":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="}"#,
)
.unwrap();
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
Some(rsa_path.to_str().unwrap()),
None,
None,
Some(root.path()),
);
assert!(resolution.signer.is_none());
assert!(
resolution
.diagnostic
.as_deref()
.is_some_and(|d| d.contains("signing_key_unsupported_algorithm")),
"diagnostic: {:?}",
resolution.diagnostic
);
}
#[test]
fn unrecognized_signing_mode_is_advisory_not_an_error() {
let root = tempfile::tempdir().unwrap();
let _default_did = generate_into(root.path(), "default");
let resolution = resolve_signer_with_env(
repo(),
&human_actor(),
None,
Some("banana"),
None,
Some(root.path()),
);
assert!(resolution.signer.is_some(), "still resolves the default");
assert!(
resolution
.diagnostic
.as_deref()
.is_some_and(|d| d.contains("signing_mode_unrecognized")),
"diagnostic: {:?}",
resolution.diagnostic
);
}
#[cfg(unix)]
#[test]
fn agent_actor_with_no_key_generates_a_0600_key_and_signs() {
use std::os::unix::fs::PermissionsExt as _;
let root = tempfile::tempdir().unwrap();
let resolution =
resolve_signer_with_env(repo(), &agent_actor(), None, None, None, Some(root.path()));
let signer = resolution
.signer
.expect("agent actor auto-keygens and signs");
assert!(!signer.signer_id().as_str().is_empty());
let key_path = root.path().join("agent-claude-code");
assert!(key_path.exists(), "the agent key file was generated");
let mode = std::fs::metadata(&key_path).unwrap().permissions().mode();
assert_eq!(mode & 0o777, 0o600, "auto-keygen key is private");
}
#[test]
fn agent_keygen_emits_enrollment_notice_on_first_write_only() {
let root = tempfile::tempdir().unwrap();
let first =
resolve_signer_with_env(repo(), &agent_actor(), None, None, None, Some(root.path()));
let notice = first
.diagnostic
.expect("first write carries the enrollment notice");
assert!(notice.contains("generated signing key"));
assert!(notice.contains("shore key enroll"));
let second =
resolve_signer_with_env(repo(), &agent_actor(), None, None, None, Some(root.path()));
assert!(
second.diagnostic.is_none(),
"the reused key emits no fresh notice"
);
}
#[test]
fn agent_key_is_reused_on_the_next_write_no_second_keygen() {
let root = tempfile::tempdir().unwrap();
let first =
resolve_signer_with_env(repo(), &agent_actor(), None, None, None, Some(root.path()));
let first_did = first
.signer
.expect("first keygen")
.signer_id()
.as_str()
.to_owned();
let second =
resolve_signer_with_env(repo(), &agent_actor(), None, None, None, Some(root.path()));
let second_did = second
.signer
.expect("reuse")
.signer_id()
.as_str()
.to_owned();
assert_eq!(first_did, second_did, "the same per-machine key is reused");
}
#[test]
fn human_actor_with_no_key_does_not_auto_keygen_and_stays_unsigned() {
let root = tempfile::tempdir().unwrap();
let resolution =
resolve_signer_with_env(repo(), &human_actor(), None, None, None, Some(root.path()));
assert!(resolution.signer.is_none());
let entries = std::fs::read_dir(root.path())
.map(|dir| dir.count())
.unwrap_or(0);
assert_eq!(entries, 0, "a human actor never auto-keygens");
}
#[test]
fn agent_keygen_failure_degrades_to_none_never_an_error() {
let file_root = tempfile::NamedTempFile::new().unwrap();
let resolution = resolve_signer_with_env(
repo(),
&agent_actor(),
None,
None,
None,
Some(file_root.path()),
);
assert!(
resolution.signer.is_none(),
"keygen failure must not yield a signer"
);
}
#[test]
fn actor_slug_maps_to_a_filesystem_safe_key_name() {
use pointbreak::keys::KeyName;
assert_eq!(
super::agent_key_name("actor:agent:claude-code"),
"agent-claude-code"
);
assert_eq!(
super::agent_key_name("actor:agent:weird/../name"),
"agent-weird-name"
);
for actor in ["actor:agent:claude-code", "actor:agent:weird/../name"] {
let name = super::agent_key_name(actor);
assert!(!name.contains('/'), "{name} has no path separator");
assert!(KeyName::parse(&name).is_ok(), "{name} is a valid key name");
}
}
}