Implement Phase 9: client libraries (Go, Rust, Lisp, Python)

- clients/README.md: canonical API surface and error type reference
- clients/testdata/: shared JSON response fixtures
- clients/go/: mciasgoclient package; net/http + TLS 1.2+; sync.RWMutex
  token state; DisallowUnknownFields on all decoders; 25 tests pass
- clients/rust/: async mcias-client crate; reqwest+rustls (no OpenSSL);
  thiserror MciasError enum; Arc<RwLock> token state; 22+1 tests pass;
  cargo clippy -D warnings clean
- clients/lisp/: ASDF mcias-client; dexador HTTP, yason JSON; mcias-error
  condition hierarchy; Hunchentoot mock-dispatcher; 37 fiveam checks pass
  on SBCL 2.6.1; yason boolean normalisation in validate-token
- clients/python/: mcias_client package (Python 3.11+); httpx sync;
  py.typed; dataclasses; 32 pytest tests; mypy --strict + ruff clean
- test/mock/mockserver.go: in-memory mock server for Go client tests
- ARCHITECTURE.md §19: updated per-language notes to match implementation
- PROGRESS.md: Phase 9 marked complete
- .gitignore: exclude clients/rust/target/, python .venv, .pytest_cache,
  .fasl files
Security: token never logged or exposed in error messages in any library;
TLS enforced in all four languages; token stored under lock/mutex/RwLock
This commit is contained in:
2026-03-11 16:38:32 -07:00
parent f34e9a69a0
commit 0c441f5c4f
1974 changed files with 10151 additions and 33 deletions

View File

@@ -1055,44 +1055,55 @@ Error types exposed by every library:
#### Common Lisp (`clients/lisp/`)
- ASDF system: `mcias-client`
- HTTP: `dexador`
- JSON: `yason` (or `cl-json`; prefer `yason` for streaming)
- TLS: delegated to Dexador/Usocket; custom CA documented per platform
- API: CLOS class `mcias-client` with slot `token`; methods are generic
functions
- Conditions: `mcias-error`, `mcias-unauthenticated`, `mcias-forbidden`,
`mcias-not-found` — all subclasses of `mcias-error`
- Tests: `fiveam` test suite; mock responses via local TCP server or
Dexador's mock facility if available
- Compatibility: SBCL primary; CCL secondary
- ASDF system: `mcias-client` (quickload-able via Quicklisp)
- HTTP: `dexador` (synchronous)
- JSON: `yason` for both encoding and decoding; all booleans normalised
(yason returns `:false` for JSON `false`; client coerces to `nil`)
- TLS: delegated to Dexador/Usocket/cl+ssl; custom CA documented per platform
- API: CLOS class `mcias-client` with `client-base-url` reader and
`client-token` accessor; plain functions (not generic) for all operations
- Conditions: `mcias-error` base with subclasses `mcias-auth-error`,
`mcias-forbidden-error`, `mcias-not-found-error`, `mcias-input-error`,
`mcias-conflict-error`, `mcias-server-error`
- Tests: 37 checks in `fiveam`; mock server implemented with Hunchentoot
(`mock-dispatcher` subclass overriding `handle-request`); all fiveam
symbols explicitly prefixed to avoid SBCL package-lock violations
- Compatibility: SBCL 2.x primary
#### Python (`clients/python/`)
- Package: `mcias_client` (PEP 517 build; `pyproject.toml`)
- HTTP: `httpx` (provides both sync `MciasClient` and async `AsyncMciasClient`)
- TLS: `ssl.create_default_context(cafile=...)` for custom CA
- Package: `mcias_client` (PEP 517 build; `pyproject.toml` / setuptools)
- HTTP: `httpx` sync client; `Client` is a context manager (`__enter__`/`__exit__`)
- TLS: `ssl.create_default_context(cafile=...)` for custom CA cert
- Types: `py.typed` marker; all public symbols fully annotated; `mypy --strict`
- Errors: `MciasError(Exception)` base with subclasses as listed above
- Token state: `_token: str | None` instance attribute; thread-safe in sync
variant via `threading.Lock`
- Python version support: 3.11+
- Linting: `ruff check` (replaces flake8/isort); `ruff format` for style
passes with zero issues; dataclasses for `Account`, `PublicKey`, `PGCreds`
- Errors: `MciasError(Exception)` base with subclasses as listed above;
`raise_for_status()` dispatcher maps status codes to typed exceptions
- Token state: `token: str | None` public attribute (single-threaded use assumed)
- Python version support: 3.11+ (uses `datetime.UTC`, `X | Y` union syntax)
- Linting: `ruff check` (E/F/W/I/UP rules, 88-char line limit); `ruff format`
- Tests: 32 pytest tests using `respx` for httpx mocking
### Versioning Strategy
Each client library follows the MCIAS server's minor version. Breaking changes
to the API surface increment the major version. The proto definitions (Phase 7)
serve as the source of truth for the canonical interface; client libraries
implement a subset matching the REST API.
to the API surface increment the major version. The REST API surface defined in
`clients/README.md` serves as the source of truth; client libraries
implement the full surface.
Client libraries are not coupled to each other. A user of the Python library
does not need the Go library installed.
### Mock Server
### Mock Servers
`test/mock/` contains a Go binary (`mcias-mock`) that implements a minimal
in-memory MCIAS server for use in client library integration tests. It
supports the full REST API surface with configurable fixture responses.
Language-specific test suites start `mcias-mock` as a subprocess and connect
to it over localhost TLS (using a bundled self-signed test certificate).
`test/mock/mockserver.go` provides a Go `httptest.Server`-compatible mock
MCIAS server (struct `Server`) for use in Go client integration tests. It
maintains in-memory account/token/revocation state with `sync.RWMutex`.
Each other language library includes its own inline mock:
- **Rust**: `wiremock::MockServer` with per-test `Mock` stubs
- **Common Lisp**: Hunchentoot acceptor (`mock-dispatcher`) in
`tests/mock-server.lisp`; started on a random port per test via
`start-mock-server` / `stop-mock-server`
- **Python**: `respx` mock transport for `httpx`; `@respx.mock` decorator