M3: BYOK — encrypted owner-only API configs + model adapters
SharedKernel: Autonomy dial enum; IModelClient (ModelRequest/ModelCompletion);
IApiConfigResolver (+ ApiConfigSummary/ResolvedApiConfig) — server-side, decrypted.
Integrations module:
- ApiConfig entity (org-scoped) + IntegrationsDbContext (schema "integrations") +
InitialIntegrations migration; the key is AES-256-GCM encrypted at rest (key derived from
Encryption:MasterKey) and never returned to a client.
- Model adapters: StubModelClient (no-network, provider "stub"/"echo"), an OpenAI-compatible
HTTP adapter, and a ModelClientRouter; ApiConfigResolver decrypts server-side only.
- Endpoints: POST/GET/DELETE /api/integrations/api-configs and POST .../{id}/test. Create/
test/delete require ManageApiKeys (owner); listing requires ConfigureAgents (assign-only,
no key). Dev master key in appsettings; override Encryption__MasterKey in prod.
Verified: build green; ArchitectureTests 8/8 (Integrations references only SharedKernel);
IntegrationTests 26/26 incl. a BYOK flow — key never appears in any response, the connection
test succeeds (stub), and a Member is 403'd from create + list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
namespace TeamUp.SharedKernel.Access;
|
||||
|
||||
/// <summary>
|
||||
/// The per-seat autonomy dial, set by the team owner. The action gate (M5) compares it to an
|
||||
/// action's risk to decide execute-vs-hold. Stored on the Agent (M3); evaluated in Governance.
|
||||
/// </summary>
|
||||
public enum Autonomy
|
||||
{
|
||||
DraftOnly,
|
||||
Gated,
|
||||
Autonomous,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace TeamUp.SharedKernel.Ai;
|
||||
|
||||
/// <summary>Non-sensitive BYOK config info (no key) — safe to list/return to clients.</summary>
|
||||
public sealed record ApiConfigSummary(Guid Id, string Name, string Provider, string Model);
|
||||
|
||||
/// <summary>A resolved config including the decrypted key. Server-side only — never serialized to a client.</summary>
|
||||
public sealed record ResolvedApiConfig(Guid Id, string Name, string Provider, string Model, string? Endpoint, string ApiKey);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a BYOK config (decrypting the key) for server-side use — the M3 connection test and the
|
||||
/// M4 assembler. Implemented by Integrations; the decrypted key never leaves the server.
|
||||
/// </summary>
|
||||
public interface IApiConfigResolver
|
||||
{
|
||||
Task<ResolvedApiConfig?> ResolveAsync(Guid apiConfigId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace TeamUp.SharedKernel.Ai;
|
||||
|
||||
/// <summary>One model invocation. The key is passed explicitly (BYOK, server-side only).</summary>
|
||||
public sealed record ModelRequest(
|
||||
string Provider,
|
||||
string Model,
|
||||
string ApiKey,
|
||||
string? Endpoint,
|
||||
string Prompt,
|
||||
int MaxTokens = 256);
|
||||
|
||||
public sealed record ModelCompletion(bool Success, string? Text, string? Error, long LatencyMs);
|
||||
|
||||
/// <summary>
|
||||
/// Provider-agnostic model client. Implemented in Integrations (a router over per-provider HTTP
|
||||
/// adapters). Used by the M3 BYOK test call and the M4 assembler. BYOK — never resells tokens.
|
||||
/// </summary>
|
||||
public interface IModelClient
|
||||
{
|
||||
Task<ModelCompletion> CompleteAsync(ModelRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
Reference in New Issue
Block a user