Day 29 — Compose the Workspace and API
Today Mosaic assembles existing crates without turning them into one dependency cycle.
Keep dependencies directional
Section titled “Keep dependencies directional”mosaic-api / forge-cli ↓mosaic-harness ─▶ mosaic-domain ↓ ↓tools evidence ↓ ↓storage / db / artifacts / modelsDomain types do not import Axum, SQLx, Clap, or a provider SDK. Interface crates translate external requests into domain values and translate domain errors into HTTP or process results.
Build the HTTP boundary
Section titled “Build the HTTP boundary”pub struct AppState { pub runs: Arc<dyn RunService>,}
async fn create_run( State(state): State<AppState>, Json(request): Json<CreateRunRequest>,) -> Result<(StatusCode, Json<RunView>), ApiError> { let command = request.try_into_domain()?; let run = state.runs.create(command).await?; Ok((StatusCode::ACCEPTED, Json(run.into())))}Validate body size, content type, identity, tenant, task schema, and idempotency key before expensive work. Return one stable error envelope; do not leak internal provider or database messages.
Run and test
Section titled “Run and test”cd rust/rust-ai-engineeringcargo test -p mosaic-apicargo run -q -p mosaic-api --example http_boundaryUse an in-process router test so status, headers, body, and shared state are exercised without binding a port.
Break it deliberately
Section titled “Break it deliberately”Test malformed JSON, oversized body, unknown field, missing tenant, duplicate idempotency key, provider unavailable, and internal timeout. Assert stable external errors and detailed internal traces.
Completion proof
Section titled “Completion proof”Trace one accepted request from HTTP JSON to domain command, durable run ID, and response. No provider wire type should cross the boundary.
Deep reference: Axum router, state, and errors.
Next: Day 30 — Durable Jobs →.