diff --git a/app/app/Console/Commands/ResetDeliveredTodayCommand.php b/app/app/Console/Commands/ResetDeliveredTodayCommand.php index d66ac4ba..a9cb7f2c 100644 --- a/app/app/Console/Commands/ResetDeliveredTodayCommand.php +++ b/app/app/Console/Commands/ResetDeliveredTodayCommand.php @@ -13,8 +13,10 @@ use Illuminate\Support\Facades\DB; * Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6.1. * Расписание: каждый день в 00:00 МСК (timezone Europe/Moscow). * - * NB: tenant-scoped запрос без RLS — UPDATE сразу на все tenant'ы. - * dev=postgres BYPASSRLS / prod=elevated role при выполнении из Scheduler'а. + * NB: tenant-scoped запрос без RLS — UPDATE сразу на все tenant'ы. На production + * queue worker (через Scheduler) запускается под ролью crm_supplier_worker + * (BYPASSRLS) — Plan 2.6 fix #iv. На dev подключение под postgres (BYPASSRLS + * implicit). См. db/00_create_roles.sql. */ class ResetDeliveredTodayCommand extends Command { diff --git a/app/app/Services/LeadRouter.php b/app/app/Services/LeadRouter.php index 030bb0f4..8ec23994 100644 --- a/app/app/Services/LeadRouter.php +++ b/app/app/Services/LeadRouter.php @@ -25,8 +25,9 @@ use InvalidArgumentException; * * RLS-quirk: запрос работает поверх N tenant'ов одновременно (sharing-model). * Не использует SET LOCAL app.current_tenant_id (в sharing-flow tenant ещё не определён — - * запрос подбирает кандидатов из всех tenant'ов параллельно). Полагается на - * dev=postgres BYPASSRLS / prod=elevated role при выполнении из job'а. + * запрос подбирает кандидатов из всех tenant'ов параллельно). На production queue worker + * запускается под ролью crm_supplier_worker (BYPASSRLS) — Plan 2.6 fix #iv. На dev + * подключение под postgres (BYPASSRLS implicit). См. db/00_create_roles.sql. * * Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6 */ diff --git a/db/00_create_roles.sql b/db/00_create_roles.sql index b8b755c5..de589948 100644 --- a/db/00_create_roles.sql +++ b/db/00_create_roles.sql @@ -1,7 +1,10 @@ -- ============================================================================= --- 00_create_roles.sql — создание 4 ролей PostgreSQL для Лидерры +-- 00_create_roles.sql — создание 5 ролей PostgreSQL для Лидерры -- ============================================================================= --- Версия: 1.0 (08.05.2026, фаза 1 backend multi-tenant фундамент) +-- Версия: 1.1 (10.05.2026, Plan 2.6 fix #iv — добавлена роль crm_supplier_worker +-- BYPASSRLS для backend queue worker, sharing-flow webhook routing +-- + global crons; brainstorm decision вариант C) +-- Версия 1.0 (08.05.2026, фаза 1 backend multi-tenant фундамент) — 4 роли. -- Источник: schema.sql v8.5 §13 «Роли БД (CTO-5)» (закомментированные DDL) -- ============================================================================= -- @@ -17,6 +20,7 @@ -- -v crm_admin_password='' \ -- -v crm_migrator_password='' \ -- -v crm_audit_writer_password='' \ +-- -v crm_supplier_worker_password='' \ -- -f db/00_create_roles.sql -- -- ПОСЛЕ: запустить миграции под `crm_migrator` (BYPASSRLS, CREATEDB): @@ -51,3 +55,19 @@ CREATE ROLE crm_migrator CREATE ROLE crm_audit_writer LOGIN PASSWORD :'crm_audit_writer_password'; + +-- Роль backend queue worker для cross-tenant операций (Plan 2.6 fix #iv). +-- BYPASSRLS by design: queue worker = system process, обрабатывает sharing-flow +-- webhook routing (RouteSupplierLeadJob — N tenant'ов параллельно) и global +-- crons (projects:reset-delivered-today, supplier:check-webhook-secret). +-- Web worker остаётся под crm_app_user (RLS-enforce). +-- WHERE(tenant_id=) фильтры в коде сохраняются как defense-in-depth даже под +-- BYPASSRLS-ролью. См. RouteSupplierLeadJob::createDealCopyForProject lines 161-164 +-- (lockForUpdate Tenant + явный tenant_id фильтр перед каждым INSERT/UPDATE). +-- Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6. +-- Brainstorm decision: вариант C из 3 опций (A=elevated DB-connection / +-- B=RLS WITH-CHECK exception / C=BYPASSRLS-роль). См. memory project_supplier_integration.md. +CREATE ROLE crm_supplier_worker + LOGIN + PASSWORD :'crm_supplier_worker_password' + BYPASSRLS; diff --git a/db/02_grants.sql b/db/02_grants.sql index 17e7e3ae..f012e2a3 100644 --- a/db/02_grants.sql +++ b/db/02_grants.sql @@ -110,3 +110,25 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO crm_migrator; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO crm_migrator; + +-- ============================================================================= +-- 5. crm_supplier_worker — backend queue worker (BYPASSRLS) — Plan 2.6 fix #iv +-- ============================================================================= +-- Для запуска `php artisan queue:work` под отдельным .env (отдельным от web). +-- Cross-tenant операции: sharing-flow webhook routing (RouteSupplierLeadJob), +-- global crons (projects:reset-delivered-today, supplier:check-webhook-secret). +-- Web worker остаётся под crm_app_user (RLS-enforce). WHERE(tenant_id=) фильтры +-- в коде сохраняются как defense-in-depth даже под BYPASSRLS-ролью. +-- +-- Brainstorm decision (10.05.2026 поздняя ночь): вариант C из 3 опций +-- (A=elevated DB-connection / B=RLS WITH-CHECK exception / C=BYPASSRLS-роль). + +GRANT USAGE ON SCHEMA public TO crm_supplier_worker; + +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO crm_supplier_worker; +GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO crm_supplier_worker; + +ALTER DEFAULT PRIVILEGES IN SCHEMA public + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crm_supplier_worker; +ALTER DEFAULT PRIVILEGES IN SCHEMA public + GRANT USAGE, SELECT ON SEQUENCES TO crm_supplier_worker;