a907fea031
Schema delta (4 правки в db/schema.sql):
- tenants + delivered_in_month INT NOT NULL DEFAULT 0 CHECK (>=0) — month
counter для PricingTierResolver O(1) lookup на горячем пути.
- lead_charges + charge_source VARCHAR(8) DEFAULT 'rub' CHECK IN ('prepaid','rub')
+ ALTER ADD CONSTRAINT chk_lead_charges_prepaid_zero_price (prepaid → price=0).
- supplier_leads + recovered_from_csv_at TIMESTAMPTZ + partial index
WHERE recovered_from_csv_at IS NOT NULL.
- Новая таблица supplier_csv_reconcile_log (SaaS-level, без RLS) + 2 индекса
(started_at DESC, partial status WHERE status IN ('drift_alert','failed')).
Метрики: 61 → 62 базовых таблиц / 114 → 117 индексов / 39 RLS (без изменений).
Сопутствующие правки:
- db/CHANGELOG_schema.md — v8.19 entry (18 записей).
- db/02_grants.sql — GRANT SELECT,INSERT,UPDATE on supplier_csv_reconcile_log
+ GRANT USAGE,SELECT on sequence для crm_supplier_worker.
- app/Models/Tenant — fillable+casts: delivered_in_month integer.
- app/Models/LeadCharge — fillable+casts: charge_source string.
- app/Models/SupplierLead — fillable+casts: recovered_from_csv_at datetime.
- LeadChargeFactory — defaults charge_source='rub' + prepaid() state.
- PricingTierSeeder (новый) — 7 ступеней дефолтного тарифа (placeholder,
Plan 4 Открытый вопрос #1: 100/200/400/800/1500/3000/∞ leads at
50000/45000/40000/35000/30000/27000/25000 копеек).
- DatabaseSeeder — call PricingTierSeeder; убран broken Laravel scaffold
User::factory(['name' => ...]) (наша схема first_name/last_name).
Tests (Plan 4 surface):
- SchemaDeltaTest.php — 5 it() блоков: delivered_in_month CHECK, charge_source
CHECK на prepaid+zero-price, recovered_from_csv_at колонка, reconcile_log
таблица+status CHECK, migrate:fresh idempotency (skip in parallel).
- pest --filter=SchemaDeltaTest: 5/5 PASS (9 assertions, 2076 ms).
- pest --filter='Tenant|LeadCharge|SupplierLead|Plan4': 111/111 PASS.
CI gates:
- composer pint: passed.
- composer stan: passed (0 errors above baseline — @phpstan-ignore-next-line
на $this->markTestSkipped в Pest closure rebound context).
Verify:
- migrate:fresh --seed на DB_DATABASE=liderra_testing: 0 errors, 754 ms.
- PricingTier::count() = 7.
Концерны (НЕ блокируют Task 1):
- pest --parallel: 617/622 PASS + 4 skipped + 1 flaky FAIL — flaky test
колеблется между ProjectExtensionsTest::supplierB1_B2_B3_relations
(SupplierProjectFactory race: closure пикает signal_type=sms до override
platform=B1 → CHECK chk_supplier_projects_b1_not_for_sms violation)
и NewLeadNotificationTest::webhook_дубль_Биз_19 (известный microsecond
precision quirk в anti-spam exclusion, memory feedback_environment.md).
Оба теста существуют на main (HEAD 0802f7c = plan4-billing branch HEAD
до этого commit'а), Plan 4 их не трогает. Фиксы — вне Task 1 scope.
Spec: docs/superpowers/specs/2026-05-11-plan4-billing-csv-admin-design.md §2.
Plan: docs/superpowers/plans/2026-05-11-plan4-billing-csv-admin-plan.md Task 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
143 lines
8.4 KiB
SQL
143 lines
8.4 KiB
SQL
-- =============================================================================
|
|
-- 02_grants.sql — GRANT/REVOKE для 4 ролей Лидерры
|
|
-- =============================================================================
|
|
-- Версия: 1.0 (08.05.2026, фаза 1 backend multi-tenant фундамент)
|
|
-- Источник: schema.sql v8.5 §13 «Роли БД (CTO-5)» + §14 «АУДИТ APPEND-ONLY»
|
|
-- =============================================================================
|
|
--
|
|
-- НАЗНАЧЕНИЕ: deployment-скрипт для production. Запускается ПОСЛЕ:
|
|
-- 1. db/00_create_roles.sql — роли созданы
|
|
-- 2. db/schema.sql — таблицы/индексы/RLS-политики созданы (через `php artisan migrate`)
|
|
--
|
|
-- ЗАПУСК:
|
|
-- psql -U postgres -h <host> -d liderra -f db/02_grants.sql
|
|
-- (требуется superuser, т.к. GRANT/REVOKE на чужие объекты)
|
|
--
|
|
-- ВНИМАНИЕ: на **dev-машине** этот файл НЕ запускается — schema.sql §13 разрешает
|
|
-- использовать суперпользователя `postgres` для разработки. RLS-политики работают
|
|
-- через `current_setting('app.current_tenant_id')::bigint` — независимо от роли.
|
|
-- =============================================================================
|
|
|
|
-- =============================================================================
|
|
-- 1. crm_app_user — tenant-уровень приложения (RLS активна)
|
|
-- =============================================================================
|
|
|
|
GRANT USAGE ON SCHEMA public TO crm_app_user;
|
|
|
|
-- SELECT/INSERT/UPDATE/DELETE на ВСЕ таблицы (RLS-политики ограничат доступ
|
|
-- к чужим tenant-данным; saas-таблицы дополнительно закрыты REVOKE ниже)
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO crm_app_user;
|
|
|
|
-- USAGE на sequences (для BIGSERIAL nextval)
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO crm_app_user;
|
|
|
|
-- Default privileges для будущих таблиц/sequences (после миграций)
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crm_app_user;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT USAGE, SELECT ON SEQUENCES TO crm_app_user;
|
|
|
|
-- v8.5 (OPEN-И-14) defense-in-depth: REVOKE ALL на 6 saas-таблицах.
|
|
-- К этим таблицам tenant-приложение НЕ должно иметь доступа даже теоретически
|
|
-- (RLS + REVOKE = 2 барьера).
|
|
REVOKE ALL ON saas_admin_users FROM crm_app_user;
|
|
REVOKE ALL ON saas_admin_sessions FROM crm_app_user;
|
|
REVOKE ALL ON saas_admin_audit_log FROM crm_app_user;
|
|
REVOKE ALL ON incidents_log FROM crm_app_user;
|
|
REVOKE ALL ON pd_subject_requests FROM crm_app_user;
|
|
REVOKE ALL ON impersonation_tokens FROM crm_app_user;
|
|
|
|
-- =============================================================================
|
|
-- 2. crm_admin_user — администрирование SaaS (BYPASSRLS)
|
|
-- =============================================================================
|
|
|
|
GRANT USAGE ON SCHEMA public TO crm_admin_user;
|
|
|
|
-- ВСЕ права crm_app_user + доступ к saas_admin_*, supplier_*, system_settings,
|
|
-- tariff_plans, legal_entities, payment_gateways
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO crm_admin_user;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO crm_admin_user;
|
|
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO crm_admin_user;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
GRANT USAGE, SELECT ON SEQUENCES TO crm_admin_user;
|
|
|
|
-- Запрет DELETE на финансовых таблицах (только soft markers через UPDATE):
|
|
REVOKE DELETE ON balance_transactions FROM crm_admin_user;
|
|
REVOKE DELETE ON supplier_invoices FROM crm_admin_user;
|
|
REVOKE DELETE ON supplier_lead_costs FROM crm_admin_user;
|
|
REVOKE DELETE ON tenant_subscriptions FROM crm_admin_user;
|
|
|
|
-- =============================================================================
|
|
-- 3. crm_audit_writer — append-only audit (INSERT-only)
|
|
-- =============================================================================
|
|
-- UPDATE/DELETE дополнительно блокируются триггерами audit_block_mutation
|
|
-- (schema.sql §14). Двухслойная защита от tampering.
|
|
|
|
GRANT USAGE ON SCHEMA public TO crm_audit_writer;
|
|
|
|
GRANT INSERT ON auth_log TO crm_audit_writer;
|
|
GRANT INSERT ON activity_log TO crm_audit_writer;
|
|
GRANT INSERT ON pd_processing_log TO crm_audit_writer;
|
|
GRANT INSERT ON saas_admin_audit_log TO crm_audit_writer;
|
|
GRANT INSERT ON balance_transactions TO crm_audit_writer;
|
|
|
|
GRANT USAGE ON SEQUENCE auth_log_id_seq TO crm_audit_writer;
|
|
GRANT USAGE ON SEQUENCE activity_log_id_seq TO crm_audit_writer;
|
|
GRANT USAGE ON SEQUENCE pd_processing_log_id_seq TO crm_audit_writer;
|
|
GRANT USAGE ON SEQUENCE saas_admin_audit_log_id_seq TO crm_audit_writer;
|
|
GRANT USAGE ON SEQUENCE balance_transactions_id_seq TO crm_audit_writer;
|
|
|
|
-- crm_audit_writer НЕ имеет SELECT/UPDATE/DELETE/TRUNCATE — это специально.
|
|
-- Явно отзываем дефолтные привилегии PUBLIC, если такие есть:
|
|
REVOKE SELECT, UPDATE, DELETE, TRUNCATE ON auth_log FROM crm_audit_writer;
|
|
REVOKE SELECT, UPDATE, DELETE, TRUNCATE ON activity_log FROM crm_audit_writer;
|
|
REVOKE SELECT, UPDATE, DELETE, TRUNCATE ON pd_processing_log FROM crm_audit_writer;
|
|
REVOKE SELECT, UPDATE, DELETE, TRUNCATE ON saas_admin_audit_log FROM crm_audit_writer;
|
|
REVOKE SELECT, UPDATE, DELETE, TRUNCATE ON balance_transactions FROM crm_audit_writer;
|
|
|
|
-- =============================================================================
|
|
-- 4. crm_migrator — runtime миграций (BYPASSRLS, CREATEDB)
|
|
-- =============================================================================
|
|
-- Для запуска `php artisan migrate` в production без ослабления RLS.
|
|
|
|
GRANT USAGE, CREATE ON SCHEMA public TO crm_migrator;
|
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO crm_migrator;
|
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO crm_migrator;
|
|
|
|
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;
|
|
|
|
-- =============================================================================
|
|
-- v8.19 (Plan 4): supplier_csv_reconcile_log — SaaS-уровневый журнал CSV recon.
|
|
-- Используется CsvReconcileJob под crm_supplier_worker (BYPASSRLS).
|
|
-- =============================================================================
|
|
|
|
GRANT SELECT, INSERT, UPDATE ON TABLE supplier_csv_reconcile_log TO crm_supplier_worker;
|
|
GRANT USAGE, SELECT ON SEQUENCE supplier_csv_reconcile_log_id_seq TO crm_supplier_worker;
|