Q123: files shared in a private conversation stay with its members
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
69b83d6b77
commit
91fa6b5f1e
11 files changed
+121
-10
No files matched your search
@@ -25,6 +25,11 @@ function harness() {
|
||||
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, owner_type, owner_id, created_at, scan_state)
|
||||
values (?, ?, ?, 'md', 1, ?, ?, 'employee', null, ?, ?)
|
||||
`).run(fileId, owner, name, createHash("sha256").update(content).digest("hex"), path, new Date().toISOString(), scanState);
|
||||
// As every recorded file is, it is readable across its company's workspace.
|
||||
connection.db.prepare(`
|
||||
insert into file_access_grants(id, org_id, file_id, granted_to_type, granted_to_id, granted_by_type, access_level, created_at)
|
||||
values (?, ?, ?, 'org_group', ?, 'system', 'manage', ?)
|
||||
`).run(`fgr_${randomUUID()}`, owner, fileId, `${owner}:workspace`, new Date().toISOString());
|
||||
return fileId;
|
||||
};
|
||||
const ctx: AgentToolContext = {
|
||||
|
||||
@@ -483,8 +483,8 @@ describe("domain services", () => {
|
||||
goal: "Ship a verified control plane.",
|
||||
});
|
||||
});
|
||||
const cleanFileId = fileService.importPath(orgId, cleanPath);
|
||||
const blockedFileId = fileService.importPath(orgId, secretPath);
|
||||
const cleanFileId = fileService.importPath(orgId, cleanPath, null);
|
||||
const blockedFileId = fileService.importPath(orgId, secretPath, null);
|
||||
|
||||
const state = snapshot.load();
|
||||
const cleanFile = state.files.find((file) => file.id === cleanFileId);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.mock("electron", () => ({ dialog: {}, shell: {} }));
|
||||
|
||||
const { DatabaseConnection } = await import("../../src/main/database/connection");
|
||||
const { OrganizationService } = await import("../../src/main/services/organization-service");
|
||||
const { ChatService } = await import("../../src/main/services/chat-service");
|
||||
const { FileService } = await import("../../src/main/services/file-service");
|
||||
const { dispatchAgentTool } = await import("../../src/main/services/agent-tools");
|
||||
|
||||
describe("files shared in a private conversation", () => {
|
||||
test("a file imported into a direct conversation is readable by its members only", () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-private-${randomUUID()}.db`));
|
||||
const organization = new OrganizationService(connection.db);
|
||||
const chat = new ChatService(connection.db);
|
||||
let orgId = "";
|
||||
let danaId = "";
|
||||
let eveId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = organization.createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
const providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
|
||||
connection.db.prepare("update providers set enabled = 1 where id = ?").run(providerId);
|
||||
danaId = organization.createAgent({ orgId, name: "Dana", role: "Engineer", department: "Engineering", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 1000, managerAgentId: null });
|
||||
eveId = organization.createAgent({ orgId, name: "Eve", role: "Engineer", department: "Engineering", modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 1000, managerAgentId: null });
|
||||
});
|
||||
const employeeId = (connection.db.prepare("select id from employees where org_id = ?").get(orgId) as { id: string }).id;
|
||||
const dm = connection.transaction(() => chat.ensureDirectSpace(orgId, { type: "employee", id: employeeId }, { type: "agent", id: danaId }));
|
||||
const source = join(tmpdir(), `ac-private-src-${randomUUID()}`);
|
||||
mkdirSync(source, { recursive: true });
|
||||
writeFileSync(join(source, "salary.md"), "Dana's raise: 10%");
|
||||
const artifacts = join(tmpdir(), `ac-private-art-${randomUUID()}`);
|
||||
const fileId = new FileService(connection.db, artifacts).importPath(orgId, join(source, "salary.md"), dm);
|
||||
|
||||
const deps = { db: connection.db, organization, chat };
|
||||
const as = (agentId: string) => ({ orgId, agent: { id: agentId, modelProviderId: null, modelId: null }, taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: null });
|
||||
expect(dispatchAgentTool(deps, as(danaId), { id: "c1", name: "read_attachment", arguments: { file_id: fileId } })).toContain("Dana's raise");
|
||||
expect(dispatchAgentTool(deps, as(eveId), { id: "c2", name: "read_attachment", arguments: { file_id: fileId } }))
|
||||
.toBe("ERROR: You have no access to salary.md; it was shared in a conversation you are not part of.");
|
||||
expect(dispatchAgentTool(deps, as(eveId), { id: "c3", name: "create_task", arguments: { title: "Leak", description: "d", input_file_ids: [fileId] } }))
|
||||
.toBe("ERROR: You have no access to salary.md; it was shared in a conversation you are not part of.");
|
||||
// A member cannot hand the file on to someone outside the conversation either.
|
||||
expect(dispatchAgentTool(deps, as(danaId), { id: "c4", name: "create_task", arguments: { title: "Pass on", description: "d", assignee_agent_name: "Eve", input_file_ids: [fileId] } }))
|
||||
.toBe("ERROR: Eve has no access to salary.md; it was shared in a conversation they are not part of. Share it with them or assign someone who has it.");
|
||||
expect(connection.db.prepare("select count(*) as n from tasks where title in ('Leak', 'Pass on')").get()).toEqual({ n: 0 });
|
||||
expect(connection.db.prepare("select granted_to_type as type from file_access_grants where file_id = ? order by granted_to_type").all(fileId))
|
||||
.toEqual([{ type: "agent" }, { type: "employee" }]);
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user