# Contributing to kanoniv-agent-auth
Thanks for your interest in contributing. This library is the cryptographic foundation for agent identity across Rust, TypeScript, and Python, so correctness matters more than speed.
## Ground rules
1. **All three languages must pass.** Every PR runs CI across Rust, TypeScript, and Python. If you change behavior in one language, you must verify it matches the others.
2. **Cross-language fixtures are the source of truth.** Test vectors in `fixtures/` are generated by Rust (`cargo run --example generate_fixtures`). JS and Python tests load these fixtures and must produce identical results. If you change serialization, hashing, or verification logic, regenerate fixtures and update all three test suites.
3. **No breaking changes without discussion.** Open an issue first if you want to change the DID format, signing scheme, delegation structure, or caveat types.
4. **Tests are required.** Every PR must include tests. For bug fixes, add a test that fails without the fix. For new features, cover the happy path and at least one failure case.
## How to contribute
1. Fork the repo and create a branch from `main`
2. Make your changes
3. Run all three test suites locally:
```bash
cargo test
cargo clippy -- -D warnings
cargo fmt --check
cd js && npm install && npm run build && npm test
cd python && python -m venv .venv && source .venv/bin/activate
pip install maturin pytest && maturin develop && pytest tests/ -v
```
4. Open a PR against `main`
5. Wait for CI and a review
## Cross-language testing
The `fixtures/` directory contains deterministic test vectors generated from known secret keys. If you change any of these, you must:
1. Update `examples/generate_fixtures.rs`
2. Run `cargo run --example generate_fixtures` to regenerate
3. Verify JS and Python tests still pass against the new fixtures
Key invariants:
- Same secret bytes produce the same DID across all languages
- `contentHash()` produces the same hash across all languages for the same signed message
- Caveat enforcement (action_scope, max_cost, expires_at, resource) produces the same pass/fail verdicts
## What we look for in PRs
- Does it break cross-language compatibility?
- Are there tests?
- Is the code clear without excessive comments?
- Does it follow existing patterns (no new dependencies without justification)?
## Reporting bugs
Open an issue with:
- Which language (Rust, TypeScript, Python)
- Minimal reproduction
- Expected vs actual behavior
- Version number