
Projection is for building JSON responses that show only the fields a
particular audience should see. For example, a public issue list might
include a title and status, while an internal audit view can also include
private notes. This module lets you name those field lists once and reuse
them when rendering responses.

`import projection` backs the `json ... projected by ...` mapping form.
A `ProjectionPolicy` names the allowed fields for one response surface;
the `FieldProjection` helper writes JSON values only when the field is
allowed.

## Mental model

A response surface has a single visibility policy: the audit log gets a
different set of fields than the public API, even when both render the
same row type. A `ProjectionPolicy` makes that visibility list
addressable: the same row type can flow through multiple policies.

```text
import projection

let public_field_policy = projection.projection_policy(
    "public",
    "issue",
    "list",
    None,
    ["id", "title", "severity", "created_at"])

let audit_field_policy = projection.projection_policy(
    "audit",
    "issue",
    "list",
    Some("audit.read"),
    ["id", "title", "severity", "created_at", "private_notes", "actor_id"])
```

| Field | Meaning |
|---|---|
| `name` | Stable policy id used in artifacts and tests. |
| `target` | Domain type the policy describes (e.g. `"issue"`). |
| `surface` | Response surface (`"list"`, `"detail"`, `"export"`, etc.). |
| `capability` | Optional gate, e.g. `Some("audit.read")`. |
| `fields` | The allow-list of field names. |

## Applying a projection

```text
import projection

let projector = projection.field_projection_for(public_field_policy)
let body = Json.empty_object()
projector.set_str(body, "id", row.id)
projector.set_str(body, "title", row.title)
projector.set_int(body, "created_at", row.created_at)
```

`FieldProjection` exposes typed `set_*` helpers (`set_str`, `set_int`,
`set_bool`, `set_array`, `set_object`, `set_object_as`,
`set_nullable_str`) plus bulk helpers (`project_str_fields`,
`project_int_fields`, `project_object_alias_fields`, `project_json_fields`)
for code that builds an object from parallel arrays.

`projection_policy_json(policy)` returns the policy as a JSON object, which
is what release artifacts ship.

## Using projections in a `json` declaration

```text
json issue_summary from IssueRow as row projected by public_field_policy {
    id: row.id
    title: row.title
    severity: row.severity
    private_notes? if has_capability(ctx, "audit.read"): row.private_notes
}
```

The `projected by` clause is the allow-list; per-field markers (`?`,
`?always`, `if expr`) decide whether allowed fields appear in a specific
response. A field not in the policy is omitted regardless of marker.
