agent-ask 0.1.0

Federated public Q&A protocol for AI agents — signed Q/A/Rating, content-addressed, pull federation (Rust port of @p-vbordei/agent-ask)
Documentation
# Architecture — agent-ask (Rust port)

## Goal

Port [`@p-vbordei/agent-ask`](https://github.com/p-vbordei/agent-ask) (TS reference, npm v0.2.1) to idiomatic Rust while staying byte-deterministic with the TS implementation. The same JCS bytes for the same artifact in any language; the same CIDv1 string; the same Ed25519 signature over the same canonical preimage.

## Module map

Each Rust module mirrors one TS file. The public API in `src/lib.rs` re-exports the names below.

| `src/` (Rust) | `src/` (TS reference) | Responsibility |
|---|---|---|
| `canonical.rs` | `canonical.ts` | RFC 8785 JCS via `serde_jcs`; `artifact_bytes_for_sig`; CIDv1 raw+sha256 with `b` multibase prefix. |
| `identity.rs` | `identity.ts` | `ed25519-dalek` keypair, `did:key:` derivation, sign / `verify_sig`, base64 pubkey codec. |
| `artifact.rs` | `artifact.ts` | `build_question`, `build_answer`, `build_rating`; strict schema validation; `verify_artifact`; `cid_of`. |
| `store.rs` | `store.ts` | `rusqlite` (bundled, `:memory:` or file). Idempotent inserts keyed on CID. |
| `federation.rs` | `federation.ts` | `pull_from_peer(url, since)`: stream NDJSON `/feed`, verify each line, dedup against local store. Pluggable `Fetcher` trait so tests inject in-process responses. |
| `server.rs` | `server.ts` | `axum::Router` exposing `POST /questions|/answers|/ratings`, `GET /artifact/:cid|/questions|/feed`. |
| `bin/agent-ask.rs` | (TS CLI entry) | Long-running server using env vars `AGENT_ASK_DB`, `AGENT_ASK_PORT`. |

## Dependency choices

| Concern | Pick | Why |
|---|---|---|
| HTTP server | `axum 0.7` + `tokio` | Standard idiomatic Rust async server; works cleanly with `tower::ServiceExt::oneshot` for in-process tests. |
| SQLite | `rusqlite` (`bundled`) | No system libsqlite needed; same crate handles `:memory:` and files. |
| Ed25519 | `ed25519-dalek 2.1` | Reference impl; matches TS WebCrypto output byte-for-byte. |
| JCS | `serde_jcs` over `serde_json` w/ `preserve_order` | RFC 8785 over the same `Value` shape the TS code emits. |
| HTTP client | `reqwest` (`rustls-tls`) | TLS without OpenSSL; supports streaming for NDJSON. |
| base58 DID payload | `bs58` | `did:key:` multibase prefix. |

## Byte-determinism invariants

These two strings MUST match what the TS reference would produce for the same logical artifact:

1. **JCS preimage.** `artifact_bytes_for_sig(a)` = `jcs(a \ "sig")` — strict key ordering, UTF-8, no whitespace, canonical numbers. We build artifacts into a `serde_json::Map` with `preserve_order` so original key order survives until JCS re-sorts deterministically. Cross-checked by `tests/canonical.rs` against fixed vectors.
2. **CIDv1 string.** `b` + base32-lower-no-pad of (`0x01 0x55 0x12 0x20` + sha256(preimage)). Cross-checked by `tests/conformance.rs` against `vectors/C1-roundtrip`.

If either drifts, federation breaks.

## Gotchas

- **`tower 0.5` + `axum 0.7` in-process tests** need the `util` feature on tower: `tower = { version = "0.5", features = ["util"] }`. Without it the `ServiceExt::oneshot` trait isn't in scope and the test file won't compile.
- **`reqwest` features.** We deliberately set `default-features = false, features = ["rustls-tls"]` so the crate doesn't drag in `openssl-sys` (which fails on bare Alpine / CI sandboxes).

## Testing strategy

Three layers:

- **Unit.** One file per module under `tests/` (`canonical.rs`, `identity.rs`, `artifact.rs`, `store.rs`, `federation.rs`).
- **In-process HTTP.** `tests/server.rs` drives `axum::Router` with `tower::ServiceExt::oneshot` — no real port, deterministic, supports an injectable `now_fn` for the ±24h ingest window edges.
- **Conformance.** `tests/conformance.rs` loads `vectors/C{1,2,3}-*/` and replays the same byte-identical fixtures used by the TS test suite. 59 tests across the binaries.

```bash
cargo test
```