# Implementation Plan: OAuth2 & Credential Management

**Branch**: `035-credential-management` | **Date**: 2026-03-31 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/035-credential-management/spec.md`

## Summary

Add pluggable credential management to the Swink Agent framework: tools declare auth requirements via `auth_config()`, the framework resolves credentials from an in-memory store (seeded at config time), automatically refreshes expired OAuth2 tokens, and delivers the resolved credential to the tool's `execute()` method. Core types and traits live in `swink-agent`; the resolver implementation and OAuth2 refresh logic live in a new `swink-agent-auth` crate.

## Technical Context

**Language/Version**: Rust 1.88, edition 2024
**Primary Dependencies**: `swink-agent` (core types/traits), `reqwest` (OAuth2 refresh HTTP), `chrono` (expiry timestamps), `serde`/`serde_json` (credential serialization), `tokio` (async runtime), `futures` (Shared combinator for dedup), `thiserror` (error types), `tracing` (diagnostics)
**Storage**: In-memory only (`Arc<RwLock<HashMap>>>`). No persistent storage built-in.
**Testing**: `cargo test --workspace`, `wiremock` for mocking OAuth2 token endpoints
**Target Platform**: Cross-platform (same as core)
**Project Type**: Library crate (new workspace member `swink-agent-auth`)
**Performance Goals**: Zero overhead for unauthenticated tools; credential resolution < 1ms for cached/valid credentials
**Constraints**: Core crate must not gain HTTP/OAuth2 dependencies. Exactly one refresh request per expired credential (deduplication).
**Scale/Scope**: ~8 new types in core, ~3 implementations in auth crate, ~600-800 lines new code

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Status | Notes |
|-----------|--------|-------|
| I. Library-First | ✅ Pass | New crate `swink-agent-auth` keeps HTTP deps out of core. Types/traits in core are dep-free. New crate justified: no existing boundary fits (see R7). |
| II. Test-Driven (NON-NEGOTIABLE) | ✅ Pass | All credential types, resolution paths, refresh, dedup, and error cases are independently testable. `wiremock` for token endpoint mocking. |
| III. Efficiency & Performance | ✅ Pass | Zero overhead for unauthenticated tools (None check). Shared futures for dedup avoid redundant HTTP. In-memory store uses RwLock for concurrent reads. |
| IV. Leverage the Ecosystem | ✅ Pass | Uses existing workspace deps (reqwest, chrono, serde, tokio, futures). No new heavy deps. oauth2 crate evaluated and rejected as overkill (see R2). |
| V. Provider Agnosticism | ✅ Pass | Credential system is for tool-level auth, not LLM providers. `get_api_key` callback unchanged. |
| VI. Safety & Correctness | ✅ Pass | `#[forbid(unsafe_code)]`. Secrets never logged (FR-016). `CredentialError` includes key names only. Poisoned lock recovery via `into_inner()`. |

**Architectural Constraints**:
- Crate count: 7 → 8. Justified in R7 — no existing crate absorbs the concern.
- Concurrency: Refresh deduplication uses `tokio::sync::Mutex` + `Shared` futures.
- No global mutable state: All state in `Arc<RwLock<>>` or `Arc<Mutex<>>`.

## Project Structure

### Documentation (this feature)

```text
specs/035-credential-management/
├── plan.md              # This file
├── spec.md              # Feature specification
├── research.md          # Research decisions (R1-R7)
├── data-model.md        # Entity definitions
├── quickstart.md        # Usage examples
├── contracts/
│   └── public-api.md    # Public API contract
├── checklists/
│   └── requirements.md  # Spec quality checklist
└── tasks.md             # Implementation tasks (generated by /speckit.tasks)
```

### Source Code (repository root)

