# Account operations

Forge gives platform administrators a bounded operations view for growth,
usage, and abuse response. It is an operational control surface, not a billing
ledger and not a destructive user-management tool.

## What the dashboard measures

The dashboard supports 7-day, 30-day, and 90-day UTC windows. It shows:

- active-user and account growth, excluding tombstoned users and closed
  accounts;
- repositories, Gists, Deploy projects, deployments, and retained asset bytes;
- build outcomes and the highest observed request-limit utilization;
- top resource-owning accounts, ranked by stored bytes and then active
  projects;
- active operator warnings and named abuse signals.
- bounded authenticated-account IP-churn evidence.

Daily series include zero-value days. Forge keeps units separate: bytes,
projects, builds, deployments, and app-scoped identity records are not added
into a meaningless combined usage score. The Forge Identity user count is the
number of app-scoped active-user records, so one human can appear in multiple
applications.

Request and bandwidth telemetry that lives in Cloudflare Analytics Engine is
reported separately from D1-backed product state. The request-pressure value is
the largest observed limiter counter for the window; it is not a count of HTTP
429 responses.

## Named signals

The initial signals are intentionally explicit and versionable:

| Signal | Initial threshold |
| --- | ---: |
| Request-limit utilization | 80% |
| Failed deployments in the selected window | 3 |
| Active critical operator warnings | 1 |
| Distinct IP fingerprints in one UTC hour | 8 |

Crossing a signal is evidence for review. It never deletes an account or
silently changes its service tier.

### IP-churn privacy and bounds

Forge does not use the `rate_limits.ip` first-address field as abuse history.
For authenticated personal accounts, it derives an HMAC-SHA-256 fingerprint
from the account, UTC hour, and connecting address. Fingerprints cannot be
correlated across accounts or hours, and raw addresses are never written to
the churn table, API response, dashboard, or failure log.

Each account, rate-limit class, and UTC hour retains at most 16 fingerprints.
Further distinct addresses set an overflow bit, which the dashboard renders as
`16+` instead of claiming an exact count. Rows expire after 90 days. The
minute maintenance loop deletes expired rows in batches of at most 500, while
active accounts also remove their own expired rows when a new hourly window is
created. The dashboard shows only the highest-pressure hour per account in the
selected 7-, 30-, or 90-day view.

The signal is contextual evidence, not proof of abuse: VPN rotation, mobile
networks, NAT, and distributed CI can all legitimately increase it. Operators
should inspect account behavior before issuing a warning or temporary cap.

## Warnings

An administrator can attach a `notice`, `warning`, or `critical` warning to an
account. Every warning includes a machine-readable code, a user-facing message,
an internal reason, the actor, an idempotency key, and optional start/end
timestamps.

Warnings and their resolutions are append-only. Resolution adds a new audit
record instead of rewriting the warning. Active warnings for a personal account
are summarized on that user's authenticated responses:

- `X-Forge-Account-Warning-Count`, including `0` after resolution;
- `X-Forge-Account-Warning-Severity` when at least one warning is active;
- `X-Forge-Account-Warning-Codes` when at least one warning is active.

The internal operator reason is never placed in response headers. Organization
warnings are visible in the administrator account view and are not leaked to
every organization member.

Signed-in users can review the active messages for their own personal account
from Settings. The backing endpoint is scoped by the authenticated user rather
than a caller-supplied account id:

```http
GET /api/accounts/me/warnings
```

It returns only `code`, `severity`, `message`, `startsAt`, and `endsAt` for
currently active, unresolved warnings. It never selects or serializes the
operator reason, audit actor, warning id, personal account id, or warnings that
belong to organizations and other users. Responses are marked
`Cache-Control: private, no-store`.

## Temporary request caps

An administrator may temporarily lower one existing personal-account request
budget. A cap must:

- name an existing Forge rate-limit class;
- retain that class's normal fixed window;
- be strictly lower than its normal request allowance;
- expire within 30 days;
- include an actor, reason, and idempotency key.

The request middleware consumes the active cap, so response limit headers and
actual rejection behavior agree. `X-Forge-RateLimit-Override` identifies the
active override. Clearing a cap creates another append-only record and restores
the normal allowance; it does not delete the original evidence.

Organization caps are rejected for now. The current limiter is keyed by the
authenticated user, so applying an organization cap would be misleading until
request traffic is reliably attributed to a resource-owning account.

## Safety boundary

These controls warn and throttle. They do not delete users, repositories, or
deployment history. Suspension and tombstoning remain separate lifecycle
operations with their own authorization-generation and cleanup procedures.
