pointbreak 0.6.0

Durable terminal code review for changes humans and coding agents collaborate on together
Documentation
use std::io::Write;
use std::path::{Path, PathBuf};

use clap::Args;
use pointbreak::crypto::SignerId;
use pointbreak::keys::{
    EnrollmentCandidate, EnrollmentCandidateSource, EnrollmentDiscoveryDiagnostic,
    EnrollmentDiscoveryDiagnosticCode, KeyCustody, agent_has_key, discover_enrollment_candidates,
    list_keys,
};
use pointbreak::model::ActorId;
use serde::Serialize;

use crate::cli::common::discover_trust_set;
use crate::cli::output;

#[derive(Debug, Args)]
pub(super) struct DiscoverArgs {
    /// Repository to inspect for advisory Git/OpenSSH signing evidence.
    #[arg(long, default_value = ".")]
    repo: PathBuf,

    #[command(flatten)]
    format_args: output::FormatArgs,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DiscoverDocument {
    schema: &'static str,
    version: u32,
    candidates: Vec<DiscoverCandidate>,
    diagnostics: Vec<EnrollmentDiscoveryDiagnostic>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DiscoverCandidate {
    id: String,
    source: EnrollmentCandidateSource,
    signer_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    key_argument: Option<String>,
    suggested_name: String,
    actor_hints: Vec<String>,
    local_keys: Vec<DiscoverLocalKey>,
    enrolled_actors: Vec<String>,
    resolved_actor: String,
    commands: Vec<Vec<String>>,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DiscoverLocalKey {
    name: String,
    default: bool,
    custody: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    agent_loaded: Option<bool>,
}

struct LocalKeyState {
    signer_id: SignerId,
    output: DiscoverLocalKey,
}

struct DiscoverRenderContext {
    resolved_actor: ActorId,
    trust_set: pointbreak::session::TrustSet,
    local_keys: Vec<LocalKeyState>,
}

pub(super) fn run(
    args: DiscoverArgs,
    stdout: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
    let discovery = discover_enrollment_candidates(&args.repo);
    let has_candidates = !discovery.candidates.is_empty();
    let context = DiscoverRenderContext::new(&args.repo);
    let candidates = discovery
        .candidates
        .into_iter()
        .map(|candidate| render_candidate(&args.repo, candidate, &context))
        .collect();
    let document = DiscoverDocument {
        schema: "pointbreak.key-discover",
        version: 1,
        candidates,
        diagnostics: filtered_diagnostics(discovery.diagnostics, has_candidates),
    };
    let format = output::resolve_format(args.format_args.explicit(), output::OutputFormat::Json)?;
    output::write_document_json_fallback(stdout, format, &document)
}

impl DiscoverRenderContext {
    fn new(repo: &Path) -> Self {
        Self {
            resolved_actor: pointbreak::session::resolve_writer_actor_id(repo, None),
            trust_set: discover_trust_set(repo),
            local_keys: discover_local_key_state(),
        }
    }
}

fn render_candidate(
    repo: &Path,
    candidate: EnrollmentCandidate,
    context: &DiscoverRenderContext,
) -> DiscoverCandidate {
    let suggested_name = suggested_name(&candidate);
    let signer_id = candidate.signer_id.as_str().to_owned();
    let local_keys = matching_local_keys(&context.local_keys, &candidate.signer_id);
    let enrolled_actors = context
        .trust_set
        .actors_for_signer(&candidate.signer_id)
        .into_iter()
        .map(|actor| actor.as_str().to_owned())
        .collect::<Vec<_>>();
    let commands = suggested_commands(
        repo,
        &candidate.signer_id,
        candidate.key_argument.as_deref(),
        &suggested_name,
        &context.resolved_actor,
        !local_keys.is_empty(),
        context
            .trust_set
            .authorizes(&context.resolved_actor, &candidate.signer_id, ""),
    );

    DiscoverCandidate {
        id: candidate.id,
        source: candidate.source,
        signer_id,
        key_argument: candidate.key_argument,
        suggested_name,
        actor_hints: candidate.actor_hints,
        local_keys,
        enrolled_actors,
        resolved_actor: context.resolved_actor.as_str().to_owned(),
        commands,
    }
}

fn suggested_name(candidate: &EnrollmentCandidate) -> String {
    candidate
        .suggested_name
        .clone()
        .unwrap_or_else(|| match &candidate.source {
            EnrollmentCandidateSource::GitUserSigningKey => "git-signing-key".to_owned(),
            EnrollmentCandidateSource::GitAllowedSignersFile { line, .. } => {
                format!("allowed-signer-line-{line}")
            }
        })
}

fn suggested_commands(
    repo: &Path,
    signer_id: &SignerId,
    key_argument: Option<&str>,
    suggested_name: &str,
    resolved_actor: &ActorId,
    has_local_key: bool,
    resolved_actor_authorized: bool,
) -> Vec<Vec<String>> {
    let mut commands = Vec::new();
    if !has_local_key && let Some(key_argument) = key_argument {
        commands.push(vec![
            "shore".to_owned(),
            "key".to_owned(),
            "use-ssh".to_owned(),
            key_argument.to_owned(),
            "--name".to_owned(),
            suggested_name.to_owned(),
        ]);
    }

    if !resolved_actor_authorized {
        commands.push(vec![
            "shore".to_owned(),
            "key".to_owned(),
            "enroll".to_owned(),
            "--signer".to_owned(),
            signer_id.as_str().to_owned(),
            "--actor".to_owned(),
            resolved_actor.as_str().to_owned(),
            "--repo".to_owned(),
            repo.display().to_string(),
        ]);
    }
    commands
}

fn discover_local_key_state() -> Vec<LocalKeyState> {
    list_keys()
        .unwrap_or_default()
        .into_iter()
        .map(|info| {
            let signer_id = info.signer_id().clone();
            let (custody, agent_loaded) = match info.custody() {
                KeyCustody::File => ("file", None),
                KeyCustody::Agent => (
                    "agent",
                    signer_id.ed25519_public_key().ok().and_then(agent_has_key),
                ),
            };
            LocalKeyState {
                signer_id,
                output: DiscoverLocalKey {
                    name: info.name().to_owned(),
                    default: info.name() == "default",
                    custody,
                    agent_loaded,
                },
            }
        })
        .collect()
}

fn matching_local_keys(
    local_keys: &[LocalKeyState],
    signer_id: &SignerId,
) -> Vec<DiscoverLocalKey> {
    local_keys
        .iter()
        .filter(|key| key.signer_id == *signer_id)
        .map(|key| DiscoverLocalKey {
            name: key.output.name.clone(),
            default: key.output.default,
            custody: key.output.custody,
            agent_loaded: key.output.agent_loaded,
        })
        .collect()
}

fn filtered_diagnostics(
    diagnostics: Vec<EnrollmentDiscoveryDiagnostic>,
    has_candidates: bool,
) -> Vec<EnrollmentDiscoveryDiagnostic> {
    diagnostics
        .into_iter()
        .filter(|diagnostic| {
            !is_redundant_allowed_signers_option_diagnostic(diagnostic, has_candidates)
        })
        .collect()
}

fn is_redundant_allowed_signers_option_diagnostic(
    diagnostic: &EnrollmentDiscoveryDiagnostic,
    has_candidates: bool,
) -> bool {
    has_candidates
        && diagnostic.code == EnrollmentDiscoveryDiagnosticCode::GitAllowedSignersLineUnsupported
        && diagnostic
            .message
            .contains("OpenSSH allowed-signers options require richer trust semantics")
}