feat(bot): таблица knowledge_chunks — база знаний бота (FTS russian + GIN)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* База знаний ИИ-бота (спека 2026-07-02-jivo-ai-support-bot-design §3).
|
||||
* Глобальная таблица (НЕ tenant-scoped): только публичные статьи инструкции,
|
||||
* данных клиентов здесь нет по определению — RLS не требуется.
|
||||
* search_tsv — generated column (russian) + GIN: поиск за миллисекунды.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::statement(<<<'SQL'
|
||||
CREATE TABLE knowledge_chunks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source_path VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
tour VARCHAR(100),
|
||||
topics TEXT NOT NULL DEFAULT '',
|
||||
chunk_index INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
search_tsv tsvector GENERATED ALWAYS AS (
|
||||
to_tsvector('russian', coalesce(title, '') || ' ' || coalesce(topics, '') || ' ' || coalesce(content, ''))
|
||||
) STORED,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT uq_knowledge_chunks_source_chunk UNIQUE (source_path, chunk_index)
|
||||
)
|
||||
SQL);
|
||||
DB::statement('CREATE INDEX idx_knowledge_chunks_search ON knowledge_chunks USING GIN (search_tsv)');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement('DROP TABLE IF EXISTS knowledge_chunks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('knowledge_chunks существует и ищется полнотекстово по-русски', function () {
|
||||
DB::table('knowledge_chunks')->insert([
|
||||
'source_path' => 'help/project.md',
|
||||
'title' => 'Что такое проект',
|
||||
'tour' => 'create-project',
|
||||
'topics' => 'заявка на лиды, создать проект, источник',
|
||||
'chunk_index' => 0,
|
||||
'content' => 'Проект — это заявка на поток лидов с выбранного источника.',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$found = DB::select(
|
||||
"SELECT id, title FROM knowledge_chunks
|
||||
WHERE search_tsv @@ websearch_to_tsquery('russian', ?)",
|
||||
['что такое проект']
|
||||
);
|
||||
|
||||
expect($found)->toHaveCount(1)
|
||||
->and($found[0]->title)->toBe('Что такое проект');
|
||||
});
|
||||
+11
-1
@@ -2,7 +2,17 @@
|
||||
|
||||
**Назначение:** консолидированный журнал изменений `schema.sql`. Содержит тридцать записей в обратном хронологическом порядке (v8.33 → v8.32 → v8.31 → v8.30 → v8.29 → v8.28 → v8.27 → v8.26 → v8.25 → v8.24 → v8.23 → v8.22 → v8.21 → v8.20 → v8.19 → v8.18 → v8.17 → v8.16 → v8.15 → v8.14 → v8.13 → v8.12 → v8.11 → v8.10 → v8.9 → v8.8 → v8.7 → v8.6 → v8.5 → v8.4 → v8.3 → v8.2), как принято в keep-a-changelog.
|
||||
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.57, консолидированная — разворачивает БД с нуля).
|
||||
**Файл схемы:** `schema.sql` (текущая версия — v8.58, консолидированная — разворачивает БД с нуля).
|
||||
|
||||
## v8.58 (2026-07-02) — ИИ-бот техподдержки Jivo: knowledge_chunks + bot_dialogs
|
||||
|
||||
+`knowledge_chunks` (база знаний ИИ-бота, глобальная, без RLS — публичные статьи инструкции resources/help/*.md;
|
||||
generated column `search_tsv` russian + GIN-индекс `idx_knowledge_chunks_search`; заполняется командой
|
||||
`help:rebuild-knowledge`; спека `docs/superpowers/specs/2026-07-02-jivo-ai-support-bot-design.md §3`).
|
||||
|
||||
+`bot_dialogs` (журнал диалогов бота, глобальная, без RLS — ПДн клиентов не пишем; `direction in/out`,
|
||||
`matched_chunks JSONB`, `latency_ms`, `escalated`; индекс `idx_bot_dialogs_chat (jivo_chat_id, created_at)`;
|
||||
спека §5). Счётчики: таблиц 79→81 (regular 69→71) / индексов 124→126. Миграции 2026_07_02_100001/100002.
|
||||
|
||||
## v8.57 (2026-06-26) — RLS GUC hardening: NULLIF во всех политиках tenant_isolation (инцидент входа)
|
||||
|
||||
|
||||
+82
-2
@@ -1,6 +1,7 @@
|
||||
-- =============================================================================
|
||||
-- schema.sql — единая схема БД для SaaS-аналога crm.bp-gr.ru («Лидерра»)
|
||||
-- Версия: v8.57 (26.06.2026 — RLS GUC hardening: ВСЕ 44 политики tenant_isolation приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint — устранён класс отказов на Managed PG/PgBouncer, когда GUC app.current_tenant_id пуст ('' → 22P02) либо не задан (→ 42704). Инцидент 26.06: вход в портал падал на резолве users (60 ошибок, все на users). 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно получили разрешающую ветку «NULLIF(...) IS NULL OR ...» — читаются/пишутся ДО tenant-контекста на auth-роутах без 'tenant' middleware; при ЗАДАННОМ tenant изоляция НЕ меняется (rls-reviewer APPROVE). Структурно БД НЕ меняется — переписаны только USING/WITH CHECK (счётчики таблиц/индексов/RLS=44/функций/триггеров без изменений). Миграция 2026_06_26_153000_rls_nullif_guc_hardening (идемпотентна). Применена на боевой кластер; lead_charges FORCE RLS сохранён.)
|
||||
-- Версия: v8.58 (02.07.2026 — ИИ-бот техподдержки Jivo: +2 таблицы (knowledge_chunks — база знаний FTS russian GIN, глобальная без RLS; bot_dialogs — журнал диалогов, глобальная без RLS) + 2 индекса (idx_knowledge_chunks_search GIN, idx_bot_dialogs_chat). Спека docs/superpowers/specs/2026-07-02-jivo-ai-support-bot-design.md. Миграции 2026_07_02_100001/100002. Счётчики: таблиц 79→81 (regular 69→71) / индексов 124→126. RLS/функций/триггеров без изменений.)
|
||||
-- Базовая версия: v8.57 (26.06.2026 — RLS GUC hardening: ВСЕ 44 политики tenant_isolation приведены к NULLIF(current_setting('app.current_tenant_id', true), '')::bigint — устранён класс отказов на Managed PG/PgBouncer, когда GUC app.current_tenant_id пуст ('' → 22P02) либо не задан (→ 42704). Инцидент 26.06: вход в портал падал на резолве users (60 ошибок, все на users). 5 bootstrap-таблиц (users, auth_log, email_verifications, user_recovery_codes, user_sessions) дополнительно получили разрешающую ветку «NULLIF(...) IS NULL OR ...» — читаются/пишутся ДО tenant-контекста на auth-роутах без 'tenant' middleware; при ЗАДАННОМ tenant изоляция НЕ меняется (rls-reviewer APPROVE). Структурно БД НЕ меняется — переписаны только USING/WITH CHECK (счётчики таблиц/индексов/RLS=44/функций/триггеров без изменений). Миграция 2026_06_26_153000_rls_nullif_guc_hardening (идемпотентна). Применена на боевой кластер; lead_charges FORCE RLS сохранён.)
|
||||
-- Базовая версия: v8.56 (26.06.2026 — Путь А, шов C: тело функции audit_block_mutation() пропускает пересчёт hash-цепочки по метке GUC app.audit_rebuild='on' (+ superuser ИЛИ член crm_migrator) ВМЕСТО session_replication_role (superuser-only, недоступен в Yandex Managed PG). Append-only сохранён: без метки UPDATE/DELETE аудита запрещён. Структурно БД НЕ меняется — только тело функции (счётчики таблиц/индексов/RLS/функций/триггеров без изменений, функций по-прежнему 5). Миграция 2026_06_26_140000_audit_block_mutation_guc_rebuild_flag. AuditRebuildChain команда переведена на SET LOCAL app.audit_rebuild в транзакции (Odyssey-safe).)
|
||||
-- Базовая версия: v8.55 (25.06.2026 — Эпик 5 отчёт заливки: +1 таблица supplier_sync_runs (сводка по вечерней заливке проектов поставщику — групп/синк/ручная/отложено/упало + status; SaaS-level без RLS/tenant_id как supplier_csv_reconcile_log, пишет crm_supplier_worker BYPASSRLS, читает SaaS-admin) + 1 явный индекс idx_supplier_sync_runs_created. Миграция 2026_06_25_130000. Структурно +1 regular-таблица + 1 индекс. NB: сводные счётчики несут известный дрейф рантайм-счётчика (ср. сверка 23.06) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.)
|
||||
-- Базовая версия: v8.54 (25.06.2026 — Эпик 4 online-defer: +1 таблица supplier_deferred_sync (системная очередь отложенных онлайн-правок в окне 18:00→00:00 МСК, project_id PK → projects ON DELETE CASCADE, без RLS/tenant_id — доступ только crm_supplier_worker BYPASSRLS, покрыт blanket-грантом ON ALL TABLES в db/02_grants.sql как supplier_manual_sync_queue). Миграция 2026_06_25_120000. Структурно +1 regular-таблица; явных CREATE INDEX +0 (PK неявный). NB: сводные счётчики таблиц/индексов несут известный дрейф рантайм-счётчика (ср. сверка 23.06 RLS 42→44) — точная пересверка отдельным canon-sync. RLS/функций/триггеров без изменений.)
|
||||
@@ -29,7 +30,7 @@
|
||||
-- Базовая версия: v8.29 (22.05.2026 — webhook_log: supplier audit columns)
|
||||
-- Базовая версия: v8.28 (22.05.2026 — tenant_operations_log: журнал тенант-уровневых операций вне сделок (проекты, API-ключи, webhook URL), append-only hash-chain, P2 operational journaling closure)
|
||||
-- Базовая версия: v8.27 (21.05.2026 — drop projects.archived_at: feature архива заменена настоящим удалением с защитой по сделкам (ProjectService::delete()))
|
||||
-- Метрики: 79 базовых таблиц (69 regular + 10 partitioned parents: deals + supplier_lead_costs + 6 audit + lead_region_resolution_log + project_routing_snapshots) + 16 партиций / 124 индекса (+1 явный: idx_supplier_sync_runs_created) / 44 RLS-политик / 5 функций / 15 триггеров
|
||||
-- Метрики: 81 базовая таблица (71 regular + 10 partitioned parents: deals + supplier_lead_costs + 6 audit + lead_region_resolution_log + project_routing_snapshots) + 16 партиций / 126 индексов (+1 явный: idx_supplier_sync_runs_created; +idx_knowledge_chunks_search GIN; +idx_bot_dialogs_chat) / 44 RLS-политик / 5 функций / 15 триггеров
|
||||
-- NB (23.06.2026 сверка-чистка перед запуском): RLS-политик исправлено 42→44 — реальное число активных CREATE POLICY в теле == 44 == на боевом liderra.ru (pg_policies). Расхождение было исторический недоучёт в бегущем счётчике шапки (tenant_requisites v8.43 и др. добавлены в тело без правки сводной метрики). Структурно БД не менялась — только корректность числа.
|
||||
-- Базовая версия: v8.25 (19.05.2026 — supplier_manual_sync_queue: SaaS-level Tier 3 очередь резерва канала миграции проектов)
|
||||
-- Базовая версия: v8.24 (18.05.2026 — supplier_leads.vid → nullable для CSV-recovered лидов (Путь 2))
|
||||
@@ -3639,6 +3640,85 @@ BEGIN
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- knowledge_chunks — база знаний ИИ-бота (v8.58, спека 2026-07-02-jivo-ai-support-bot-design §3)
|
||||
-- =============================================================================
|
||||
-- Глобальная таблица (НЕ tenant-scoped): только публичные статьи клиентской инструкции,
|
||||
-- данных клиентов здесь нет по определению — RLS не требуется.
|
||||
-- search_tsv — generated column (russian): title+topics+content → tsvector + GIN-индекс.
|
||||
-- Заполняется командой help:rebuild-knowledge (ночной schedule 04:30).
|
||||
-- Миграция 2026_07_02_100001_create_knowledge_chunks_table.
|
||||
-- =============================================================================
|
||||
CREATE TABLE knowledge_chunks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source_path VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
tour VARCHAR(100),
|
||||
topics TEXT NOT NULL DEFAULT '',
|
||||
chunk_index INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
search_tsv tsvector GENERATED ALWAYS AS (
|
||||
to_tsvector('russian', coalesce(title, '') || ' ' || coalesce(topics, '') || ' ' || coalesce(content, ''))
|
||||
) STORED,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT uq_knowledge_chunks_source_chunk UNIQUE (source_path, chunk_index)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_knowledge_chunks_search ON knowledge_chunks USING GIN (search_tsv);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_user') THEN
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_app_user;
|
||||
GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_app_user;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_admin') THEN
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_app_admin;
|
||||
GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_app_admin;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_migrator') THEN
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON knowledge_chunks TO crm_migrator;
|
||||
GRANT USAGE, SELECT ON SEQUENCE knowledge_chunks_id_seq TO crm_migrator;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- bot_dialogs — журнал диалогов ИИ-бота (v8.58, спека §5)
|
||||
-- =============================================================================
|
||||
-- Глобальная (НЕ tenant-scoped) в v1: диалоги Jivo анонимны до этапа личных ответов;
|
||||
-- ПДн клиентов не пишем. direction: in = сообщение клиента, out = ответ бота.
|
||||
-- Миграция 2026_07_02_100002_create_bot_dialogs_table.
|
||||
-- =============================================================================
|
||||
CREATE TABLE bot_dialogs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
jivo_chat_id VARCHAR(64) NOT NULL,
|
||||
direction VARCHAR(3) NOT NULL CHECK (direction IN ('in', 'out')),
|
||||
message TEXT NOT NULL,
|
||||
matched_chunks JSONB,
|
||||
latency_ms INTEGER,
|
||||
escalated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_bot_dialogs_chat ON bot_dialogs (jivo_chat_id, created_at);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_user') THEN
|
||||
GRANT SELECT, INSERT ON bot_dialogs TO crm_app_user;
|
||||
GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_app_user;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_app_admin') THEN
|
||||
GRANT SELECT, INSERT ON bot_dialogs TO crm_app_admin;
|
||||
GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_app_admin;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'crm_migrator') THEN
|
||||
GRANT SELECT, INSERT ON bot_dialogs TO crm_migrator;
|
||||
GRANT USAGE, SELECT ON SEQUENCE bot_dialogs_id_seq TO crm_migrator;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- КОНЕЦ schema.sql
|
||||
-- =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user