Give every task a bot: drop shared workspace tasks

A workspace task with no bot was seen by every bot working there. Now a
task can't be saved without a bot, a worker reaches only its own (the
workspace admin and the coordinator still reach all), the workspace page
picks who has a new task, and a bot leaving a workspace or deleted passes
its tasks there to the workspace's admin bot, else the coordinator (also
at boot for rows without a bot).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 06:26:44 +01:00
co-authored by Claude Opus 5.5
parent 3e78d51b56
commit 623112fd19
30 changed files with 239 additions and 106 deletions
+5 -6
View File
@@ -19,11 +19,10 @@ type Params = {
};
/**
* The tasks a bot's run reaches: its own list, and in every workspace it
* works in the tasks it has there plus the shared ones nobody has yet.
* Another bot's task in a workspace is that bot's. A workspace admin
* reaches every task in that workspace; the coordinator every task in the
* install.
* The tasks a bot's run reaches. Every task belongs to one bot: a worker
* reaches its own list and the tasks it has in the workspaces it still
* works in, never another bot's. A workspace admin also reaches every task
* in that workspace; the coordinator every task in the install.
*/
export default function botTaskScopesSql({
bot_id,
@@ -42,7 +41,7 @@ export default function botTaskScopesSql({
sql: `(
(${alias}.bot_id = ? AND ${alias}.workspace_id IS NULL)
OR (${alias}.workspace_id IS NOT NULL
AND (${alias}.bot_id IS NULL OR ${alias}.bot_id = ?)
AND ${alias}.bot_id = ?
AND ${alias}.workspace_id IN (SELECT workspace_id FROM workspace_bots WHERE bot_id = ?))
${admin_sql}
)`,
+5 -5
View File
@@ -21,7 +21,7 @@ beforeAll(() => {
db.run(`CREATE TABLE tasks (id INTEGER PRIMARY KEY, bot_id INTEGER, workspace_id INTEGER, created_by_bot_id INTEGER)`);
db.run(`INSERT INTO tasks VALUES
(100, 1, NULL, NULL), (101, 2, NULL, NULL), (102, 3, NULL, NULL),
(110, NULL, 10, NULL), (111, NULL, 11, NULL),
(110, 3, 10, NULL), (111, 1, 11, NULL),
(112, 3, 11, NULL), (113, 2, 11, NULL)`);
});
@@ -54,13 +54,13 @@ describe("taskScopesSql", () => {
});
describe("botTaskScopesSql", () => {
test("a worker reaches its own list, its tasks in its workspaces and the shared ones there", () => {
expect(ids({ condition: botTaskScopesSql({ bot_id: 3, is_coordinator: false }) })).toEqual([102, 110, 111, 112]);
test("a worker reaches its own list and its tasks in its workspaces, never another bot's there", () => {
expect(ids({ condition: botTaskScopesSql({ bot_id: 3, is_coordinator: false }) })).toEqual([102, 110, 112]);
});
test("another bot's task in a workspace stays that bot's", () => {
db.run(`INSERT INTO workspace_bots VALUES (11, 2)`);
expect(ids({ condition: botTaskScopesSql({ bot_id: 2, is_coordinator: false }) })).toEqual([101, 111, 113]);
expect(ids({ condition: botTaskScopesSql({ bot_id: 2, is_coordinator: false }) })).toEqual([101, 113]);
db.run(`DELETE FROM workspace_bots WHERE bot_id = 2`);
});
@@ -81,7 +81,7 @@ describe("botTaskScopesSql for a workspace admin", () => {
expect(ids({ condition: botTaskScopesSql({ bot_id: 4, is_coordinator: false, admin_workspace_ids: [11] }) })).toEqual([
111, 112, 113,
]);
expect(ids({ condition: botTaskScopesSql({ bot_id: 4, is_coordinator: false }) })).toEqual([111]);
expect(ids({ condition: botTaskScopesSql({ bot_id: 4, is_coordinator: false }) })).toEqual([]);
db.run(`DELETE FROM workspace_bots WHERE bot_id = 4`);
});
});
+2 -1
View File
@@ -10,7 +10,8 @@ export type SqlCondition = {
/**
* Where a task is: a bot's own list (`bot_id` alone), or a workspace
* (`workspace_id`, with the bot that has it, if any).
* (`workspace_id`, with the `bot_id` that has it there). Every task has a
* bot.
*/
export type TaskScope = {
bot_id?: number | null;