feat: merge opus comfyui and login updates

This commit is contained in:
boos
2026-05-21 16:32:04 +02:00
parent c16479da7c
commit 9c84759498
105 changed files with 20160 additions and 1034 deletions
@@ -0,0 +1,153 @@
## ADDED Requirements
### Requirement: Route rebuild reads from `model_availability`
The rebuild pipeline SHALL derive its working set from the
`model_availability` table joined to `accounts`, filtering out:
- accounts whose `status` is not `'active'`
- rows whose `available` is not `1` (treating `NULL` as not available)
- `(account, model)` combinations covered by `site_disabled_models`
for the account's `site_id`
The pipeline SHALL NOT make outbound HTTP requests to upstream platforms
during a rebuild. Refreshing `model_availability` is the responsibility
of the existing `POST /api/accounts/{id}/models/refresh` endpoint and
is out of scope for this requirement.
#### Scenario: Inactive account is excluded
- **WHEN** an account with `status = 'suspended'` has rows in
`model_availability`
- **THEN** no `token_routes` row is created on its behalf and no
`route_channels` row references its `account_id`
#### Scenario: Site-disabled model is excluded
- **WHEN** `site_disabled_models` contains `(site_id = 7, model_name = 'gpt-x')`
and an active account on site 7 has `model_availability` for `gpt-x`
- **THEN** the rebuild does not include that account in the channel set
for the `gpt-x` route
### Requirement: Rebuild aggregates one route per distinct model name
The rebuild SHALL ensure exactly one row exists in `token_routes` for
every distinct `model_name` in the filtered working set, keyed by
`model_pattern = model_name`. The rebuild SHALL reuse existing rows
when their `model_pattern` already matches; new rows SHALL be inserted
with `routing_strategy = 'weighted'`, `route_mode = 'pattern'`, and
`enabled = 1`.
The rebuild MUST NOT overwrite `display_name`, `display_icon`, or any
other fields on existing `token_routes` rows. Operators rely on these
fields being editable from the frontend without being clobbered on
re-runs.
#### Scenario: New model creates a new route
- **WHEN** model `claude-4-opus` appears in the working set and no
`token_routes` row has `model_pattern = 'claude-4-opus'`
- **THEN** the rebuild inserts a `token_routes` row with
`model_pattern = 'claude-4-opus'`, `routing_strategy = 'weighted'`,
`route_mode = 'pattern'`, `enabled = 1`
#### Scenario: Existing route is reused without overwrite
- **WHEN** a `token_routes` row exists with
`model_pattern = 'gpt-4'` and `display_name = 'GPT-4 Turbo (Custom)'`
- **THEN** the rebuild leaves `display_name` unchanged and reuses the
same `id` for channel reconciliation
### Requirement: Channel reconciliation preserves manual overrides
For each route, the rebuild SHALL reconcile `route_channels` rows
keyed by `(account_id, source_model)` against the desired set derived
from `model_availability`. Rows with `manual_override = 1` SHALL be
treated as immutable: not inserted, not updated, not deleted. Rows
with `manual_override = 0` (or `NULL`) SHALL be:
- inserted when missing from the existing set
- hard-deleted when no longer in the desired set
The desired set SHALL exclude any `(account, model)` combination
already covered by a `manual_override = 1` row, so the rebuild never
inserts an auto channel that shadows a manual one.
#### Scenario: Manual channel survives rebuild
- **WHEN** a `route_channels` row has `manual_override = 1` and its
`(account_id, source_model)` is no longer in the desired set
- **THEN** the row is left untouched and counted in
`RebuildSummary.skipped_manual_channels`
#### Scenario: Stale auto channel is deleted
- **WHEN** a `route_channels` row has `manual_override = 0`,
`account_id = 12`, `source_model = 'old-model'`, and no longer
appears in the desired set
- **THEN** the row is deleted and counted in
`RebuildSummary.deleted_channels`
#### Scenario: New auto channel is inserted with default token
- **WHEN** account 12 newly reports `gpt-5` available and has at least
one enabled `account_tokens` row
- **THEN** a new `route_channels` row is inserted with `route_id`
pointing at the `gpt-5` route, `account_id = 12`, the
default-flagged enabled token's id (or the lowest id when none is
default), `priority = 0`, `weight = 10`, `enabled = 1`,
`manual_override = 0`
#### Scenario: Account without tokens still gets a channel
- **WHEN** account 13 reports `gpt-5` available but has no enabled
`account_tokens` rows
- **THEN** a `route_channels` row is inserted with `token_id = NULL`,
relying on the proxy layer's account-level token fallback
### Requirement: Rebuild runs in a single transaction
The rebuild SHALL execute every read and write inside one database
transaction, committed at the end. A partial failure MUST NOT leave
`token_routes` and `route_channels` mutually inconsistent.
#### Scenario: Failure mid-rebuild rolls back
- **WHEN** an `INSERT` into `route_channels` fails partway through
the rebuild
- **THEN** the transaction aborts and no `token_routes` insert from
the same rebuild persists
### Requirement: Rebuild summary matches frontend contract
The rebuild SHALL return a `RebuildSummary` containing the fields
`createdRoutes`, `reusedRoutes`, `createdChannels`, `deletedChannels`,
`skippedManualChannels`, `scannedModels`, `scannedAccounts` (camelCase
when serialized). The `POST /api/routes/rebuild` handler SHALL embed
this object under the JSON key `rebuild`, alongside `success: true`.
`TokenRoutes.tsx:411-412` reads `rebuild.createdRoutes` and
`rebuild.createdChannels`; both MUST be present and SHALL count only
rows actually inserted by the current rebuild (not pre-existing rows
that were reused).
#### Scenario: Rebuild response shape
- **WHEN** the handler completes successfully
- **THEN** the response body is
`{ "success": true, "rebuild": { "createdRoutes": <n1>,
"reusedRoutes": <n2>, "createdChannels": <n3>,
"deletedChannels": <n4>, "skippedManualChannels": <n5>,
"scannedModels": <n6>, "scannedAccounts": <n7> } }`
#### Scenario: Reused route does not increment createdRoutes
- **WHEN** every model in the working set has a pre-existing
`token_routes` row
- **THEN** `createdRoutes = 0` and `reusedRoutes` equals the number of
distinct models scanned
### Requirement: Auto-rebuild after account creation
The system SHALL fire one route rebuild in the background after
`POST /api/accounts` returns success for at least one created
account. The HTTP response SHALL return without waiting for the
rebuild to finish. Errors from the background rebuild SHALL be logged
at `warn` level and SHALL NOT affect the response status or body.
#### Scenario: Single-account create triggers rebuild
- **WHEN** `POST /api/accounts` creates exactly one account
- **THEN** `spawn_route_rebuild` is invoked once with the cloned
`AppState` and the response returns immediately
#### Scenario: Batch-create with all failures does not trigger
- **WHEN** `POST /api/accounts` returns with `created.len() == 0` and
`failed.len() > 0`
- **THEN** `spawn_route_rebuild` is not invoked