| `internal/api/handlers_backups.go` | `handleInternalBackup` decodes the optional `os_user` body (gated on `ContentLength`, not `Content-Type`) and passes it as the audit actor; defaults `break-glass` |
| `internal/api/handlers_backup_now_test.go` | **+subtest** in `TestInternalBackup`: `os_user` body attributes the audit to the operator |
| `docs/openapi.yaml` | document the `internalBackupNow` optional `os_user` request body |
## Verification
WSL oracle (go1.26.4, FedoraLinux-44, authoritative for Go):
```
go build ./... && go vet ./... && go test ./... → ALL_GREEN
```
`cmd/felis` and `internal/api` both re-ran (not cached), so the new `backupnow_test.go`
and the added `TestInternalBackup` subtest executed. `TestOpenAPIMatchesServedRoutes`
still passes: the `os_user` body is an addition to an already-documented operation, so
the served⇔documented route match is unchanged. The core is covered against a real
`fake.Client` (resolves the Service/Secret) + `httptest.Server` (asserts the wire
request and maps every status), so the tests exercise persisted/observable behaviour,
not merely that a call was made. `tui_backupnow.go` is thin glue, untested per the
`tui_halt.go` convention.
## Self-review outcome
-**ponytail (over-engineering):** lean — no new abstraction beyond the four core
functions two call sites (test + TUI) already justify; the picker reuses halt's
server-list core rather than cloning it; the deliberate rejection of a `not_stopped`
soft-landing path kept the state machine at four steps. Nothing cut.
-**correctness:** the `os_user` decode is gated on `ContentLength`, matching how
`decodeJSON` actually works (no `Content-Type` check), so a header-less console still
attributes correctly; the stopped-gate and audit stay single-sourced server-side, so
the peer cannot drift from the endpoint on the RWO safety check or the audit record.