- PROJECT_PLAN.md: insert Step 8.6 (Dockerfile) before the documentation step (renumbered to 8.7); acceptance criteria cover multi-stage build, non-root runtime user, EXPOSE ports, VOLUME /data, dist/mcias.conf.docker.example, Makefile docker target, and image size target (<50 MB) - ARCHITECTURE.md §18: add Dockerfile to artifact inventory table; add Dockerfile Design section covering build stages, security properties (no shell, non-root uid 10001, TLS inside container), operator workflow, and the new Makefile docker target
29 KiB
MCIAS Project Plan
Discrete implementation steps with acceptance criteria. See ARCHITECTURE.md for design rationale.
Phase 0 — Repository Bootstrap
Step 0.1: Go module and dependency setup
Acceptance criteria:
go.modexists with module pathgit.wntrmute.dev/kyle/mcias- Required dependencies declared:
modernc.org/sqlite(CGo-free SQLite),golang.org/x/crypto(Argon2, Ed25519 helpers),github.com/golang-jwt/jwt/v5,github.com/pelletier/go-toml/v2,github.com/google/uuid,git.wntrmute.dev/kyle/goutils go mod tidysucceeds;go build ./...succeeds on empty stubs
Step 0.2: .gitignore
Acceptance criteria:
.gitignoreexcludes: build output (mciassrv,mciasctl),*.db,*.db-wal,*.db-shm, test coverage files, editor artifacts
Phase 1 — Foundational Packages
Step 1.1: internal/model — shared data types
Acceptance criteria:
Account,Role,Token,AuditEvent,PGCredentialstructs defined- Account types and statuses as typed constants
- No external dependencies (pure data definitions)
- Tests: struct instantiation and constant validation
Step 1.2: internal/config — configuration loading
Acceptance criteria:
- TOML config parsed into a
Configstruct matching ARCHITECTURE.md §11 - Validation: required fields present, listen_addr parseable, TLS paths non-empty, Argon2 params within safe bounds (memory >= 64MB, time >= 2)
- Master key config: exactly one of
passphrase_envorkeyfileset - Tests: valid config round-trips; missing required field → error; unsafe Argon2 params rejected
Step 1.3: internal/crypto — key management and encryption helpers
Acceptance criteria:
GenerateEd25519KeyPair() (crypto/ed25519.PublicKey, crypto/ed25519.PrivateKey, error)MarshalPrivateKeyPEM(key crypto/ed25519.PrivateKey) ([]byte, error)(PKCS#8)ParsePrivateKeyPEM(pemData []byte) (crypto/ed25519.PrivateKey, error)SealAESGCM(key, plaintext []byte) (ciphertext, nonce []byte, err error)OpenAESGCM(key, nonce, ciphertext []byte) ([]byte, error)DeriveKey(passphrase string, salt []byte) ([]byte, error)— Argon2id KDF for master key derivation (separate from password hashing)- Key is always 32 bytes (AES-256)
crypto/randused for all nonce/salt generation- Tests: seal/open round-trip; open with wrong key → error; PEM round-trip; nonces are unique across calls
Step 1.4: internal/db — database layer
Acceptance criteria:
Open(path string) (*DB, error)opens/creates SQLite DB with WAL mode and foreign keys enabledMigrate(db *DB) errorapplies schema from ARCHITECTURE.md §9 idempotently (version table tracks applied migrations)- CRUD for
accounts,account_roles,token_revocation,system_tokens,pg_credentials,audit_log,server_config - All queries use prepared statements (no string concatenation)
- Tests: in-memory SQLite (
:memory:); each CRUD operation; FK constraint enforcement; migration idempotency
Phase 2 — Authentication Core
Step 2.1: internal/token — JWT issuance and validation
Acceptance criteria:
IssueToken(key ed25519.PrivateKey, claims Claims) (string, error)- Header:
{"alg":"EdDSA","typ":"JWT"} - Claims:
iss,sub,iat,exp,jti(UUID),roles
- Header:
ValidateToken(key ed25519.PublicKey, tokenString string) (Claims, error)- Must check
algheader before any other processing; reject non-EdDSA - Validates
exp,iat,iss,jtipresence - Returns structured error types for: expired, invalid signature, wrong alg, missing claim
- Must check
RevokeToken(db *DB, jti string, reason string) errorPruneExpiredTokens(db *DB) (int64, error)— removes rows past expiry- Tests: happy path; expired token rejected; wrong alg (none/HS256/RS256) rejected; tampered signature rejected; missing jti rejected; revoked token checked by caller; pruning removes only expired rows
Step 2.2: internal/auth — login and credential verification
Acceptance criteria:
HashPassword(password string, params ArgonParams) (string, error)— returns PHC-format stringVerifyPassword(password, hash string) (bool, error)— constant-time; re-hashes if params differ (upgrade path)ValidateTOTP(secret []byte, code string) (bool, error)— RFC 6238, 1-window tolerance, constant-timeLogin(ctx, db, key, cfg, req LoginRequest) (LoginResponse, error)- Orchestrates: load account → verify status → verify password → verify TOTP (if required) → issue JWT → write token_revocation row → write audit_log
- On any failure: write audit_log (login_fail or login_totp_fail); return generic error to caller (no information about which step failed)
- Tests: successful login; wrong password (timing: compare duration against a threshold — at minimum assert no short-circuit); wrong TOTP; suspended account; deleted account; TOTP enrolled but not provided; constant-time comparison verified (both branches take comparable time)
Phase 3 — HTTP Server
Step 3.1: internal/middleware — HTTP middleware
Acceptance criteria:
RequestLogger— logs method, path, status, duration, IPRequireAuth(key ed25519.PublicKey, db *DB)— extracts Bearer token, validates, checks revocation, injects claims into context; returns 401 on failureRequireRole(role string)— checks claims from context for role; returns 403 on failureRateLimit(rps float64, burst int)— per-IP token bucket; returns 429 on limit exceeded- Tests: logger captures fields; RequireAuth rejects missing/invalid/revoked tokens; RequireRole rejects insufficient roles; RateLimit triggers after burst exceeded
Step 3.2: internal/server — HTTP handlers and router
Acceptance criteria:
- Router wired per ARCHITECTURE.md §8 (all endpoints listed)
POST /v1/auth/login— delegates toauth.Login; returns{token, expires_at}POST /v1/auth/logout— revokes current token from context; 204POST /v1/auth/renew— validates current token, issues new one, revokes oldPOST /v1/token/validate— validates submitted token; returns claimsDELETE /v1/token/{jti}— admin or role-scoped; revokes by JTIGET /v1/health— 200{"status":"ok"}GET /v1/keys/public— returns Ed25519 public key as JWKPOST /v1/accounts— admin; creates accountGET /v1/accounts— admin; lists accounts (no password hashes in response)GET /v1/accounts/{id}— admin; single accountPATCH /v1/accounts/{id}— admin; update status/fieldsDELETE /v1/accounts/{id}— admin; soft-delete + revoke all tokensGET|PUT /v1/accounts/{id}/roles— admin; get/replace role setPOST /v1/auth/totp/enroll— returns TOTP secret + otpauth URIPOST /v1/auth/totp/confirm— confirms TOTP enrollmentDELETE /v1/auth/totp— admin; removes TOTP from accountGET|PUT /v1/accounts/{id}/pgcreds— get/set Postgres credentials- Credential fields (password hash, TOTP secret, Postgres password) are never included in any API response
- Tests: each endpoint happy path; auth middleware applied correctly; invalid JSON body → 400; credential fields absent from all responses
Step 3.3: cmd/mciassrv — server binary
Acceptance criteria:
- Reads config file path from
--configflag - Loads config, derives master key, opens DB, runs migrations, loads/generates signing key
- Starts HTTPS listener on configured address
- Graceful shutdown on SIGINT/SIGTERM (30s drain)
- If no signing key exists in DB, generates one and stores it encrypted
- Integration test: start server on random port, hit
/v1/health, assert 200
Phase 4 — Admin CLI
Step 4.1: cmd/mciasctl — admin CLI
Acceptance criteria:
- Subcommands:
mciasctl account create --username NAME --type human|systemmciasctl account listmciasctl account suspend --id UUIDmciasctl account delete --id UUIDmciasctl role grant --account UUID --role ROLEmciasctl role revoke --account UUID --role ROLEmciasctl token issue --account UUID(system accounts)mciasctl token revoke --jti JTImciasctl pgcreds set --account UUID --host H --port P --db D --user U --password Pmciasctl pgcreds get --account UUID
- CLI reads admin JWT from
MCIAS_ADMIN_TOKENenv var or--tokenflag - All commands make HTTPS requests to mciassrv (base URL from
--serverflag orMCIAS_SERVERenv var) - Tests: flag parsing; missing required flags → error; help text complete
Phase 5 — End-to-End Tests and Hardening
Step 5.1: End-to-end test suite
Acceptance criteria:
TestE2ELoginLogout— create account, login, validate token, logout, validate token again (must fail)TestE2ETokenRenewal— login, renew, old token rejected, new token validTestE2EAdminFlow— create account via CLI, assign role, login as userTestE2ETOTPFlow— enroll TOTP, login without code (fail), login with code (succeed)TestE2ESystemAccount— create system account, issue token, validate token, rotate token (old token rejected)TestE2EAlgConfusion— attempt login, forge token with alg=HS256/none, submit to validate endpoint, assert 401
Step 5.2: Security hardening review
Acceptance criteria:
golangci-lint run ./...passes with zero warningsgo test -race ./...passes with zero race conditions- Manual review checklist:
- No password/token/secret in any log line (grep audit)
- All
crypto/rand— nomath/randusage - All token comparisons use
crypto/subtle - Argon2id params meet OWASP minimums
- JWT alg validated before signature check in all code paths
- All DB queries use parameterized statements
- TLS min version enforced in server config
Step 5.3: Documentation and commit
Acceptance criteria:
- README.md updated with: build instructions, config file example, first-run steps
- Each phase committed separately with appropriate commit messages
PROGRESS.mdreflects completed state
Phase 6 — mciasdb: Database Maintenance Tool
See ARCHITECTURE.md §16 for full design rationale, trust model, and command surface.
Step 6.1: cmd/mciasdb — binary skeleton and config loading
Acceptance criteria:
- Binary at
cmd/mciasdb/main.goparses--configflag - Loads
[database]and[master_key]sections from mciassrv config format - Derives master key (passphrase or keyfile, identical logic to mciassrv)
- Opens SQLite DB with WAL mode and FK enforcement (reuses
internal/db) - Exits with error if DB cannot be opened (e.g., busy-timeout exceeded)
- Help text lists all subcommands
Step 6.2: Schema subcommands
Acceptance criteria:
mciasdb schema verify— opens DB, reports current schema version, exits 0 if up-to-date, exits 1 if migrations pendingmciasdb schema migrate— applies any pending migrations, reports each one applied, exits 0- Tests: verify on fresh DB reports version 0; migrate advances to current version; verify after migrate exits 0
Step 6.3: Account and role subcommands
Acceptance criteria:
mciasdb account list— prints uuid, username, type, status for all accountsmciasdb account get --id UUID— prints single account recordmciasdb account create --username NAME --type human|system— inserts row, prints new UUIDmciasdb account set-password --id UUID— prompts twice (confirm), re-hashes with Argon2id, updates row; no--passwordflag permittedmciasdb account set-status --id UUID --status STATUS— updates statusmciasdb account reset-totp --id UUID— clears totp_required and totp_secret_encmciasdb role list --id UUID— prints rolesmciasdb role grant --id UUID --role ROLE— inserts role rowmciasdb role revoke --id UUID --role ROLE— deletes role row- All write operations append an audit log row with actor tagged
mciasdb - Tests: each subcommand happy path against in-memory SQLite; unknown UUID returns error; set-password with mismatched confirmation returns error
Step 6.4: Token subcommands
Acceptance criteria:
mciasdb token list --id UUID— prints jti, issued_at, expires_at, revoked_at for accountmciasdb token revoke --jti JTI— sets revoked_at = now on the rowmciasdb token revoke-all --id UUID— revokes all non-revoked tokens for accountmciasdb prune tokens— deletes rows fromtoken_revocationwhere expires_at < now; prints count removed- All write operations append an audit log row
- Tests: revoke on unknown JTI returns error; revoke-all on account with no active tokens is a no-op (exits 0); prune removes only expired rows
Step 6.5: Audit log subcommands
Acceptance criteria:
mciasdb audit tail [--n N]— prints last N events (default 50), newest lastmciasdb audit query --account UUID— filters by actor_id or target_idmciasdb audit query --type EVENT_TYPE— filters by event_typemciasdb audit query --since TIMESTAMP— filters by event_time >= RFC-3339 timestamp- Flags are combinable (AND semantics)
--jsonflag on any audit subcommand emits newline-delimited JSON- Tests: tail returns correct count; query filters correctly; --json output is valid JSON
Step 6.6: Postgres credentials subcommands
Acceptance criteria:
mciasdb pgcreds get --id UUID— decrypts and prints host, port, db, username, password with a warning header that output is sensitivemciasdb pgcreds set --id UUID --host H --port P --db D --user U— prompts for password interactively (no--passwordflag), encrypts with AES-256-GCM, stores row- All write operations append an audit log row
- Tests: get on account with no pgcreds returns error; set then get round-trips correctly (decrypted value matches original)
Step 6.7: .gitignore and documentation
Acceptance criteria:
.gitignoreupdated to excludemciasdbbinary- README.md updated with mciasdb usage section (when to use vs mciasctl, config requirements, example commands)
PROGRESS.mdupdated to reflect Phase 6 complete
Phase 7 — gRPC Interface
See ARCHITECTURE.md §17 for full design rationale, proto definitions, and transport security requirements.
Step 7.1: Protobuf definitions and generated code
Acceptance criteria:
proto/mcias/v1/directory contains.protofiles for all service groups:auth.proto,token.proto,account.proto,admin.proto- All RPC methods mirror the REST API surface (see ARCHITECTURE.md §8 and §17)
proto/generate.gocontains a//go:generate protoc ...directive that produces Go stubs undergen/mcias/v1/usingprotoc-gen-goandprotoc-gen-go-grpc- Protobuf field conventions:
snake_casefield names,google.protobuf.Timestampfor all time fields, no credential fields in response messages (same exclusion rules as JSON API) go generate ./...re-runsprotocidempotently- Tests: generated code compiles cleanly (
go build ./...succeeds)
Step 7.2: internal/grpcserver — gRPC handler implementations
Acceptance criteria:
- Package
internal/grpcserverimplements all generated gRPC service interfaces - Handlers delegate to the same
internal/auth,internal/token, andinternal/dbpackages used by the REST server — no duplicated logic - Authentication: Bearer token extracted from gRPC metadata key
authorization, validated viainternal/middlewarelogic (same JWT validation path) - Admin RPCs require the
adminrole (checked identically to REST middleware) - Errors mapped to canonical gRPC status codes:
- Unauthenticated →
codes.Unauthenticated - Forbidden →
codes.PermissionDenied - Not found →
codes.NotFound - Invalid input →
codes.InvalidArgument - Internal error →
codes.Internal(no internal detail leaked to caller)
- Unauthenticated →
- Tests: unit tests for each RPC handler; unauthenticated calls rejected; credential fields absent from all response messages
Step 7.3: TLS and interceptors
Acceptance criteria:
- gRPC server uses the same TLS certificate and key as the REST server (loaded
from config); minimum TLS 1.2 enforced via
tls.Config - Unary server interceptor chain:
- Request logger (method name, peer IP, status, duration)
- Auth interceptor (extracts Bearer token, validates, injects claims into
context.Context) - Rate-limit interceptor (per-IP token bucket, same parameters as REST)
- No credential material logged by any interceptor
- Tests: interceptor chain applied correctly; rate-limit triggers after burst
Step 7.4: cmd/mciassrv integration — dual-stack server
Acceptance criteria:
- mciassrv starts both the REST listener and the gRPC listener on separate ports
(gRPC port configurable:
[server] grpc_addr; defaults to same host, port+1) - Both listeners share the same signing key, DB connection, and config
- Graceful shutdown drains both servers within the configured drain window
- Config:
grpc_addradded to[server]section (optional; if absent, gRPC listener is disabled) - Integration test: start server, connect gRPC client, call
HealthRPC, assert OK response
Step 7.5: cmd/mciasgrpcctl — gRPC admin CLI (optional companion)
Acceptance criteria:
- Binary at
cmd/mciasgrpcctl/main.gowith subcommands mirroringmciasctl - Uses generated gRPC client stubs; connects to
--serveraddress with TLS - Auth via
--tokenflag orMCIAS_TOKENenv var (sent as gRPC metadata) - Custom CA cert support identical to mciasctl
- Tests: flag parsing; missing required flags → error; help text complete
Step 7.6: Documentation and commit
Acceptance criteria:
- ARCHITECTURE.md §17 written (proto service layout, transport security, interceptor chain, dual-stack operation)
- README.md updated with gRPC section: enabling gRPC, connecting clients,
example
grpcurlinvocations .gitignoreupdated to exclude themciasgrpcctlbinary (using a root-anchored path/mciasgrpcctl); generated code ingen/is committed to the repository so that consumers do not need the protoc toolchainPROGRESS.mdupdated to reflect Phase 7 complete
Phase 8 — Operational Artifacts
See ARCHITECTURE.md §18 for full design rationale and artifact inventory.
Step 8.1: systemd service unit
Acceptance criteria:
dist/mcias.service— a hardened systemd unit for mciassrv:Type=notifywithsd_notifysupport (orType=simplewith explicitExecStartif notify is not implemented)User=mcias/Group=mcias— dedicated service accountCapabilityBoundingSet=— no capabilities required (port > 1024 assumed; document this assumption)ProtectSystem=strict,ProtectHome=true,PrivateTmp=trueReadWritePaths=/var/lib/mciasfor the SQLite fileEnvironmentFile=/etc/mcias/envforMCIAS_MASTER_PASSPHRASERestart=on-failure,RestartSec=5LimitNOFILE=65536
dist/mcias.env.example— template environment file with comments explaining each variable- Tests:
systemd-analyze verify dist/mcias.serviceexits 0 (run in CI if systemd available; skip gracefully if not)
Step 8.2: Default configuration file
Acceptance criteria:
dist/mcias.conf.example— fully-commented TOML config covering all sections from ARCHITECTURE.md §11, including the newgrpc_addrfield from Phase 7- Comments explain every field, acceptable values, and security implications
- Defaults match the ARCHITECTURE.md §11 reference config
- A separate
dist/mcias-dev.conf.examplesuitable for local development (localhost address, relaxed timeouts, short token expiry for testing)
Step 8.3: Installation script
Acceptance criteria:
dist/install.sh— POSIX shell script (no bash-isms) that:- Creates
mciassystem user and group (idempotent) - Copies binaries to
/usr/local/bin/ - Creates
/etc/mcias/and/var/lib/mcias/with correct ownership/perms - Installs
dist/mcias.serviceto/etc/systemd/system/ - Prints post-install instructions (key generation, first-run steps)
- Does not start the service automatically
- Creates
- Script is idempotent (safe to re-run after upgrades)
- Tests: shellcheck passes with zero warnings
Step 8.4: Man pages
Acceptance criteria:
man/man1/mciassrv.1— man page for the server binary; covers synopsis, description, options, config file format, signals, exit codes, and filesman/man1/mciasctl.1— man page for the admin CLI; covers all subcommands with examplesman/man1/mciasdb.1— man page for the DB maintenance tool; includes trust model and safety warningsman/man1/mciasgrpcctl.1— man page for the gRPC CLI (Phase 7)- Man pages written in
mdocformat (BSD/macOS compatible) ortroff(Linux) make mantarget in Makefile generates compressed.gzversions- Tests:
man --warnings -l man/man1/*.1exits 0 (or equivalent lint)
Step 8.5: Makefile
Acceptance criteria:
Makefileat repository root with targets:build— compile all binaries tobin/test—go test -race ./...lint—golangci-lint run ./...generate—go generate ./...(proto stubs from Phase 7)man— build compressed man pagesinstall— rundist/install.shclean— removebin/and generated artifactsdist— build release tarballs for linux/amd64 and linux/arm64 (usingGOOS/GOARCHcross-compilation)
make buildworks from a clean checkout aftergo mod download- Tests:
make buildproduces binaries;make testpasses;make lintpasses
Step 8.6: Dockerfile
Acceptance criteria:
Dockerfileat repository root using a multi-stage build:- Build stage:
golang:1.26-bookworm— compiles all four binaries withCGO_ENABLED=1(required for SQLite viamodernc.org/sqlite) and-trimpath -ldflags="-s -w"to strip debug info - Runtime stage:
debian:bookworm-slim— installs onlyca-certificatesandlibc6; copies binaries from the build stage - Final image runs as a non-root user (
uid=10001,gid=10001; namedmcias) EXPOSE 8443(REST) andEXPOSE 9443(gRPC); both are overridable via envVOLUME /data— operator mounts the SQLite database hereENTRYPOINT ["mciassrv"]withCMD ["-config", "/etc/mcias/mcias.conf"]- Image must not contain the Go toolchain, source code, or build cache
- Build stage:
dist/mcias.conf.docker.example— config template suitable for container deployment:listen_addr = "0.0.0.0:8443",grpc_addr = "0.0.0.0:9443",db_path = "/data/mcias.db", TLS cert/key paths under/etc/mcias/Makefilegains adockertarget:docker build -t mcias:$(VERSION) .whereVERSIONdefaults to the output ofgit describe --tags --always- Tests:
docker build .completes without error (run in CI if Docker available; skip gracefully if not)docker run --rm mcias:latest mciassrv --helpexits 0- Image size documented in PROGRESS.md (target: under 50 MB)
Step 8.7: Documentation
Acceptance criteria:
README.mdupdated with: quick-start section referencing both the install script and the Docker image, links to man pages, configuration walkthrough- ARCHITECTURE.md §18 updated to include the Dockerfile in the artifact inventory and document the container deployment model
PROGRESS.mdupdated to reflect Phase 8 complete
Phase 9 — Client Libraries
See ARCHITECTURE.md §19 for full design rationale, API surface, and per-language implementation notes.
Step 9.1: API surface definition and shared test fixtures
Acceptance criteria:
clients/README.md— defines the canonical client API surface that all language implementations must expose:Clienttype: configured with server URL, optional CA cert, optional tokenClient.Login(username, password, totp_code) → (token, expires_at, error)Client.Logout() → errorClient.RenewToken() → (token, expires_at, error)Client.ValidateToken(token) → (claims, error)Client.GetPublicKey() → (public_key_jwk, error)Client.Health() → error- Account management methods (admin only):
CreateAccount,ListAccounts,GetAccount,UpdateAccount,DeleteAccount - Role management:
GetRoles,SetRoles - Token management:
IssueServiceToken,RevokeToken - PG credentials:
GetPGCreds,SetPGCreds
clients/testdata/— shared JSON fixtures for mock server responses (used by all language test suites)- A mock MCIAS server (
test/mock/) in Go that can be started by integration tests in any language via subprocess or a pre-built binary
Step 9.2: Go client library
Acceptance criteria:
clients/go/— Go modulegit.wntrmute.dev/kyle/mcias/clients/go- Package
mciasgoclientexposes the canonical API surface from Step 9.1 - Uses
net/httpwithcrypto/tls; custom CA cert supported viax509.CertPool - Token stored in-memory;
Client.Token()accessor returns current token - Thread-safe: concurrent calls from multiple goroutines are safe
- All JSON decoding uses
DisallowUnknownFields - Tests:
- Unit tests with
httptest.Serverfor all methods - Integration test against mock server (Step 9.1) covering full login → validate → logout flow
go test -race ./...passes with zero race conditions
- Unit tests with
go doccomments on all exported types and methods
Step 9.3: Rust client library
Acceptance criteria:
clients/rust/— Rust cratemcias-client- Uses
reqwest(async, TLS-enabled) andserde/serde_json - Exposes the canonical API surface from Step 9.1 as an async Rust API
(
tokio-compatible) - Custom CA cert supported via
reqwest::Certificate - Token stored in
Arc<Mutex<Option<String>>>for async-safe sharing - Errors: typed
MciasErrorenum coveringUnauthenticated,Forbidden,NotFound,InvalidInput,Transport,Server - Tests:
- Unit tests with
wiremockormockitofor all methods - Integration test against mock server covering full login → validate → logout
cargo testpasses;cargo clippy -- -D warningspasses
- Unit tests with
cargo docwith#[doc]comments on all public items
Step 9.4: Common Lisp client library
Acceptance criteria:
clients/lisp/— ASDF systemmcias-client- Uses
dexadorfor HTTP andyason(orcl-json) for JSON - TLS handled by the underlying Dexador/Usocket stack; custom CA cert documented (platform-specific)
- Exposes the canonical API surface from Step 9.1 as synchronous functions with keyword arguments
- Errors signalled as conditions:
mcias-error,mcias-unauthenticated,mcias-forbidden,mcias-not-found - Token stored as a slot on the
mcias-clientCLOS object - Tests:
- Unit tests using
fiveamwith mock HTTP responses (viadexadormocking or a local test server) - Integration test against mock server covering full login → validate → logout
- Tests run with SBCL;
(asdf:test-system :mcias-client)passes
- Unit tests using
- Docstrings on all exported symbols;
(describe 'mcias-client)is informative
Step 9.5: Python client library
Acceptance criteria:
clients/python/— Python packagemcias_client; supports Python 3.11+- Uses
httpx(sync and async variants) orrequests(sync-only acceptable for v1) - Exposes the canonical API surface from Step 9.1 as a
MciasClientclass - Custom CA cert supported via
ssl.create_default_context()withcafile - Token stored as an instance attribute;
client.tokenproperty - Errors:
MciasErrorbase class with subclassesMciasAuthError,MciasForbiddenError,MciasNotFoundError - Typed: full
py.typedmarker; all public symbols annotated with PEP 526 type annotations;mypy --strictpasses - Tests:
- Unit tests with
pytestandrespx(httpx mock) orresponses(requests) for all methods - Integration test against mock server covering full login → validate → logout
pytestpasses;ruff checkandmypy --strictpass
- Unit tests with
- Docstrings on all public classes and methods;
help(MciasClient)is informative
Step 9.6: Documentation and commit
Acceptance criteria:
- Each client library has its own
README.mdwith: installation instructions, quickstart example, API reference summary, error handling guide - ARCHITECTURE.md §19 written (client library design, per-language notes, versioning strategy)
PROGRESS.mdupdated to reflect Phase 9 complete
Implementation Order
Phase 0 → Phase 1 (1.1, 1.2, 1.3, 1.4 in parallel or sequence)
→ Phase 2 (2.1 then 2.2)
→ Phase 3 (3.1, 3.2, 3.3 in sequence)
→ Phase 4
→ Phase 5
→ Phase 6 (6.1 → 6.2 → 6.3 → 6.4 → 6.5 → 6.6 → 6.7)
→ Phase 7 (7.1 → 7.2 → 7.3 → 7.4 → 7.5 → 7.6)
→ Phase 8 (8.1 → 8.2 → 8.3 → 8.4 → 8.5 → 8.6)
→ Phase 9 (9.1 → 9.2 → 9.3 → 9.4 → 9.5 → 9.6)
Each step must have passing tests before the next step begins.