Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5f7e936
feat(node-sdk): scaffold napi-rs project with workspace config
bordumb Mar 9, 2026
474f2b0
feat(node-sdk): add error mapping, shared helpers, and napi types
bordumb Mar 9, 2026
02802fb
feat(auths-node): add identity, device, signing, and verification bin…
bordumb Mar 9, 2026
57498e7
feat(auths-node): add TypeScript client layer with error hierarchy
bordumb Mar 9, 2026
6f5d544
feat(auths-node): add org, trust, witness, artifact, audit, diagnosti…
bordumb Mar 9, 2026
1e7bc61
feat(auths-node): add remaining TS service wrappers, wire client
bordumb Mar 9, 2026
b929f40
feat(auths-node): add policy builder, pairing bindings, wire all serv…
bordumb Mar 9, 2026
e1578eb
fix(auths-node): fix napi u64->i64, trust remove return, policy compile
bordumb Mar 9, 2026
8eb9b6a
fix(auths-node): resolve all clippy lints, cargo fmt
bordumb Mar 9, 2026
f63bc8a
fix(auths-cli): audit and fix all production unwrap/expect calls
bordumb Mar 9, 2026
039e88a
test: add vitest suite for Node SDK
bordumb Mar 9, 2026
1aaea49
ci: add Node SDK build/test and publish workflows
bordumb Mar 9, 2026
526b575
fix: resolve test failures in Node SDK
bordumb Mar 9, 2026
83b530c
docs: add Node SDK README with quickstart and API examples
bordumb Mar 9, 2026
bd23af7
refactor: batch 1 - dead code removal, exports field, test cleanup, k…
bordumb Mar 9, 2026
5455a17
refactor: consolidate tokio runtimes - verify functions now async via…
bordumb Mar 9, 2026
aef6b88
refactor: replace pairing singleton with instance-based class, add Sy…
bordumb Mar 9, 2026
2d8070a
refactor: fix agent attestation to use SDK link_device machinery
bordumb Mar 9, 2026
dc7bbac
fix: resolve PassphraseProvider trait bound in create_agent_identity
bordumb Mar 9, 2026
ea1dedc
refactor: add regression tests for SDK refactor
bordumb Mar 9, 2026
ef7be38
fix: use role-filtered key lookup in getPublicKey
bordumb Mar 9, 2026
b95c518
fix: correct bug around hardcoded identity repo location
bordumb Mar 9, 2026
bdbedbc
refactor: align Node and Python SDKs on repo_path and key lookup
bordumb Mar 9, 2026
ff6c06b
refactor: add cargo fmt for SDK packages to pre-commit hook
bordumb Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/node-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Node SDK

on:
push:
branches: [main]
paths:
- 'packages/auths-node/**'
- 'crates/**'
pull_request:
branches: [main]
paths:
- 'packages/auths-node/**'
- 'crates/**'

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
build-and-test:
name: Build & Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/auths-node/target
key: ${{ runner.os }}-node-sdk-${{ hashFiles('packages/auths-node/Cargo.lock') }}
restore-keys: ${{ runner.os }}-node-sdk-

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: packages/auths-node
run: pnpm install

- name: Build native module
working-directory: packages/auths-node
run: pnpm build

- name: Configure Git
run: |
git config --global user.name "CI"
git config --global user.email "ci@auths.dev"

- name: Run tests
working-directory: packages/auths-node
run: pnpm test

- name: Upload native module
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: packages/auths-node/auths.*.node

lint:
name: Lint (Rust)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/auths-node/target
key: ${{ runner.os }}-node-lint-${{ hashFiles('packages/auths-node/Cargo.lock') }}
restore-keys: ${{ runner.os }}-node-lint-

- name: Check formatting
working-directory: packages/auths-node
run: cargo fmt --check

- name: Clippy
working-directory: packages/auths-node
run: cargo clippy --all-features -- -D warnings
163 changes: 163 additions & 0 deletions .github/workflows/publish-node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: Publish Node SDK

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
target:
description: "Publish target"
required: true
type: choice
options:
- npm
- dry-run
default: dry-run

permissions:
contents: read

jobs:
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
use-cross: true
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
use-cross: true
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
use-cross: true
- os: macos-latest
target: x86_64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: packages/auths-node
run: pnpm install

- name: Install cross
if: matrix.use-cross
run: cargo install cross --git https://github.com/cross-rs/cross

- name: Build native module
working-directory: packages/auths-node
run: pnpm build -- --target ${{ matrix.target }}

- uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: packages/auths-node/auths.*.node

test:
name: Test (${{ matrix.os }})
needs: [build]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact: bindings-x86_64-unknown-linux-gnu
- os: macos-latest
artifact: bindings-aarch64-apple-darwin
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: packages/auths-node
run: pnpm install

- uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact }}
path: packages/auths-node

- name: Configure Git
run: |
git config --global user.name "CI"
git config --global user.email "ci@auths.dev"

- name: Run tests
working-directory: packages/auths-node
run: pnpm test

publish:
name: Publish to npm
needs: [build, test]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'npm')
permissions:
id-token: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: packages/auths-node
run: pnpm install

- uses: actions/download-artifact@v4
with:
path: packages/auths-node/artifacts
merge-multiple: true

- name: Move artifacts
working-directory: packages/auths-node
run: pnpm artifacts

- name: Prepare npm packages
working-directory: packages/auths-node
run: pnpm prepublishOnly

- name: Publish
working-directory: packages/auths-node
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.target }}" = "dry-run" ]; then
echo "Dry run - skipping publish"
npm pack
else
npm publish --provenance --access public
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repos:

- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --all --
entry: bash -c 'cargo fmt --all && cargo fmt --all --manifest-path packages/auths-node/Cargo.toml && cargo fmt --all --manifest-path packages/auths-python/Cargo.toml'
language: system
types: [rust]
pass_filenames: false
Expand Down
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,13 @@ When the user is getting errors locally, don't forget to remind them to reinstal
```
The existing SDK error types (`SetupError`, `DeviceError`, `RegistrationError` in `crates/auths-sdk/src/error.rs`) currently wrap `anyhow::Error` in their `StorageError` and `NetworkError` variants (e.g., `StorageError(#[source] anyhow::Error)`). These must be migrated to domain-specific `thiserror` variants during Epic 1/2 execution — the `anyhow` wrapping is a transitional pattern, not a permanent design. The `map_storage_err()` and `map_device_storage_err()` helper functions should be replaced with direct `From` impls on the domain storage errors.
6. **No reverse dependencies**: Core and SDK must never reference presentation layer crates.
7. **`unwrap()` / `expect()` Policy**: The workspace denies `clippy::unwrap_used` and `clippy::expect_used` globally. `clippy.toml` sets `allow-unwrap-in-tests = true`, so test code is exempt. For production code:
- **Default**: Use `?` (in functions returning `Result`), `.ok_or_else(|| ...)`, `.unwrap_or_default()`, or `match` instead of `.unwrap()` / `.expect()`.
- **Provably safe unwraps**: When an unwrap is provably infallible (e.g., `try_into()` after a length check, `ProgressStyle::with_template()` on a compile-time constant, `Regex::new()` on a literal), use an inline `#[allow]` with an `INVARIANT:` comment explaining why it cannot fail:
```rust
#[allow(clippy::expect_used)] // INVARIANT: length validated to be 32 bytes on line N
let arr: [u8; 32] = vec.try_into().expect("validated above");
```
- **FFI boundaries**: `expect()` is acceptable in FFI/WASM `extern "C"` functions where panicking is the only option (no `Result` return). Annotate with `#[allow]`.
- **Mutex/RwLock poisoning**: `lock().expect()` / `write().expect()` on stdlib mutexes is acceptable — a poisoned mutex means another thread panicked, which is unrecoverable. Annotate with `#[allow]` and an INVARIANT comment.
- **Never** add blanket `#![allow(clippy::unwrap_used, clippy::expect_used)]` to crate roots. Fix each site individually.
6 changes: 2 additions & 4 deletions crates/auths-cli/src/bin/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
clippy::print_stdout,
clippy::print_stderr,
clippy::disallowed_methods,
clippy::exit,
clippy::unwrap_used,
clippy::expect_used
clippy::exit
)]
//! auths-sign: Git SSH signing program compatible with `gpg.ssh.program`
//!
Expand Down Expand Up @@ -190,8 +188,8 @@ fn run_verify(args: &Args) -> Result<()> {
"-n",
namespace,
"-s",
sig_file.to_str().unwrap(),
]);
cmd.arg(sig_file);
for opt in &args.verify_options {
cmd.arg("-O").arg(opt);
}
Expand Down
32 changes: 9 additions & 23 deletions crates/auths-cli/src/bin/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
clippy::print_stdout,
clippy::print_stderr,
clippy::disallowed_methods,
clippy::exit,
clippy::unwrap_used,
clippy::expect_used
clippy::exit
)]
//! auths-verify: SSH signature verification for Auths identities
//!
Expand Down Expand Up @@ -189,18 +187,10 @@ fn verify_with_ssh_keygen(

// Run ssh-keygen -Y verify
let output = Command::new("ssh-keygen")
.args([
"-Y",
"verify",
"-f",
allowed_signers.to_str().unwrap(),
"-I",
identity,
"-n",
namespace,
"-s",
signature_file.to_str().unwrap(),
])
.args(["-Y", "verify", "-f"])
.arg(allowed_signers)
.args(["-I", identity, "-n", namespace, "-s"])
.arg(signature_file)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
Expand Down Expand Up @@ -240,14 +230,10 @@ fn find_signer(
allowed_signers: &std::path::Path,
) -> Result<Option<String>> {
let output = Command::new("ssh-keygen")
.args([
"-Y",
"find-principals",
"-f",
allowed_signers.to_str().unwrap(),
"-s",
signature_file.to_str().unwrap(),
])
.args(["-Y", "find-principals", "-f"])
.arg(allowed_signers)
.arg("-s")
.arg(signature_file)
.output();

if let Ok(out) = output
Expand Down
5 changes: 4 additions & 1 deletion crates/auths-cli/src/commands/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ fn start_agent(
create_restricted_dir(&auths_dir)
.with_context(|| format!("Failed to create auths directory: {:?}", auths_dir))?;

let socket = socket_path.unwrap_or_else(|| get_default_socket_path().unwrap());
let socket = match socket_path {
Some(s) => s,
None => get_default_socket_path()?,
};
let pid_path = get_pid_file_path()?;
let env_path = get_env_file_path()?;
let timeout = parse_timeout(timeout_str)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/auths-cli/src/commands/artifact/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ fn output_error(file: &str, exit_code: i32, message: &str) -> Result<()> {
issuer: None,
error: Some(message.to_string()),
};
println!("{}", serde_json::to_string(&result).unwrap());
println!("{}", serde_json::to_string(&result)?);
} else {
eprintln!("Error: {}", message);
}
Expand All @@ -310,7 +310,7 @@ fn output_error(file: &str, exit_code: i32, message: &str) -> Result<()> {
/// Output the verification result.
fn output_result(exit_code: i32, result: VerifyArtifactResult) -> Result<()> {
if is_json_mode() {
println!("{}", serde_json::to_string(&result).unwrap());
println!("{}", serde_json::to_string(&result)?);
} else if result.valid {
print!("Artifact verified");
if let Some(ref issuer) = result.issuer {
Expand Down
Loading
Loading