Фиксация логики программирования/создания субъекта в сущностях

Цель

Зафиксировать инженерный контракт: отдельное вычисление в системе (job) всегда является субъектным актом, а не «законом природы».


1) Базовый инвариант

J0: каждая запись в jobs принадлежит ровно одному owner_id.

Из J0 напрямую следуют инфраструктурные свойства:
— доступ к статусу и результатам определяется владельцем;
— дедупликация вычисляется в границах владельца;
— квоты, rate-limit, биллинг и приоритизация считаются по владельцу;
— удаление, отмена и GC артефактов привязаны к владельцу;
— аудит становится определённым: кто инициировал, повторил, форсировал, остановил.

Это не доменная опция, а минимальная онтология ответственности для многосубъектной системы.


2) Ключевое разделение уровней

2.1 Универсальный исполнитель (worker loop)

Исполнитель может быть полностью нейтральным:
— взять job из очереди;
— поставить lease;
— обновить статус;
— передать handler.

Это слой механизма.

2.2 Job как акт

job — не механизм, а заявка/билет: инициированное действие с ресурсной стоимостью и последствиями.

Это слой ответственности.

Формула:
— worker универсален;
— job субъектна.

Универсальность исполнителя не отменяет субъектность записи в jobs.


3) Почему «owner только в handler» недостаточен

Если owner_id отсутствует в jobs, а проверяется только в обработчиках, инфраструктура теряет управляемость:
— нельзя надёжно ограничить создание/выполнение задач по субъекту на уровне очереди;
— нельзя честно считать нагрузку и стоимость по субъекту;
— нельзя корректно дедупить в границах субъекта;
— нельзя быстро сделать kill all jobs for owner;
— нельзя жёстко гарантировать tenant-изоляцию в самом ядре очереди.

Итог: субъектность уезжает в «внешнюю логику», где неизбежны рассинхроны.


4) Минимальная модель сущностей

Для универсальной системы вычислений в jobs должны быть:
owner_id — ось доступа и ответственности;
dedup_key (job_key) — ось идентичности работы;
pipeline_version — ось версионности результата.

Это инфраструктурные поля вычислительного акта, а не поля конкретного домена.


5) Контракт истинности owner

Допустимы два корректных режима.

Вариант A (нормализация)

  • jobs.owner_id — единственная истина;
  • transcriptions.owner_id отсутствует;
  • owner производной сущности вычисляется через ...job_id -> jobs.owner_id.

Вариант B (практичный денорм)

  • jobs.owner_id — единственная истина;
  • transcriptions.owner_id — кэш/производное поле;
  • приложение не принимает owner_id извне при создании/обновлении производной сущности;
  • БД запрещает рассинхрон transcriptions.owner_id != jobs.owner_id.

Наличие двух колонок допустимо только при наличии одной истины и одной производной.


6) Инварианты БД

  1. jobs.owner_idNOT NULL + FK owners(id).
  2. Идемпотентность в пределах субъекта:
UNIQUE (owner_id, kind, pipeline_version, dedup_key)
WHERE dedup_key IS NOT NULL
  1. (Для варианта B) запрет рассинхрона owner между jobs и производными таблицами.

7) Контракт сервисного слоя

  1. Создание job всегда субъектно:
  2. create_job(owner_id, kind, pipeline_version, dedup_key, payload...).
  3. Получение статуса/результата всегда проверяет владение:
  4. job.owner_id == requester.owner_id.
  5. Воркеры могут оставаться универсальными, но учёт ресурса/лимитов/аудита идёт по owner_id.

8) Миграционный маршрут без «взрыва базы»

  1. Добавить jobs.owner_id как временно nullable.
  2. Выполнить backfill из связанных таблиц (transcriptions и др.).
  3. Перевести jobs.owner_id в NOT NULL.
  4. Добавить pipeline_version, dedup_key, partial unique index.
  5. Закрыть рассинхрон для денормированных owner-полей (триггер/constraint + код).

9) Быстрый архитектурный тест

Если архитектура верна, на уровне jobs можно ответить на вопросы без «магии» в handler:
— кто инициировал и кто платит;
— сколько ресурсов потратил конкретный субъект;
— какие задачи нужно остановить у конкретного субъекта;
— кто имеет право видеть/повторять/удалять результат.

Если на эти вопросы нельзя ответить через jobs, субъектность в ядре ещё не зафиксирована.


10) Принципиальный вывод

owner_id в jobs — не усложнение, а возвращение ответственности в структуру системы.

Система может иметь универсальный механизм исполнения, но каждый вычислительный акт внутри него должен иметь владельца. Иначе возникает «анонимная зона» — место утечки границ, лимитов и ответственности.


11) Онтологическое уточнение: субъект не эпифеномен

Ключевая фиксация:

Субъект не должен появляться «где-то в середине». Он не производный от процесса, а инициатор действия.

Если owner_id хранится только в payload-таблицах, схема неявно утверждает ложную причинность: будто сначала есть процесс/содержание, и лишь потом «внутри» обнаруживается субъект.

Правильная причинная ось обратная:
— субъект инициирует акт;
— акт фиксируется как job;
— payload описывает материал/параметры акта.

Иными словами:
job — событие ответственности;
payload — аргументы события;
owner_id — атрибут акта, а не атрибут материала.

Поэтому owner_id должен быть в корневой сущности jobs, а не в «сменной плоти» payload-структур.


12) Практическая формула архитектуры

Рекомендуемая форма:

Job(owner_id, kind, status, schedule, lease, pipeline_version, dedup_key, ...)

и отдельно:

JobPayload(job_id, ...)

Что это даёт сразу:
— единая и быстрая выборка «все мои задачи» по индексу jobs.owner_id;
— естественная политика доступа (job.owner_id как первичный предикат);
— чистая идемпотентность в границах субъекта (owner_id + ключ идентичности);
— нормальные операции retention, cleanup, rate limiting, abuse detection, audit без зоопарка join-веток по типам payload.

Это не просто «удобнее хранить». Это возвращение субъектности в начало причинной цепи системы.

Прокрутить вверх