```text
# Core crate — new types and trait extensions
src/
├── credential.rs        # NEW: Credential, ResolvedCredential, AuthConfig, AuthScheme,
│                        #      CredentialType, CredentialError, CredentialStore trait,
│                        #      CredentialResolver trait, AuthorizationHandler trait
├── tool.rs              # MODIFIED: auth_config() default method, execute() credential param
├── agent_options.rs     # MODIFIED: with_credential_resolver() builder method
├── loop_/
│   ├── mod.rs           # MODIFIED: credential_resolver field on AgentLoopConfig
│   └── tool_dispatch.rs # MODIFIED: credential resolution before execute()
└── lib.rs               # MODIFIED: re-export credential types

# New auth crate
auth/
├── Cargo.toml           # swink-agent-auth package
├── src/
│   ├── lib.rs           # Re-exports
│   ├── in_memory.rs     # InMemoryCredentialStore
│   ├── resolver.rs      # DefaultCredentialResolver (expiry check, refresh, dedup)
│   └── oauth2.rs        # OAuth2 refresh and authorization code exchange helpers
└── tests/
    ├── common/
    │   └── mod.rs        # Test helpers (mock store, mock handler)
    ├── in_memory_tests.rs
    ├── resolver_tests.rs
    └── oauth2_tests.rs

# Existing crates — modifications
Cargo.toml               # MODIFIED: add "auth" to workspace members, add chrono workspace dep usage
```

**Structure Decision**: New `auth/` directory at workspace root, following the pattern of `policies/`, `adapters/`, `memory/`, etc. Core crate gains one new module (`credential.rs`). Tool dispatch gains ~30 lines for credential resolution.

## Implementation Phases

### Phase 1: Core Types & Traits (swink-agent)

Create `src/credential.rs` with all types, enums, error type, and traits. Add `auth_config()` default method to `AgentTool`. Re-export from `lib.rs`. No behavioral changes yet. Note: core crate gains `chrono` workspace dep for `DateTime<Utc>` in `Credential` variants.

**Files**: `src/credential.rs` (new), `src/tool.rs`, `src/lib.rs`, `Cargo.toml` (core crate — add chrono dep)

### Phase 2: Execute Signature & Tool Dispatch Integration (swink-agent)

Add `credential: Option<ResolvedCredential>` parameter to `AgentTool::execute`. Update all existing tool implementations (builtin tools, test mocks). Add `credential_resolver` to `AgentOptions` and `AgentLoopConfig`. Wire credential resolution into `tool_dispatch.rs`.

**Files**: `src/tool.rs`, `src/agent_options.rs`, `src/loop_/mod.rs`, `src/loop_/tool_dispatch.rs`, `src/builtin_tools/*.rs`, `tests/common/mod.rs`

### Phase 3: Auth Crate — InMemoryCredentialStore (swink-agent-auth)

Create crate scaffold. Implement `InMemoryCredentialStore` with `Arc<RwLock<HashMap>>` backing. Builder pattern for seeding. Full test coverage.

**Files**: `auth/Cargo.toml`, `auth/src/lib.rs`, `auth/src/in_memory.rs`, `auth/tests/in_memory_tests.rs`

### Phase 4: Auth Crate — DefaultCredentialResolver (swink-agent-auth)

Implement resolver with expiry checking, OAuth2 refresh via reqwest, concurrent refresh deduplication via `Shared` futures. Configurable expiry buffer and timeout.

**Files**: `auth/src/resolver.rs`, `auth/src/oauth2.rs`, `auth/tests/resolver_tests.rs`, `auth/tests/oauth2_tests.rs`

### Phase 5: Authorization Flow (swink-agent-auth)

Implement authorization code exchange in the resolver. Wire `AuthorizationHandler` callback. Timeout support.

**Files**: `auth/src/resolver.rs` (extend), `auth/src/oauth2.rs` (extend), `auth/tests/resolver_tests.rs` (extend)

### Phase 6: Integration Tests

End-to-end tests: agent with authenticated tools, credential resolution, refresh, error paths. Test zero-overhead for unauthenticated tools.

**Files**: `auth/tests/integration_tests.rs`

### Phase 7: Polish

Update workspace Cargo.toml, ensure clippy clean, verify no secrets in tracing output, update AGENTS.md with active technologies.

**Files**: `Cargo.toml`, `AGENTS.md`

## Complexity Tracking

> No constitution violations requiring justification. New crate addition justified in R7.
