feat(credentials): make provider substitution and runtime auth mode visible
ci / gates (push) Successful in 8s
ci / rust (push) Failing after 11s
ci / frontend (push) Failing after 22s
ci / e2e (push) Skipped
ci / publish (push) Skipped

Three guardrails around which credential pays for what.

1. Boot announces the mission-runtime auth mode, and warns when subscription
   auth is configured on a deployment with more than one user. A consumer
   subscription credential may only run the account holder's own work, and
   that condition is otherwise invisible -- it holds today and quietly stops
   holding the first time someone else signs up. Adds users::count_all
   (dynamic query, so the offline cache needs no regeneration).

2. Reject an ANTHROPIC_API_KEY shaped like a subscription OAuth token
   (sk-ant-oat...) at boot rather than failing on the first model call far
   from the mistake. Both credentials start sk-ant-, so the confusion is easy
   to make and hard to spot.

3. provider_alias_for's GLM/Kimi -> anthropic.default fallback was documented
   as deliberate but was silent in effect: a user picking "kimi" in the UI got
   an agent spending the Anthropic key, with nothing saying so. It now logs
   the substitution, and is_exact_provider_match() lets callers tell a real
   family match from a substitution so a UI can say which model will actually
   run. Behaviour is unchanged -- only the silence is.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-07-30 19:41:19 -07:00
co-authored by Claude Opus 5
parent af44c92dd6
commit ca45597c79
3 changed files with 102 additions and 3 deletions
+15
View File
@@ -92,6 +92,21 @@ pub async fn owner_of_workspace(
}
/// Members table for the Team page (§8.3), in join order.
/// Total users across the whole deployment, not scoped to a workspace.
///
/// Used by the boot-time credential check: a consumer subscription credential
/// may only run the account holder's own work, so a deployment configured for
/// subscription auth with more than one user needs a warning.
/// Dynamic rather than `query!` so the offline query cache doesn't need
/// regenerating for a one-off count.
pub async fn count_all(pool: &PgPool) -> Result<i64, DbError> {
use sqlx::Row;
let row = sqlx::query("SELECT count(*) AS n FROM users")
.fetch_one(pool)
.await?;
Ok(row.try_get::<i64, _>("n").unwrap_or(0))
}
pub async fn list_by_workspace(
pool: &PgPool,
workspace_id: WorkspaceId,