# User Profile Updates

## Goal

Make the existing gh-web Profile settings form persist user-controlled profile
metadata through AGS while preserving the identity-provider ownership boundary.
The GitHub-compatible endpoint is `PATCH /api/v3/user`.

## Problem

gh-web already sends `PATCH /api/v3/user`. AGS previously registered only
`GET /api/v3/user`, which returned HTTP 405 for every save attempt. In addition,
the OIDC and embedded-identity login flows refreshed both `name` and `email` on
each login. A naive write endpoint would make a display-name update appear to
succeed, then silently lose it on the next login.

## Ownership Model

| Field | Owner | Behavior |
| --- | --- | --- |
| `login`, account type, permissions, admin state | AGS account system | Never writable through this endpoint. |
| `email` | Configured identity provider | A request may repeat the current value for form compatibility, but a different value is rejected. |
| `name` | Identity provider until the user saves it; user afterwards | Saving `name`, including an empty value, sets `name_set_by_user`. Subsequent identity sync preserves it. |
| `bio`, `company`, `blog`, `location` | User | Writable profile metadata. |

Existing accounts begin with `name_set_by_user = false`, so provider display
name refreshes retain their previous behavior until a user explicitly changes
the profile name.

## API Contract

`PATCH /api/v3/user` accepts any subset of:

```json
{
  "name": "Hazel",
  "email": "identity@example.com",
  "bio": "Product manager",
  "company": "ClawMem",
  "blog": "https://example.com",
  "location": "Shanghai"
}
```

Omitted fields are unchanged. Empty strings clear mutable fields. Unknown
fields, malformed JSON, and trailing JSON values receive HTTP 400. Values that
exceed their persisted limits, and an email different from the current
identity-provider email, receive HTTP 422. The response is the normal private
authenticated-user shape; public user responses expose the public profile
fields but never email.

## Storage And Migration

The `users` table gains `name_set_by_user`, `company`, `blog`, and `location`.
AGS already includes `User` in its startup GORM migration, so standard
deployments add these columns through the existing migration path. Deployments
that deliberately set `AGS_SKIP_AUTO_MIGRATE` must run the equivalent schema
migration before enabling the new API.

## Verification

- REST tests prove a full update, partial updates, public/private response
  visibility, immutable email behavior, malformed/unknown input rejection, and
  unauthenticated rejection.
- OIDC and embedded-identity tests prove identity email continues to refresh
  while a user-managed name remains unchanged.
- The published `/api/v3/openapi.json` contract includes the PATCH operation.
