
`import api_response` produces the JSON envelope strings public API
handlers return. Every helper writes a stable shape with `ok`, optional
data fields, and `request_id`, so clients and audit logs can correlate on
the same key.

The `ManualResponseEnvelopeRule` (`VCH010`) flags hand-built envelopes;
this module is the single place handlers build responses from.

## Success envelopes

```text
import api_response

return api_response.ok_object("issue", issue_to_json(created))
return api_response.ok_array("issues", json_array_map(rows, issue_to_json))
return api_response.ok_fields(extra_fields)
```

| Helper | Shape |
|---|---|
| `ok_object(name, value)` | `{ "ok": true, <name>: { ... } }` |
| `ok_array(name, value)` | `{ "ok": true, <name>: [ ... ] }` |
| `ok_fields(fields)` | `{ "ok": true, ...fields }` (spreads a JSON object) |

## Page envelopes

```text
return api_response.ok_page(
    Json.empty_object(),
    "issues",
    issue_array,
    limit,
    next_page_token)
```

`ok_page(fields, name, value, limit, next_page_token)` returns
`{ "ok": true, ...fields, "limit": limit, "next_page_token": <int|null>, <name>: [...] }`.
Pair it with `paging.page_slice` to compute the slice and cursor from a
typed query that fetched `limit + 1` rows.

## Error envelopes

| Failure | Helper | Notes |
|---|---|---|
| Single field failed validation | `validation_error(field, message, rid)` | Tags `field_errors` with `path` for client highlighting. |
| Multiple fields or shape-level failure | `error_fields(code, message, field_errors, extra, rid)` | Decoder uses this when more than one field is wrong. |
| Caller lacks a capability | `capability_error(message, capability, rid)` | Names the capability in `extra` for scope-aware retry. |
| Anything else | `error_object(code, message, rid)` | Default for not-found, conflict, or generic refusal. |

`rid` should be `ctx.request_id.value`; when no id is available the helper
defaults to `"req_unavailable"` instead of silently dropping the
correlation.

```text
return api_response.validation_error("title", "must be 1-200 chars", ctx.request_id.value)
return api_response.capability_error("forbidden", "issues.moderate", ctx.request_id.value)
return api_response.error_object("not_found", "issue not found", ctx.request_id.value)
```

## When to prefer contract data

For routes that declare a `response` shape, prefer returning typed values
through the contract instead of building envelopes directly. Use this
module when the route's contract intentionally returns a raw string
envelope (mutations with custom shapes, error paths, or pagination wrappers
that pre-date the response DSL).
