forked from boos/BoosAPI
feat: merge opus comfyui and login updates
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
# route-rebuild-pipeline Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-route-rebuild-pipeline. Update Purpose after archive.
|
||||
## 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
|
||||
|
||||
Reference in New Issue
Block a user