fix(supplier): миграция supplier_order_checks — pgsql_supplier + инлайн-GRANT crm_supplier_worker
По rls-ревью: соседние SaaS-supplier-таблицы (supplier_sync_runs/deferred_sync) создаются через pgsql_supplier + GRANT SELECT,INSERT + USAGE на sequence для crm_supplier_worker в DO-блоке с IF EXISTS. Дефолтный Schema::create оставлял таблицу во владении crm_migrator без грантов → VerifySupplierOrderJob упал бы permission denied на боевом Managed PG. Приведено к паттерну + SaaS-коммент + CHECK status. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,32 +3,61 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* supplier_order_checks — история итоговой проверки заказа у поставщика.
|
||||
*
|
||||
* VerifySupplierOrderJob после ночного прогона робота перечитывает живой кабинет
|
||||
* поставщика и сверяет каждую нашу строку с формулой; результат
|
||||
* (ok / mismatch / unable_to_verify + список расхождений) пишется сюда одной строкой.
|
||||
*
|
||||
* Системная таблица (как supplier_sync_runs / supplier_deferred_sync) — без RLS/
|
||||
* tenant_id, пишет supplier-flow джоб под crm_supplier_worker (BYPASSRLS), читает SaaS-admin.
|
||||
* Создаётся через pgsql_supplier + инлайн-GRANT (guard IF EXISTS) — на проде миграции
|
||||
* катит crm_migrator, поэтому дефолтные привилегии crm_supplier_worker не наследуются.
|
||||
*
|
||||
* Spec: docs/superpowers/specs/2026-07-09-supplier-order-verification-and-deadline-watch-design.md §6.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('supplier_order_checks', function (Blueprint $table): void {
|
||||
$table->bigIncrements('id');
|
||||
$table->timestampTz('checked_at');
|
||||
$table->unsignedBigInteger('sync_run_id')->nullable();
|
||||
$table->integer('intended_rows')->default(0);
|
||||
$table->integer('live_rows')->default(0);
|
||||
$table->integer('mismatch_count')->default(0);
|
||||
// ok | mismatch | unable_to_verify
|
||||
$table->string('status', 32);
|
||||
$table->jsonb('details')->nullable();
|
||||
$table->timestampTz('created_at')->useCurrent();
|
||||
$supplier = DB::connection('pgsql_supplier');
|
||||
|
||||
$table->index('checked_at');
|
||||
$table->index('status');
|
||||
});
|
||||
$supplier->statement(<<<'SQL'
|
||||
CREATE TABLE IF NOT EXISTS supplier_order_checks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
checked_at TIMESTAMPTZ NOT NULL,
|
||||
sync_run_id BIGINT,
|
||||
intended_rows INTEGER NOT NULL DEFAULT 0,
|
||||
live_rows INTEGER NOT NULL DEFAULT 0,
|
||||
mismatch_count INTEGER NOT NULL DEFAULT 0,
|
||||
status VARCHAR(32) NOT NULL
|
||||
CHECK (status IN ('ok','mismatch','unable_to_verify')),
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
SQL);
|
||||
$supplier->statement('CREATE INDEX IF NOT EXISTS idx_supplier_order_checks_checked ON supplier_order_checks (checked_at DESC)');
|
||||
$supplier->statement('CREATE INDEX IF NOT EXISTS idx_supplier_order_checks_status ON supplier_order_checks (status)');
|
||||
|
||||
foreach (['crm_supplier_worker'] as $role) {
|
||||
$supplier->statement(<<<SQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{$role}') THEN
|
||||
GRANT SELECT, INSERT ON supplier_order_checks TO {$role};
|
||||
GRANT USAGE, SELECT ON SEQUENCE supplier_order_checks_id_seq TO {$role};
|
||||
END IF;
|
||||
END
|
||||
\$\$
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('supplier_order_checks');
|
||||
DB::connection('pgsql_supplier')->statement('DROP TABLE IF EXISTS supplier_order_checks CASCADE');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-07-10T01:43:47.562Z
|
||||
Last updated: 2026-07-10T01:58:51.729Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -112,8 +112,8 @@ Episodes since last run: 542 / threshold: 10
|
||||
|
||||
| PID | Имя | CPU-время | Возраст |
|
||||
|---|---|---|---|
|
||||
| 11216 | MsMpEng | 2.69ч | NaNч |
|
||||
| 10400 | Code | 1.98ч | NaNч |
|
||||
| 11216 | MsMpEng | 2.76ч | 0.0ч |
|
||||
| 10400 | Code | 2.00ч | 0.0ч |
|
||||
|
||||
⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user