# Commercial Backend Contracts

This document fixes the client-side contract expected by the desktop commercial layer.
The current implementation is still scaffold/local-first, but the DTO fields below must stay stable when the real backend is connected.

## Session

### `POST /auth/login`

Client constant: `AUTH_LOGIN_PATH = "/auth/login"`.

Input:
- `email`
- `password` or external auth token
- `device_id`
- `device_name`

Output:
- `access_token`
- `refresh_token`
- `account_email`
- `display_name`
- `plan_code`
- `subscription_status`
- `expires_at`
- `offline_until`
- `entitlements[]`
- `device_id`
- `device_name`
- `device_limit`
- `device_used`
- `billing_url`

### `POST /auth/refresh`

Client constant: `AUTH_REFRESH_PATH = "/auth/refresh"`.

Input:
- `refresh_token`
- `device_id`

Output:
- same session fields as login

## License Envelope

### `GET /license/current`

Client constant: `LICENSE_CURRENT_PATH = "/license/current"`.

Input:
- bearer token
- `device_id`

Output:
- `account_email`
- `plan_code`
- `subscription_status`
- `expires_at`
- `offline_until`
- `valid_until`
- `offline_grace_until`
- `entitlements[]`
- `device_id`
- `device_name`
- `device_limit`
- `device_used`
- `issued_at`
- `license_id`
- `billing_url`
- `source`
- `signature`

Rules:
- `valid_until` is the commercial subscription boundary.
- `offline_grace_until` is the last moment a cached envelope may be used without a server refresh.
- Expired `valid_until` blocks product operations even if the local session still exists.
- Expired cached `offline_grace_until` blocks product operations until the license is refreshed online.
- `signature` is scaffold-only now, but the field is reserved for signed envelopes.
- Invalid `signature` blocks cached and gateway envelopes before entitlement checks.
- `device_used > device_limit` blocks protected operations with `device_limit_exceeded`.

Client policy status labels:
- `not_logged_in`
- `trial`
- `active`
- `expired`
- `offline_grace`
- `offline_expired`
- `invalid_license`
- `device_limit_exceeded`

## Entitlements

Required entitlement codes:
- `sorting.run`
- `control_sheets.generate`
- `pdf.export`
- `profiles.manage`
- `settings.edit_catalog`
- `updates.receive`

Client operations must check entitlement plus usable subscription state.

## Device Registry

### `POST /devices/register`

Client constant: `DEVICE_REGISTER_PATH = "/devices/register"`.

Input:
- `account_email`
- `device_id`
- `device_name`

Output:
- `device_id`
- `device_name`
- `device_limit`
- `device_used`
- `status`

Rules:
- Device limit defaults to `3`.
- If the backend rejects a device because the limit is reached, the license status must remain non-usable for protected operations.

## Billing

### `POST /billing/checkout-link`

Client constant: `BILLING_CHECKOUT_PATH = "/billing/checkout-link"`.

Input:
- `account_email`
- `plan_code`
- `return_url`

Output:
- `ok`
- `url`
- `message`

The desktop app currently exposes this through `BillingGateway.create_checkout_link()`.

## Local Scaffold Fallback

Until `ApiClient(base_url=...)` is configured, gateways must not block local development:
- `SessionGateway.refresh()` returns the current session for logged-in local sessions.
- `LicenseGateway.fetch_for_session()` builds a session-backed scaffold envelope.
- `DeviceGateway.register()` returns an active local registration.
- `BillingGateway.create_checkout_link()` returns `session.billing_url` or a `scaffold://billing/checkout?...` URL.

When `base_url` is configured, gateway tests expect calls to the endpoint constants above and DTO parsing into the current client dataclasses.

## Client Configuration

Commercial backend connection is intentionally separated from editable business profiles.

Local file:
- `config/commercial_settings.json`

Fields:
- `api_base_url`
- `request_timeout_seconds`
- `backend_enabled`

Rules:
- Product/profile/price settings must not contain auth tokens, license cache, device identity, or backend endpoint state.
- `backend_enabled=false` forces local scaffold fallback even if `api_base_url` is present.
- `backend_enabled=true` wires all commercial gateways through one shared `ApiClient`.
- Runtime session/cache/device files remain separate from editable business profile JSON.

## Legacy Launch Guard

The old local `app.launch_guard.check_launch()` is no longer a commercial authority path.

Rules:
- It is wrapped by `LegacyLaunchGuardService`.
- Its result may produce a startup warning.
- It must not set `BootstrapResult.can_start=false`.
- It must not grant or deny paid features.
- Paid feature access is decided only by account session, license policy, entitlements, and backend/device state.

## Diagnostics Export

Desktop support diagnostics are exported by `DiagnosticsService`.

Rules:
- Export format is JSON.
- `schema_version` is currently `1`.
- Full auth tokens are never exported.
- Email and device/license identifiers are masked.
- Export contains backend configuration presence, not the full API URL.
- Export contains account/license status labels, policy reason, entitlement text, device usage, and billing availability.
- Export is available from the account window.
