Files
transcriber/openspec/changes/archive/2026-08-14-record-ownership/proposal.md
T
av 8af8ec2e54 у записи появился владелец: чужую больше не отдают
- колонка `owner` связью с `users` в обеих коллекциях новым шагом схемы
  `202608140001`; чтение задачи сужено владельцем, и чужая, ничья и
  несуществующая дают один ответ; правило просмотра файлов сужено им же
- приём по HTTP берёт владельца из сессии, а предъявителя без учётной записи
  пользователя отвергает до чтения тела: позже пришлось бы убирать уложенный
  файл, а уборки файлов сервис не умеет. Выборка воркера владельцем не сужается
- удаление учётной записи с записями отвергается стражем, и вешает его сама
  сборка хранилища: сборка, забывшая его позвать, теряла защиту молча
2026-08-14 12:18:11 +03:00

4.8 KiB
Raw Blame History

Why

Сегодня знание идентификатора задачи и есть право её читать: всякий вошедший видит любую расшифровку по её идентификатору — свою, соседа, чью угодно. Паспорт обещает приглашённому пользователю обратное: «каждый видит только свои записи».

Разграничение стоит первым в очереди не само по себе. На владельце записи стоят список записей, дедупликация, удаление, учёт расхода и квота — всё, что проект собирается делать дальше. Заводить их поверх общей кучи записей значит переделать каждое из них потом.

What Changes

  • У записи появляется владелец — учётная запись, от имени которой её завели. Назначается он один раз, при приёме, и больше не меняется.
  • Опрос готовности отдаёт только свои записи. Чужая запись по её идентификатору отвечает «не найдено» — тем же ответом, что и несуществующая. Отдельного «доступ запрещён» нет намеренно: по нему перебирается список заведённых задач.
  • Приём по HTTP заводит владельца из предъявленной сессии. Приём без сессии отказывал и раньше, и это не меняется.
  • Конвейер владельцем не сужается: расшифровка идёт для всех записей подряд, как и прежде. Владелец решает, кому запись показывать, а не кому её считать.
  • BREAKING для содержимого базы: колонка владельца добавляется шагом схемы, и записи, заведённые до него, владельца не получают. Данные не переносим — проект заводится с чистого листа, так решено рамками задачи.

Записи, пришедшие ботом, владельца здесь не получают: связи чата Telegram с учётной записью приложения ещё нет, её заводит следующая задача. Что это значит для обязательности колонки — развилка, вынесенная в design.md и на чекпоинт.

Capabilities

New Capabilities

Новых нет: разграничение доступа — это поведение уже заведённой access, а не новый домен.

Modified Capabilities

  • access: появляется владелец записи и правило «чужая запись неотличима от несуществующей». Сегодня спека прямо говорит обратное — «разграничения записей по владельцу здесь нет».
  • intake: приём назначает владельца принятой записи, опрос готовности сужается владельцем. Обе строки спеки, обещающие обратное, снимаются.
  • pipeline: записывается то, что до сих пор было умолчанием, — выборка задачи воркером владельцем не сужается.
  • storage: у задачи в схеме появляется колонка владельца, и заводится она без умолчания.

Impact

  • схема хранилища: новый шаг с колонкой владельца в таблице задач; docs/database.md;
  • internal/entity, internal/contract: у задачи появляется владелец, чтение сужается им;
  • internal/adapter/repo/pocketbase: четыре места правки колонок очереди — applyToRecord, recordToJob, acquireColumns, acquiredRow;
  • internal/service: оба метода заведения задачи;
  • internal/controller/http: опрос готовности и приём;
  • публичный контракт GET /api/status/{id}: чужая запись начинает отвечать 404 там, где прежде отдавала содержимое. Форма ответа не меняется.