import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; async function source(path: string): Promise { return readFile(new URL(path, import.meta.url), 'utf8'); } test('account migration adds roles, actor attribution, and reversible archive without purging history', async () => { const sql = await source('../migrations/015_account_roles_and_task_audit.sql'); assert.match(sql, /CHECK \(role IN \('admin', 'user'\)\)/); assert.match(sql, /must_change_password boolean NOT NULL DEFAULT false/); assert.match(sql, /password_changed_at timestamptz NOT NULL DEFAULT now\(\)/); assert.match(sql, /idempotency_keys[\s\S]+actor_user_id uuid REFERENCES users\(id\)/); assert.match(sql, /idempotency_keys_actor_unique_idx/); assert.match(sql, /idempotency_keys_system_unique_idx/); assert.match(sql, /actor_user_id uuid REFERENCES users\(id\)/); assert.match(sql, /input_source IN \('manual', 'agentbus', 'reparse', 'system'\)/); assert.match(sql, /task_input_attachments[\s\S]+created_by uuid REFERENCES users\(id\)/); assert.match(sql, /archived_at timestamptz/); assert.match(sql, /archived_by uuid REFERENCES users\(id\)/); assert.doesNotMatch(sql, /DELETE\s+FROM/i); }); test('team-lead migration adds the role and bounded dashboard indexes without a tenant concept', async () => { const sql = await source('../migrations/016_team_lead_operations_dashboard.sql'); assert.match(sql, /CHECK \(role IN \('admin', 'team_lead', 'user'\)\)/); assert.match(sql, /tasks_operations_dashboard_created_idx/); assert.match(sql, /tasks_operations_dashboard_actor_idx/); assert.match(sql, /tasks_operations_dashboard_status_idx/); assert.match(sql, /tasks_operations_dashboard_business_idx/); assert.match(sql, /WHERE source = 'manual'/); assert.doesNotMatch(sql, /CREATE TABLE\s+(?:organizations|tenants)/i); assert.doesNotMatch(sql, /DELETE\s+FROM/i); }); test('business authorization migration adds a fail-closed per-user allowlist for all registered routes', async () => { const sql = await source('../migrations/017_user_business_route_authorizations.sql'); assert.match(sql, /business_authorization_revision integer NOT NULL DEFAULT 0/); assert.match(sql, /CREATE TABLE IF NOT EXISTS user_business_route_authorizations/); assert.match(sql, /PRIMARY KEY \(organization_id, user_id, route_id\)/); assert.match(sql, /granted_by uuid REFERENCES users\(id\)/); assert.match(sql, /FOREIGN KEY \(organization_id, user_id\)[\s\S]+REFERENCES users \(organization_id, id\)/); assert.match(sql, /FOREIGN KEY \(organization_id, granted_by\)[\s\S]+REFERENCES users \(organization_id, id\)/); for (const routeId of [ 'team_order_create', 'passenger_list_import_independent', 'arrangement_hotel_create', 'order_update_independent', 'order_cancel', 'confirmation_export' ]) { assert.match(sql, new RegExp(`'${routeId}'`)); } assert.doesNotMatch(sql, /INSERT INTO user_business_route_authorizations[\s\S]+SELECT[\s\S]+FROM users/i); }); test('AgentBus account-worker migration adds fail-closed channel, task, browser, and ERP identity ownership', async () => { const sql = await source('../migrations/018_agentbus_account_workers.sql'); assert.match(sql, /ADD COLUMN IF NOT EXISTS erp_account text/); assert.match(sql, /users_org_erp_account_unique_idx/); assert.match(sql, /ADD COLUMN IF NOT EXISTS owner_user_id uuid/); assert.match(sql, /FOREIGN KEY \(organization_id, owner_user_id\)[\s\S]+REFERENCES users \(organization_id, id\)/); assert.match(sql, /user_channels_owner_unique_idx/); assert.match(sql, /ADD COLUMN IF NOT EXISTS assigned_user_id uuid/); assert.match(sql, /FOREIGN KEY \(organization_id, assigned_user_id\)[\s\S]+REFERENCES users \(organization_id, id\)/); assert.match(sql, /source = 'manual'[\s\S]+created_by IS NOT NULL/); assert.match(sql, /erp_account_verified boolean NOT NULL DEFAULT false/); assert.match(sql, /browser_connections_active_user_unique_idx/); assert.match(sql, /status = 'superseded'/); assert.match(sql, /owner_user_id IS NULL[\s\S]+enabled = true/); }); test('AgentBus channel keys and owners are unique so one inbound identity cannot fan out to multiple employees', async () => { const channels = await source('../src/agentbus-channels.ts'); assert.match(channels, /requireAssignableOwner/); assert.match(channels, /requireUniqueAgentBusKey/); assert.match(channels, /pg_advisory_xact_lock/); assert.match(channels, /sha256Text\(agentbusKey\)/); assert.match(channels, /channel_key_conflict/); assert.match(channels, /channel_owner_conflict/); }); test('account lifecycle is administrator-gated and protects passwords, sessions, and the last administrator', async () => { const [auth, server] = await Promise.all([ source('../src/auth.ts'), source('../src/server.ts') ]); assert.match(auth, /export type AuthRole = 'admin' \| 'team_lead' \| 'user'/); assert.match(auth, /role: normalizeRole\(row\.role\)/); assert.match(auth, /argon2\.hash\([^;]+type: argon2\.argon2id/s); assert.match(auth, /self_lockout_forbidden/); assert.match(auth, /last_admin_protected/); assert.match(auth, /UPDATE sessions SET revoked_at = now\(\)/); assert.match(auth, /must_change_password = false/); assert.match(auth, /function validatePassword[\s\S]+if \(!password\)/); assert.doesNotMatch(auth, /password\.length < 12|12—512/); assert.match(auth, /account\.password_reset/); assert.match(auth, /account\.password_changed/); assert.match(auth, /function validateAccountRouting/); assert.match(auth, /admin_erp_account_forbidden/); assert.match(auth, /erp_account_conflict/); assert.match(server, /app\.get\('\/api\/accounts'[\s\S]+requireAdminSession\(request\)/); assert.match(server, /app\.post\('\/api\/accounts'[\s\S]+requireAdminMutationSession\(request\)/); assert.match(server, /app\.get\('\/api\/audit'[\s\S]+requireAdminSession\(request\)/); assert.doesNotMatch(server, /password_change_required|must_change_password|\.min\(12\)/); const publicUser = server.slice(server.indexOf('function publicUser'), server.indexOf('async function loadExternalParser')); assert.doesNotMatch(publicUser, /must_change_password/); assert.doesNotMatch(publicUser, /organization/); }); test('administrators manage task-type grants and manual intake enforces them before parsing or ERP dispatch', async () => { const [auth, tasks, server] = await Promise.all([ source('../src/auth.ts'), source('../src/task-service.ts'), source('../src/server.ts') ]); assert.match(auth, /authorized_business_route_ids: BusinessRouteId\[\]/); assert.match(auth, /async setBusinessRouteAuthorizations\(/); assert.match(auth, /business_authorization_revision_conflict/); assert.match(auth, /account\.business_authorizations_updated/); assert.match(auth, /admin_business_authorization_fixed/); assert.match(server, /task_types: BUSINESS_ROUTES\.map/); assert.match(server, /app\.put\('\/api\/accounts\/:userId\/business-authorizations'[\s\S]+requireAdminMutationSession\(request\)/); assert.match(tasks, /export function canExecuteBusinessRoute/); assert.match(tasks, /task\.business_authorization_denied/); assert.match(tasks, /business_type_unresolved/); assert.match(tasks, /当前账号未授权“\$\{route\.directive\}”业务,已禁止执行/); assert.match(tasks, /no_parse: true/); assert.match(tasks, /no_plugin_dispatch: true/); assert.match(tasks, /no_erp_write: true/); const createTask = tasks.slice(tasks.indexOf('async createTask('), tasks.indexOf('async ingestMessage(')); assert.match(createTask, /requireBusinessAuthorization\(context, requestedRouteId/); assert.match(createTask, /assertBusinessAuthorizationInTransaction/); const ingestMessage = tasks.slice(tasks.indexOf('async ingestMessage('), tasks.indexOf('async getTask(')); assert.match(ingestMessage, /requireBusinessAuthorization\(context, preflightRouteId/); assert.match(ingestMessage, /assertBusinessAuthorizationInTransaction/); const attachment = tasks.slice( tasks.indexOf('async attachPassengerRosterAttachment('), tasks.indexOf('async createTask(') ); assert.match(attachment, /requireBusinessAuthorization\(context, target\.routeId/); assert.match(attachment, /assertBusinessAuthorizationInTransaction/); for (const transition of ['confirmTask', 'claimForBrowser']) { const start = tasks.indexOf(`async ${transition}(`); assert.notEqual(start, -1); assert.match(tasks.slice(start, start + 8_000), /assertTaskCreatorBusinessAuthorizationInTransaction/); } const parseResult = tasks.slice(tasks.indexOf('async applyParseResult('), tasks.indexOf('async confirmTask(')); assert.match(parseResult, /taskAuthorization\.allowed && shouldAutomaticallyConfirm/); }); test('operations dashboard is leadership-gated, business-facing, searchable, and separate from normal task authority', async () => { const [tasks, server] = await Promise.all([ source('../src/task-service.ts'), source('../src/server.ts') ]); assert.match(tasks, /canViewOperationsDashboard[\s\S]+role === 'admin' \|\| role === 'team_lead'/); assert.match(tasks, /isTaskOwnerRestricted[\s\S]+role === 'team_lead' \|\| role === 'user'/); assert.match(tasks, /async listOperationsDashboard[\s\S]+t\.source = 'manual'/); assert.match(tasks, /operations_dashboard_range_too_large/); assert.match(tasks, /businessRouteId\?: string/); assert.match(tasks, /operations_dashboard_business_invalid/); assert.match(tasks, /operations_dashboard_search_scope_too_large/); assert.match(tasks, /function operationsDashboardRouteFromInstruction/); assert.match(tasks, /function operationsDashboardRouteFromOperation/); assert.match(tasks, /if \(search && candidateResult\.rows\.length > 2_000\)/); assert.match(tasks, /OPERATIONS_DASHBOARD_FALLBACK_TYPES/); assert.match(tasks, /dashboard_order_delete/); assert.match(tasks, /dashboard_arrangement_hotel/); assert.match(tasks, /dashboard_passenger_list_import/); assert.match(tasks, /dashboard_order_update/); assert.match(tasks, /const routeMatches = !businessRouteId/); assert.match(tasks, /content_ciphertext[\s\S]+decryptText\(this\.config, row\.content_ciphertext/); assert.match(tasks, /operationsDashboardSearchMatches[\s\S]+projection\.instruction[\s\S]+projection\.result/); assert.match(tasks, /function operationsDashboardLeadershipResultText/); assert.match(tasks, /function operationsDashboardLeadershipInstructionText/); assert.match(tasks, /待跟进/); assert.match(tasks, /未完成/); assert.match(tasks, /containsTechnicalLanguage/); assert.match(tasks, /当前结果需要人工确认/); assert.match(tasks, /timezone\('Asia\/Shanghai', t\.created_at\)/); assert.match(tasks, /days,[\s\S]+businesses,[\s\S]+business_options/); assert.match(tasks, /instructionPreview\.length > 360/); const detailType = tasks.slice( tasks.indexOf('export interface PublicOperationsDashboardTaskDetail'), tasks.indexOf('export interface PublicOperationsDashboardPage') ); assert.match(detailType, /creator: PublicOperationsDashboardActor/); assert.match(detailType, /instructions: PublicOperationsDashboardInstruction\[\]/); assert.match(detailType, /result_summary: string/); assert.doesNotMatch(detailType, /events|stage|operation|parse_response|execution_result|download_url/); const detailMethod = tasks.slice( tasks.indexOf('async getOperationsDashboardTask('), tasks.indexOf('async listAuditEvents(') ); assert.match(detailMethod, /source = 'manual'/); assert.match(detailMethod, /getTaskInputHistory/); assert.match(detailMethod, /instructions,[\s\S]+attachments:/); assert.doesNotMatch(detailMethod, /this\.getTask\(/); assert.match(server, /app\.get\('\/api\/operations-dashboard'[\s\S]+requireLeadershipSession\(request\)/); assert.match(server, /business_route_id: z\.string\(\)\.trim\(\)\.max\(120\)\.optional\(\)/); assert.match(server, /businessRouteId: query\.business_route_id/); assert.match(server, /signal: controller\.signal/); assert.match(server, /app\.get\('\/api\/operations-dashboard\/tasks\/:taskId'[\s\S]+requireLeadershipSession\(request\)/); assert.match(server, /read_only: true/); assert.doesNotMatch(server, /app\.(?:post|put|patch|delete)\('\/api\/operations-dashboard/); }); test('ordinary task access is enforced across reads, mutations, artifacts, events, and browser connections', async () => { const [tasks, server] = await Promise.all([ source('../src/task-service.ts'), source('../src/server.ts') ]); assert.match(tasks, /assigned_user_id = \$4/); assert.match(tasks, /private async lockTaskForAccess/); for (const mutation of ['reparseTaskWithAi', 'confirmTask', 'claimForBrowser', 'recordExecutionResult', 'cancelTask']) { const start = tasks.indexOf(`async ${mutation}(`); assert.notEqual(start, -1, `${mutation} exists`); const body = tasks.slice(start, start + 20_000); assert.match(body, /lockTaskForAccess\(/, `${mutation} uses the task access lock`); } assert.match(tasks, /async getTaskArtifact[\s\S]+assigned_user_id = \$4/); assert.match(tasks, /async eventsSince[\s\S]+t\.assigned_user_id = \$4/); assert.match(tasks, /async getTaskInputHistory[\s\S]+actor_user_id/); assert.match(tasks, /connection\.organization_id = \$1[\s\S]+connection\.user_id = \$2[\s\S]+connection\.connection_id = \$3/); assert.match(tasks, /WHERE browser_connections\.user_id = EXCLUDED\.user_id/); assert.match(tasks, /browser_worker_conflict/); assert.match(tasks, /identity_mismatch/); assert.match(tasks, /erp_account_mismatch/); assert.match(tasks, /task_execution_assignee_mismatch/); assert.match(tasks, /assignee: publicActor\(row\.assignee_id, row\.assignee_username\)/); const confirmation = tasks.slice(tasks.indexOf('async confirmTask('), tasks.indexOf('async claimForBrowser(')); assert.match(confirmation, /assigned_user_id/); assert.match(confirmation, /task_execution_assignee_mismatch/); assert.match(tasks, /i\.actor_user_id IS NOT DISTINCT FROM \$3::uuid/); assert.match(tasks, /private async lockIdempotencyKey/); assert.match(tasks, /pg_advisory_xact_lock/); assert.match(server, /tasks\.listTasksPage[\s\S]+access: contextFor\(session, request\)/); assert.match(server, /tasks\.getTaskArtifact[\s\S]+contextFor\(session, request\)/); assert.match(server, /tasks\.eventsSince\(session\.user\.organizationId, since, contextFor\(session, request\)\)/); }); test('operator UI exposes role-aware accounts, executive drill-through, original input, final output, and reversible archive', async () => { const [app, index, retention] = await Promise.all([ source('../../LianSyn-platform/app.js'), source('../../LianSyn-platform/index.html'), source('../src/retention.ts') ]); assert.match(index, /href="\/accounts"/); assert.match(index, /href="\/audit"/); assert.match(index, /href="\/operations-dashboard"/); assert.match(index, /id="passwordChangeForm"/); assert.match(index, /id="accountForm"/); assert.match(index, /id="accountErpAccount"/); assert.match(index, /id="channelOwnerUserId"/); assert.doesNotMatch(index, /accountMustChangePassword|首次登录必须修改密码|minlength="12"|12—512/); assert.match(index, /id="accountAuthorizationPanel"/); assert.match(index, /id="accountAuthorizationTypes"/); assert.match(index, /id="accountAuthorizationSave"/); assert.match(index, /value="team_lead">组长/); assert.match(index, /id="operationsDashboardFilters"/); assert.match(index, /id="operationsDashboardBusiness"/); assert.match(index, /id="operationsDashboardBusinesses"/); assert.match(index, /任务类型排名/); assert.match(index, /员工操作任务排名/); assert.match(index, /员工名称/); assert.match(index, /class="operations-dashboard-table"/); assert.match(index, /员工名称、原始输入或最终输出/); assert.match(index, /id="operationsDashboardRangePresets"/); assert.match(index, /id="operationsDashboardRefresh"/); assert.match(index, /任务操作明细/); assert.match(index, /id="operationsDashboardDetail"/); assert.doesNotMatch(index, /平台运行全景|operations-dashboard-hero|OPERATIONS OVERVIEW|BUSINESS TRACE|指令操作历史/); assert.match(index, /id="historyArchiveInput"/); assert.match(app, /authUser\?\.role === 'admin'/); assert.doesNotMatch(app, /passwordChangeForced|must_change_password/); assert.match(app, /crypto\.randomUUID/); assert.match(app, /\/api\/tasks\/\$\{encodeURIComponent\(taskId\)\}\/input-history/); assert.match(app, /创建人与原始输入审计/); assert.match(app, /function renderAccountAuthorizationPanel/); assert.match(app, /function renderChannelOwnerOptions/); assert.match(app, /function taskAssignedToCurrentAccount/); assert.match(app, /expected_erp_account/); assert.match(app, /executable_by=me/); assert.match(app, /const candidates = Array\.isArray\(result\.tasks\) \? result\.tasks : \[\]/); assert.match(app, /\/business-authorizations/); assert.match(app, /当前默认不能执行任何业务/); assert.match(app, /function canViewOperationsDashboard/); assert.match(app, /\/api\/operations-dashboard\?/); assert.match(app, /\/api\/operations-dashboard\/tasks\/\$\{encodeURIComponent\(taskId\)\}/); assert.match(app, /business_route_id/); assert.doesNotMatch(app, /data-operations-status|dataset\.operationsStatus/); assert.match(app, /\$\('#operationsDashboardStatus'\)\.value = 'all'/); assert.match(app, /data-operations-business|dataset\.operationsBusiness/); assert.match(app, /function operationsDashboardRankScale/); assert.match(app, /operationsDashboardRankBar/); assert.match(app, /operations-dashboard-rank-bar/); assert.match(app, /sort\(\(left, right\) => Number\(right\.total/); assert.doesNotMatch(app, /完成率|operationsDashboardCompletionRate|operationsDashboardDays/); const rankScaleSource = app.slice( app.indexOf('function operationsDashboardRankScale('), app.indexOf('function operationsDashboardRankBar(') ); const rankScale = Function(`${rankScaleSource}\nreturn operationsDashboardRankScale;`)() as (maximum: number) => number; assert.equal(rankScale(0), 1); assert.equal(rankScale(1), 2); assert.equal(rankScale(43), 50); assert.equal(rankScale(100), 120); assert.equal(rankScale(359), 400); assert.match(app, /function operationsDashboardReadableResult/); assert.match(app, /function operationsDashboardReadableInput/); assert.match(app, /原始输入/); assert.match(app, /最终输出/); const dashboardTaskRenderer = app.slice( app.indexOf('function renderOperationsDashboardTasks()'), app.indexOf('function renderOperationsDashboard()') ); assert.match(dashboardTaskRenderer, /task\.creator\.username/); assert.match(dashboardTaskRenderer, /task\.created_at/); assert.match(dashboardTaskRenderer, /task\.business_route_label/); assert.match(dashboardTaskRenderer, /task\.instruction_preview/); assert.match(dashboardTaskRenderer, /operationsDashboardReadableInput\(task\.instruction_preview/); assert.match(dashboardTaskRenderer, /operationsDashboardReadableResult\(task\.result_summary/); assert.doesNotMatch(dashboardTaskRenderer, /记录号|task\.task_id\}`/); const dashboardDetailRenderer = app.slice( app.indexOf('function renderOperationsDashboardDetail()'), app.indexOf('async function syncOperationsDashboard()') ); assert.match(dashboardDetailRenderer, /人员/); assert.match(dashboardDetailRenderer, /原始输入/); assert.match(dashboardDetailRenderer, /最终输出/); assert.match(dashboardDetailRenderer, /operationsDashboardReadableResult/); assert.doesNotMatch(dashboardDetailRenderer, /任务生命周期|处理结果与技术上下文|renderTaskLifecycle|JSON\.stringify|task\.stage|parse_response|operation:/); assert.match(app, /\/api\/tasks\/\$\{encodeURIComponent\(taskId\)\}\/archive/); assert.match(app, /\/api\/tasks\/\$\{encodeURIComponent\(taskId\)\}\/restore/); assert.doesNotMatch(app, /sendToExtension\('DELETE_TASK'/); assert.match(retention, /SET archived_at = now\(\)/); assert.doesNotMatch(retention, /DELETE FROM tasks/); assert.doesNotMatch(retention, /DELETE FROM audit_events/); });