Wahoo provides explicit application foundations for request handling, configuration, sessions, tenant authorization, and rate limits.

> **Work in progress:** Do not use Wahoo in production yet.

## Start

Generated applications load visible runtime policy from `internal/config`.

```go
runtime.Use(
    server.SecurityHeaders(configuration.SecurityHeaders),
    server.MaxBodyBytes(configuration.JSONBodyLimit),
)
```

The generated configuration uses a 1 MiB JSON limit and a small response-header policy. Review it before deployment. Wahoo does not infer CORS, trusted proxies, TLS, HSTS, or CSRF policy.

## JSON APIs

Use the bounded JSON decoder for public API handlers.

```go
var input createProjectInput
if err := api.DecodeJSON(w, r, &input, 1<<20); err != nil {
    _ = api.WriteDecodeError(w, r, err)
    return
}
if fields := api.Validate(input); len(fields) > 0 {
    _ = api.WriteError(w, r, http.StatusBadRequest, "invalid_request", "check the request fields", fields...)
    return
}
```

The decoder requires `application/json`. It rejects unknown fields, trailing JSON values, and bodies over the selected limit. Error responses include the request ID when the request passed through `server.Server`.

## Sessions And CSRF

Store only `auth.TokenHash(rawToken)`, never the raw session token. Use `auth.AuthenticateSession` to check token hash, expiry, and revocation through an application store.

```go
session, err := auth.AuthenticateSession(r.Context(), sessions, rawToken, time.Now())
```

Use `auth.NewCSRFToken` and `auth.VerifyCSRFToken` only as primitives. The application must set secure cookies, select a CSRF pattern, rotate sessions, and revoke sessions at logout.

## Tenant Authorization

Resolve the authenticated subject and tenant in application middleware. Do not infer them from request headers.

```go
ctx := authz.WithScope(r.Context(), authz.Scope{
    SubjectID: "usr_123",
    TenantID:  "ten_123",
})
if err := authz.Require(ctx, authorizer, "project.read"); err != nil {
    // Map the error to the application response.
}
```

Every protected repository query must accept and constrain by the resolved tenant ID.

## Rate Limits

Use `server.RateLimit` with an application-defined key. The built-in `ratelimit.Memory` implementation bounds its key count and is safe only for one process.

```go
limiter, err := ratelimit.NewMemory(ratelimit.Policy{Limit: 10}, 10000)
runtime.Use(server.RateLimit(limiter, func(r *http.Request) string {
    return authenticatedUserID(r)
}))
```

Use a shared limiter for a multi-instance deployment. Do not trust forwarded client IP headers until the application defines trusted proxies.

## Database Readiness

Wahoo does not choose a database driver or migration format. Implement `database.Migrator` in the application and use bounded `database.Ready` checks for `/readyz`.

## Observe Requests

`server.Config.Observer` receives request ID, method, bounded path, status, bytes, duration, and panic state. Adapt it to the metrics or tracing system used by the application. Keep observers fast and bounded.