Skip to content

feat: refactor CLI signing orchestration into SDK#19

Merged
bordumb merged 11 commits intomainfrom
fn-13
Mar 4, 2026
Merged

feat: refactor CLI signing orchestration into SDK#19
bordumb merged 11 commits intomainfrom
fn-13

Conversation

@bordumb
Copy link
Contributor

@bordumb bordumb commented Mar 4, 2026

Summary

  • AgentSigningPort trait in auths-sdk with three-tier fallback workflow (Agent → Auto-start → Direct signing)
  • CommitSigningWorkflow extracted from CLI's monolithic bin/sign.rs into a composable SDK workflow
  • CliAgentAdapter in CLI implements the port trait, delegating to Unix SSH agent
  • SshAgentPort trait in auths-core abstracts ssh-add subprocess behind a port, removing all std::process::Command from core production code
  • Eliminated unwrap/expect from auths-core and auths-sdk production code with #![deny(clippy::unwrap_used, clippy::expect_used)] enforcement

Test plan

  • cargo nextest run -p auths-core — 273 tests pass
  • cargo nextest run -p auths-sdk --features test-utils — 77 tests pass
  • cargo nextest run -p auths-cli — 142 tests pass
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo fmt --check --all — clean
  • Pre-push hooks pass (fmt, clippy, deny, unit tests, wasm, cross-compile)

bordumb added 5 commits March 4, 2026 14:20
Define AgentSigningPort trait in auths-sdk/src/ports/agent.rs with
try_sign, ensure_running, and add_identity methods. Add NoopAgentProvider
default for platforms without agent support. Extend AuthsContext builder
with optional agent_signing field. Add FakeAgentProvider for testing.
Create CommitSigningWorkflow in auths-sdk/src/workflows/signing.rs with
Agent -> Auto-start+Direct -> Direct fallback logic. Extend SigningError
with AgentUnavailable, AgentSigningFailed, PassphraseExhausted,
KeychainUnavailable, and KeyDecryptionFailed variants. Add comprehensive
workflow tests covering all tiers, error paths, and edge cases.
…SigningWorkflow

- Create CliAgentAdapter in adapters/agent.rs implementing AgentSigningPort
  - Wraps Unix agent client (agent_sign, ensure_agent_running, add_identity)
  - Produces SSHSIG PEM via construct_sshsig_signed_data + construct_sshsig_pem
  - Gated with #[cfg(unix)], non-Unix uses NoopAgentProvider

- Introduce CommitSigningContext for lightweight dependency injection
  - Contains only key_storage, passphrase_provider, agent_signing
  - Avoids requiring full AuthsContext for signing operations
  - Add From<&AuthsContext> conversion for ergonomic use

- Refactor bin/sign.rs run_sign() to delegate to CommitSigningWorkflow
  - Remove try_sign_via_agent, get_passphrase, load_key_with_retry
  - Remove process::exit(1) — errors propagate as SigningError
  - Preserve typed SigningError (no anyhow!("{}", e) discarding)
  - Utc::now() called at presentation boundary only

- Add build_agent_provider() factory for platform-conditional wiring
- Add SigningError handling in error renderer (text + JSON)
Create SshAgentPort trait in auths-core/src/ports/ssh_agent.rs with
SshAgentError thiserror enum. Refactor register_keys_with_macos_agent
functions to accept &dyn SshAgentPort instead of spawning Command::new
directly. Add MacOsSshAgentAdapter in CLI adapters wrapping ssh-add.

No std::process::Command usage remains in auths-core production code.
Replace Mutex::lock().unwrap() with proper error handling in signing.rs
(map_err to AgentError::MutexError for Result fns, unwrap_or_else for
void fns). Refactor witness/server.rs create_receipt and sign_payload to
return Result instead of panicking. Replace try_into().unwrap() with ?
in encryption.rs. Add #[allow] with rationale for safe-by-invariant
sites (provider_bridge runtime, secp256k1 stored keys, runtime length
check, deprecated FFI).

Add #![deny(clippy::unwrap_used, clippy::expect_used)] to auths-core
and auths-sdk lib.rs. Configure allow-unwrap-in-tests and
allow-expect-in-tests in clippy.toml.
@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
auths Ready Ready Preview, Comment Mar 4, 2026 5:14pm

@bordumb bordumb self-assigned this Mar 4, 2026
PASSPHRASE_SERVICE, encode_secret, and decode_secret are only used by
the macOS and Linux keychain modules. Without cfg gating they trigger
dead-code warnings on platforms where neither module compiles.
The blanket *.lock pattern was preventing Cargo.lock from being
tracked, causing cargo-audit in CI to fail with "not found". Replace
with specific JS lock file patterns since Cargo.lock should be
committed for application crates per Rust convention.
Add comprehensive SDK README covering architecture, AuthsContext
builder, modules, port traits, error handling, and design principles.
Update CHANGELOG unreleased section with fn-13 work.
LockGuard::drop was deleting the lock file, creating a race where two
threads could acquire flock on different inodes simultaneously. One
thread would then get ENOENT when renaming the .tmp file. Fix: stop
deleting the lock file on drop — closing the fd releases the advisory
lock. The lock file stays as a harmless empty file.
…feature

get_kdf_params() was gated on #[cfg(test)] which only applies to unit
tests within the crate. Integration tests (separate binaries) hit
production Argon2 params (m=64 MiB, t=3), making each encrypt/decrypt
~3s. Changed to #[cfg(any(test, feature = "test-utils"))] so all
consumer crates with test-utils enabled get fast params too.

Before: lifecycle tests took 13-45s each (~137s total)
After: all under 0.07s each
The --all-features run is a strict superset of the default-features
run. Running both doubled test job time for no additional coverage.
@bordumb bordumb merged commit 3ef189c into main Mar 4, 2026
8 checks passed
@bordumb bordumb deleted the fn-13 branch March 4, 2026 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant