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

@@ -0,0 +1,76 @@
"""Data models for MCIAS API responses."""
from dataclasses import dataclass, field
from typing import cast
@dataclass
class Account:
"""A user or service account."""
id: str
username: str
account_type: str
status: str
created_at: str
updated_at: str
totp_enabled: bool = False
@classmethod
def from_dict(cls, d: dict[str, object]) -> "Account":
return cls(
id=str(d["id"]),
username=str(d["username"]),
account_type=str(d["account_type"]),
status=str(d["status"]),
created_at=str(d["created_at"]),
updated_at=str(d["updated_at"]),
totp_enabled=bool(d.get("totp_enabled", False)),
)
@dataclass
class PublicKey:
"""Ed25519 public key in JWK format."""
kty: str
crv: str
x: str
use: str = ""
alg: str = ""
@classmethod
def from_dict(cls, d: dict[str, object]) -> "PublicKey":
return cls(
kty=str(d["kty"]),
crv=str(d["crv"]),
x=str(d["x"]),
use=str(d.get("use", "")),
alg=str(d.get("alg", "")),
)
@dataclass
class TokenClaims:
"""Claims from a validated token."""
valid: bool
sub: str = ""
roles: list[str] = field(default_factory=list)
expires_at: str = ""
@classmethod
def from_dict(cls, d: dict[str, object]) -> "TokenClaims":
roles_raw = cast(list[object], d.get("roles") or [])
return cls(
valid=bool(d.get("valid", False)),
sub=str(d.get("sub", "")),
roles=[str(r) for r in roles_raw],
expires_at=str(d.get("expires_at", "")),
)
@dataclass
class PGCreds:
"""Postgres connection credentials."""
host: str
port: int
database: str
username: str
password: str
@classmethod
def from_dict(cls, d: dict[str, object]) -> "PGCreds":
return cls(
host=str(d["host"]),
port=int(cast(int, d["port"])),
database=str(d["database"]),
username=str(d["username"]),
password=str(d["password"]),
)