feat: bind AgentBus work to account workers

This commit is contained in:
inman committed 2026-09-02 15:09:07 +08:00
1 parent d034f649c4
commit 6f9fd0f0bd
31 files changed
+1119 -257

No files matched your search

+21 -15
View File
@@ -47,18 +47,18 @@ test('message routing starts a new session for a business directive, not for a s
test('task access contract isolates users and team leads while preserving administrator and worker access', () => {
const cases = [
{ name: 'administrator sees another user manual task', access: { userId: 'admin', role: 'admin' as const }, createdBy: 'user-a', source: 'manual' as const, allowed: true },
{ name: 'trusted worker sees AgentBus task', access: { userId: '', role: undefined }, createdBy: null, source: 'agentbus' as const, allowed: true },
{ name: 'team lead sees own manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'lead-a', source: 'manual' as const, allowed: true },
{ name: 'team lead cannot use the normal task path for another manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'user-b', source: 'manual' as const, allowed: false },
{ name: 'team lead cannot use the normal task path for AgentBus work', access: { userId: 'lead-a', role: 'team_lead' as const }, createdBy: 'lead-a', source: 'agentbus' as const, allowed: false },
{ name: 'ordinary user sees own manual task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-a', source: 'manual' as const, allowed: true },
{ name: 'ordinary user cannot see another manual task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-b', source: 'manual' as const, allowed: false },
{ name: 'ordinary user cannot see AgentBus task', access: { userId: 'user-a', role: 'user' as const }, createdBy: 'user-a', source: 'agentbus' as const, allowed: false },
{ name: 'ordinary user without an actor cannot see a task', access: { userId: '', role: 'user' as const }, createdBy: '', source: 'manual' as const, allowed: false }
{ name: 'administrator can inspect another assigned task', access: { userId: 'admin', role: 'admin' as const }, assignedUserId: 'user-a', allowed: true },
{ name: 'trusted worker can inspect an unassigned task', access: { userId: '', role: undefined }, assignedUserId: null, allowed: true },
{ name: 'team lead sees own manual task', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'lead-a', allowed: true },
{ name: 'team lead cannot use the normal task path for another task', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'user-b', allowed: false },
{ name: 'team lead sees own AgentBus work', access: { userId: 'lead-a', role: 'team_lead' as const }, assignedUserId: 'lead-a', allowed: true },
{ name: 'ordinary user sees own manual task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-a', allowed: true },
{ name: 'ordinary user cannot see another task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-b', allowed: false },
{ name: 'ordinary user sees own AgentBus task', access: { userId: 'user-a', role: 'user' as const }, assignedUserId: 'user-a', allowed: true },
{ name: 'ordinary user without an actor cannot see a task', access: { userId: '', role: 'user' as const }, assignedUserId: '', allowed: false }
];
for (const item of cases) {
assert.equal(canAccessTask(item.access, { createdBy: item.createdBy, source: item.source }), item.allowed, item.name);
assert.equal(canAccessTask(item.access, { assignedUserId: item.assignedUserId }), item.allowed, item.name);
}
assert.equal(isTaskOwnerRestricted('admin'), false);
assert.equal(isTaskOwnerRestricted('team_lead'), true);
@@ -86,8 +86,14 @@ test('business route authorization is an explicit allowlist for team leads and o
role: 'user', source: 'manual', routeId: null, authorizedRouteIds: [routeId]
}), false, 'unclassified manual input fails closed for non-administrators');
assert.equal(canExecuteBusinessRoute({
role: undefined, source: 'agentbus', routeId: null, authorizedRouteIds: []
}), true, 'trusted AgentBus intake retains its separate administrator-controlled boundary');
role: 'user', source: 'agentbus', routeId, authorizedRouteIds: [routeId]
}), true, 'bound AgentBus intake uses the employee route allowlist');
assert.equal(canExecuteBusinessRoute({
role: undefined, source: 'agentbus', routeId, authorizedRouteIds: [routeId]
}), false, 'unbound AgentBus intake fails closed');
assert.equal(canExecuteBusinessRoute({
role: 'admin', source: 'agentbus', routeId, authorizedRouteIds: [routeId]
}), false, 'administrators cannot be AgentBus execution owners');
});
test('field encryption round-trips without storing plaintext', () => {
@@ -311,7 +317,7 @@ test('control plane requires the latest durable task-outcome migration before re
const { readFile } = await import('node:fs/promises');
const db = await readFile(new URL('../src/db.ts', import.meta.url), 'utf8');
const server = await readFile(new URL('../src/server.ts', import.meta.url), 'utf8');
assert.equal(REQUIRED_SCHEMA_VERSION, '017_user_business_route_authorizations');
assert.equal(REQUIRED_SCHEMA_VERSION, '018_agentbus_account_workers');
assert.match(db, /schema_migrations/);
assert.match(db, /databaseReadiness/);
assert.match(db, /assertDatabaseSchema/);
@@ -1226,7 +1232,7 @@ test('operator page has a login gate and uses the durable task API', async () =>
assert.match(index, /id="loginPanel"/);
assert.match(index, /id="workbench"[^>]*hidden/);
assert.match(index, /styles\.css\?v=20260902-dashboard-mobile-share-1/);
assert.match(index, /app\.js\?v=20260902-dashboard-mobile-share-1/);
assert.match(index, /app\.js\?v=20260902-agentbus-account-routing-1/);
assert.match(index, /id="statusDetailsPopover"/);
assert.match(index, /id="statusDetailsRefresh"/);
assert.match(app, /apiRequest\(`\/api\/tasks\?\$\{params\.toString\(\)\}`/);
@@ -1430,7 +1436,7 @@ test('operator page has a login gate and uses the durable task API', async () =>
assert.doesNotMatch(app, /task-stage-index|task-stage-current/);
assert.ok(app.indexOf('/claim') < app.indexOf("sendToExtension('CREATE_TASK'"), 'server claim must precede extension dispatch');
assert.doesNotMatch(app, /syncPendingTasks|>重交</);
assert.match(app, /runtimeTasks\(\)\.filter\(isTaskPollable\)/);
assert.match(app, /runtimeTasks\(\)\.filter\(\(task\) => taskAssignedToCurrentAccount\(task\) && isTaskPollable\(task\)\)/);
assert.match(app, /execution_id: executionId/);
assert.doesNotMatch(bridge, /async function upsertBusinessTask/);
assert.match(bridge, /task: currentBusinessTask/);