Scope provider requests to workspaces
This commit is contained in:
1 parent
ee5eff7725
commit
335d57b77f
15 files changed
+144
-16
No files matched your search
@@ -23,7 +23,7 @@ describe("domain services", () => {
|
||||
const connection = createConnection();
|
||||
|
||||
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]);
|
||||
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ describe("migration service", () => {
|
||||
create table chat_presence(principal_type text, principal_id text);
|
||||
create table chat_read_state(org_id text, employee_id text, space_id text, last_read_message_id text);
|
||||
create table providers(id text primary key, org_id text, credential_id text);
|
||||
create table provider_requests(id text primary key, org_id text, provider_id text);
|
||||
create table provider_requests(id text primary key, org_id text, provider_id text, workspace_id text);
|
||||
create table provider_usage_windows(id text primary key, org_id text, provider_id text);
|
||||
create table provider_rate_limit_observations(id text primary key, org_id text, provider_id text);
|
||||
create table credentials(id text primary key, org_id text);
|
||||
@@ -225,7 +225,7 @@ describe("migration service", () => {
|
||||
insert into board_interventions(id, org_id, thread_id, message_id, approval_id) values ('bdi-1', 'org-1', 'thread-1', 'msg-1', 'approval-1');
|
||||
insert into chat_presence(principal_type, principal_id) values ('agent', 'agent-1');
|
||||
insert into chat_read_state(org_id, employee_id, space_id, last_read_message_id) values ('org-1', 'emp-1', 'space-1', 'msg-1');
|
||||
insert into provider_requests(id, org_id, provider_id) values ('req-1', 'org-1', 'prov-1');
|
||||
insert into provider_requests(id, org_id, provider_id, workspace_id) values ('req-1', 'org-1', 'prov-1', 'wsp-1');
|
||||
insert into provider_usage_windows(id, org_id, provider_id) values ('usage-1', 'org-1', 'prov-1');
|
||||
insert into provider_rate_limit_observations(id, org_id, provider_id) values ('quota-1', 'org-1', 'prov-1');
|
||||
insert into skill_definitions(id, org_id) values ('skill-1', 'org-1');
|
||||
|
||||
@@ -142,11 +142,11 @@ describe("provider service streaming", () => {
|
||||
providers.saveCredential({ orgId, providerId: openAiProviderId, secret: "sk-live-provider", model: "gpt-5.4" });
|
||||
connection.db.prepare(`
|
||||
insert into provider_requests(
|
||||
id, org_id, provider_id, provider_key, model, prompt_sha256,
|
||||
id, org_id, provider_id, provider_key, workspace_id, model, prompt_sha256,
|
||||
response_text, raw_response_json, state, error_message, input_char_count,
|
||||
output_char_count, execution_mode, policy_decision, external_response_id,
|
||||
stream_event_count, cancelled_at, created_at, completed_at
|
||||
) values (?, ?, ?, 'openai', 'gpt-5.4', ?, 'partial', '[]', ?, null, 6, 7, ?, 'allowed:standard', ?, 2, null, ?, null)
|
||||
) values (?, ?, ?, 'openai', null, 'gpt-5.4', ?, 'partial', '[]', ?, null, 6, 7, ?, 'allowed:standard:organization', ?, 2, null, ?, null)
|
||||
`).run(
|
||||
requestId,
|
||||
orgId,
|
||||
|
||||
@@ -380,7 +380,7 @@ describe("provider service", () => {
|
||||
inputCharCount: 16,
|
||||
outputCharCount: 25,
|
||||
executionMode: PROVIDER_EXECUTION_MODE.FOREGROUND,
|
||||
policyDecision: "allowed:standard",
|
||||
policyDecision: "allowed:standard:organization",
|
||||
});
|
||||
expect(state.providerRateLimits.find((item) => item.providerId === openAiProviderId)).toMatchObject({
|
||||
providerId: openAiProviderId,
|
||||
@@ -394,6 +394,68 @@ describe("provider service", () => {
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("requires workspace context for workspace-scoped provider execution", async () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const snapshot = new SnapshotService(connection.db);
|
||||
const adapters: ProviderAdapterRegistry = {
|
||||
...providerAdapters,
|
||||
openai: {
|
||||
...providerAdapters.openai,
|
||||
parseAuthProfile: (secret) => ({ type: "api_key", apiKey: secret.trim() }),
|
||||
checkHealth: async () => ({ checkedAt: new Date().toISOString(), rateLimitState: null }),
|
||||
createResponse: async ({ prompt }) => ({ text: `accepted:${prompt}`, rawResponseJson: "{}", rateLimitState: null }),
|
||||
},
|
||||
};
|
||||
const providers = new ProviderService(connection.db, adapters, testVault);
|
||||
let orgId = "";
|
||||
let openAiProviderId = "";
|
||||
let workspaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = createOrganization(organization);
|
||||
openAiProviderId = providerId(connection, orgId, "openai");
|
||||
workspaceId = "wsp_release";
|
||||
const now = new Date().toISOString();
|
||||
connection.db.prepare(`
|
||||
insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at)
|
||||
values (?, ?, ?, ?, ?, ?, null, ?, ?, ?)
|
||||
`).run(workspaceId, orgId, "Release", "C:/work/release", "https://example.com/release.git", "main", 1, now, now);
|
||||
providers.saveCredential({ orgId, providerId: openAiProviderId, secret: "sk-live-provider", model: "gpt-5.4" });
|
||||
providers.updatePolicy({
|
||||
orgId,
|
||||
providerId: openAiProviderId,
|
||||
dataPolicy: PROVIDER_DATA_POLICY.STANDARD,
|
||||
backgroundEnabled: false,
|
||||
monthlyRequestLimit: 10,
|
||||
workspaceScope: "workspace",
|
||||
});
|
||||
});
|
||||
|
||||
await expect(providers.createResponse({
|
||||
orgId,
|
||||
providerId: openAiProviderId,
|
||||
prompt: "Draft workspace memo",
|
||||
instructions: null,
|
||||
})).rejects.toThrow("Provider workspace scope requires a workspace context.");
|
||||
expect(connection.db.prepare("select count(*) as count from provider_requests").get()).toEqual({ count: 0 });
|
||||
|
||||
await providers.createResponse({
|
||||
orgId,
|
||||
providerId: openAiProviderId,
|
||||
workspaceId,
|
||||
prompt: "Draft workspace memo",
|
||||
instructions: null,
|
||||
});
|
||||
|
||||
expect(snapshot.load().providerRequests[0]).toMatchObject({
|
||||
providerId: openAiProviderId,
|
||||
workspaceId,
|
||||
policyDecision: `allowed:standard:workspace:${workspaceId}`,
|
||||
});
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("blocks background execution under zero data retention policy", async () => {
|
||||
const connection = createConnection();
|
||||
const organization = new OrganizationService(connection.db);
|
||||
|
||||
@@ -50,6 +50,7 @@ describe("task execution service", () => {
|
||||
let orgId = "";
|
||||
let providerId = "";
|
||||
let taskId = "";
|
||||
let workspaceId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({
|
||||
@@ -60,6 +61,12 @@ describe("task execution service", () => {
|
||||
});
|
||||
providerId = openAiProviderId(connection, orgId);
|
||||
providers.saveCredential({ orgId, providerId, secret: "sk-live-provider", model: "gpt-5.4" });
|
||||
workspaceId = "wsp_task_execution";
|
||||
const now = new Date().toISOString();
|
||||
connection.db.prepare(`
|
||||
insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at)
|
||||
values (?, ?, ?, ?, ?, ?, null, ?, ?, ?)
|
||||
`).run(workspaceId, orgId, "Task Workspace", "C:/work/task", "https://example.com/task.git", "main", 1, now, now);
|
||||
const agentId = organization.createAgent({
|
||||
orgId,
|
||||
name: "Ada",
|
||||
@@ -79,7 +86,7 @@ describe("task execution service", () => {
|
||||
risk: RISK_LEVEL.MEDIUM,
|
||||
projectId: null,
|
||||
goalId: null,
|
||||
workspaceId: null,
|
||||
workspaceId,
|
||||
priority: TASK_PRIORITY.HIGH,
|
||||
requiresFilesystem: false,
|
||||
});
|
||||
@@ -91,9 +98,10 @@ describe("task execution service", () => {
|
||||
const request = state.providerRequests.find((item) => item.id === run?.providerRequestId);
|
||||
|
||||
expect(run).toMatchObject({ taskId, agentId: expect.any(String), providerId, state: "completed", errorMessage: null });
|
||||
expect(request).toMatchObject({ providerId, state: "succeeded", responseText: "executed:gpt-5.4" });
|
||||
expect(request).toMatchObject({ providerId, workspaceId, state: "succeeded", responseText: "executed:gpt-5.4" });
|
||||
expect(capturedPrompt).toContain("Task: Draft launch checklist");
|
||||
expect(capturedPrompt).toContain("Assignee: Ada (Execution lead)");
|
||||
expect(capturedPrompt).toContain("Workspace: Task Workspace");
|
||||
expect(state.auditEvents.some((event) => event.entityType === "task_run" && event.action === "completed")).toBe(true);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user