Context
#718 enforced a single-trust-domain model: in hosted mode the shared, machine-wide credential + GitHub-PAT mutation endpoints fail closed (403), and hosted tenants supply keys via per-instance env vars. This issue tracks the richer alternative — letting each hosted tenant store/manage their own credentials through the API.
Why deferred from #718
CredentialManager is machine-wide (keyring / encrypted-file keyed by provider, no user dimension). Per-user scoping is a cross-cutting rework, not a one-session change.
Acceptance criteria
Anchors
codeframe/core/credentials.py — CredentialManager (machine-wide today)
codeframe/ui/dependencies.py — forbid_shared_credentials_in_hosted_mode (remove once scoped)
codeframe/ui/routers/settings_v2.py, github_integrations_v2.py — credential endpoints
Context
#718 enforced a single-trust-domain model: in hosted mode the shared, machine-wide credential + GitHub-PAT mutation endpoints fail closed (403), and hosted tenants supply keys via per-instance env vars. This issue tracks the richer alternative — letting each hosted tenant store/manage their own credentials through the API.
Why deferred from #718
CredentialManageris machine-wide (keyring / encrypted-file keyed by provider, no user dimension). Per-user scoping is a cross-cutting rework, not a one-session change.Acceptance criteria
CredentialManagerkeys credentials byuser_id(keyring service name + encrypted-file store), with a migration for existing machine-wide entries.auth["user_id"]and operate on the caller's own credentials only.Anchors
codeframe/core/credentials.py— CredentialManager (machine-wide today)codeframe/ui/dependencies.py—forbid_shared_credentials_in_hosted_mode(remove once scoped)codeframe/ui/routers/settings_v2.py,github_integrations_v2.py— credential endpoints