[598][@ant] feat(marketplace): documentation pass + grants canon + onboarding via council + payout/IDOR/own-KU fixes #46

Merged
ant merged 32 commits from feat/marketplace-docs into marketplace2 2026-05-28 16:41:26 +00:00
Owner

Что вливаем

40 коммитов работы, накопленных в feat/marketplace-docs — изначально документационная ветка, фактически выросла в полноценный набор фиксов и фич Эпиков 1–11 Стола заказов.

Ключевые блоки

Документация и harness

  • Полные локализованные скриншоты и проза приёмок (магистраль II), returns/writeoff flow, ready-to-receive с реальными данными.
  • Страница «Открытие выдачи (подпись председателя)» — закрыта дыра в end-to-end документации поставки (signiss1).
  • Страница «Типы снабжения» (cycle_type) для заказчика и поставщика.
  • docs-harness: hash-routing + cold-Vite + onboarding-dialog; параметризация под cycle_type.

Backend / контроллер

  • Контрактный мост списания → повестка совета (Story 8.1) + восстановление ledger2-модели Стола заказов.
  • own-KU ownership scoping в read/payload-резолверах + own-KU лента поставок ПВЗ и whoAmI.branches (Поток IV).
  • Read-only лента выплат поставщикам для совета (Payment:read:all).
  • Auto-seed карты ПВЗ при reboot:extra + sentinel-username для анонимных мутаций.
  • Кассирское подтверждение выплаты доводится до COMPLETED (payment_hash = order_hash + outcome_hash lowercase).
  • IDOR ownership-проверка превью акта выдачи (по order_id).
  • STATUS_MAP приведён к коротким on-chain именам (supplyprep/acceptcoop/readyrecv).
  • Дефолтная генерация EAN при выдаче этикеток партии.
  • Списание скоропорта по факту истечения срока + post_expiry_grace_days; автопроект включён по умолчанию.

Канон расширений и онбординг ЦПП

  • Единый канон авторизации столов через grants — устранён дубль meta.roles ↔ access-matrix.
  • Канон онбординга расширений — гейтинг столов до принятия ЦПП + редирект при включении.
  • Реальный L1-онбординг ЦПП «Стол заказов» через повестку совета вместо stub (Положение 1107 + оферта 1100) — состояние меняется по ончейн-решению как в Капитале.
  • Регенерация GraphQL-схемы и Zeus-клиента под DesktopWorkspace.grants.
  • Трактовка grants=null как core-стол (legacy meta.roles).

Desktop

  • Детальный просмотр и правка предложений marketplace.
  • Синхронизация активного стола с текущим маршрутом.
  • Разнесение «Стола заказов» на 4 ролевых стола.
  • Не блокировать загрузку приложения ожиданием SDK ChatWoot.
  • Кнопка «Запустить поставку» на столе «Мои предложения» + актуализация supply-types.
  • Батч-акцепт сводной заявки и триггер open_subscription (SDK + desktop + aggregator).
  • Восстановление сессии до первого getDesktop в init-app — иначе grant-столы расширений получали гостевой пустой набор grants и не появлялись.

Конфиг и инфра

  • Минимизация конфига установки расширения marketplace + русификация настроек.
  • Человеческий заголовок группе настроек writeoff.
  • Logging HTTP-статуса и тела ошибки Novu (вместо [object Object]).
  • nodemon debounce + SIGTERM в controller/nodemon.json — устранить restart-storm на bind-mount.

Совместимость с marketplace2

Текущий merge origin/marketplace2 → feat/marketplace-docs уже завершён (коммит 2734d9dc633) — подтянут PR #16 trustees-ownership :own-KU (MarketplaceBranchOwnershipService применён в return-claim resolver). После него regen schema.gql и Zeus-клиента (коммит d2c0f605d02).

Поэтому merge в marketplace2 = fast-forward без конфликтов.

Проверки

  • pnpm typecheck в controller — green.
  • pnpm eslint по 4 затронутым merge-файлам — clean.
  • pnpm generate-schema + pnpm generate-client + pnpm build в SDK — без ошибок.
  • ⚠️ Полный e2e не прогонялся (нужен поднятый стек) — отдельный шаг после merge.

Следующий шаг

После merge — заводим origin/dev → marketplace2 (марш в magistral III).

## Что вливаем 40 коммитов работы, накопленных в `feat/marketplace-docs` — изначально документационная ветка, фактически выросла в полноценный набор фиксов и фич Эпиков 1–11 Стола заказов. ## Ключевые блоки ### Документация и harness - Полные локализованные скриншоты и проза приёмок (магистраль II), returns/writeoff flow, ready-to-receive с реальными данными. - Страница «Открытие выдачи (подпись председателя)» — закрыта дыра в end-to-end документации поставки (signiss1). - Страница «Типы снабжения» (cycle_type) для заказчика и поставщика. - docs-harness: hash-routing + cold-Vite + onboarding-dialog; параметризация под cycle_type. ### Backend / контроллер - Контрактный мост списания → повестка совета (Story 8.1) + восстановление ledger2-модели Стола заказов. - own-KU ownership scoping в read/payload-резолверах + own-KU лента поставок ПВЗ и whoAmI.branches (Поток IV). - Read-only лента выплат поставщикам для совета (Payment:read:all). - Auto-seed карты ПВЗ при reboot:extra + sentinel-username для анонимных мутаций. - Кассирское подтверждение выплаты доводится до COMPLETED (payment_hash = order_hash + outcome_hash lowercase). - IDOR ownership-проверка превью акта выдачи (по order_id). - STATUS_MAP приведён к коротким on-chain именам (supplyprep/acceptcoop/readyrecv). - Дефолтная генерация EAN при выдаче этикеток партии. - Списание скоропорта по факту истечения срока + post_expiry_grace_days; автопроект включён по умолчанию. ### Канон расширений и онбординг ЦПП - Единый канон авторизации столов через `grants` — устранён дубль meta.roles ↔ access-matrix. - Канон онбординга расширений — гейтинг столов до принятия ЦПП + редирект при включении. - Реальный L1-онбординг ЦПП «Стол заказов» через повестку совета вместо stub (Положение 1107 + оферта 1100) — состояние меняется по ончейн-решению как в Капитале. - Регенерация GraphQL-схемы и Zeus-клиента под DesktopWorkspace.grants. - Трактовка `grants=null` как core-стол (legacy meta.roles). ### Desktop - Детальный просмотр и правка предложений marketplace. - Синхронизация активного стола с текущим маршрутом. - Разнесение «Стола заказов» на 4 ролевых стола. - Не блокировать загрузку приложения ожиданием SDK ChatWoot. - Кнопка «Запустить поставку» на столе «Мои предложения» + актуализация supply-types. - Батч-акцепт сводной заявки и триггер open_subscription (SDK + desktop + aggregator). - Восстановление сессии до первого getDesktop в init-app — иначе grant-столы расширений получали гостевой пустой набор grants и не появлялись. ### Конфиг и инфра - Минимизация конфига установки расширения marketplace + русификация настроек. - Человеческий заголовок группе настроек writeoff. - Logging HTTP-статуса и тела ошибки Novu (вместо [object Object]). - nodemon debounce + SIGTERM в controller/nodemon.json — устранить restart-storm на bind-mount. ## Совместимость с marketplace2 Текущий merge `origin/marketplace2 → feat/marketplace-docs` уже завершён (коммит `2734d9dc633`) — подтянут PR #16 trustees-ownership :own-KU (MarketplaceBranchOwnershipService применён в return-claim resolver). После него regen schema.gql и Zeus-клиента (коммит `d2c0f605d02`). Поэтому **merge в marketplace2 = fast-forward без конфликтов**. ## Проверки - ✅ `pnpm typecheck` в controller — green. - ✅ `pnpm eslint` по 4 затронутым merge-файлам — clean. - ✅ `pnpm generate-schema` + `pnpm generate-client` + `pnpm build` в SDK — без ошибок. - ⚠️ Полный e2e не прогонялся (нужен поднятый стек) — отдельный шаг после merge. ## Следующий шаг После merge — заводим `origin/dev → marketplace2` (марш в magistral III).
claude added 30 commits 2026-05-28 15:17:28 +00:00
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Систематический баг: 10 сценариев навигировали по URL без #/ (десктоп — hash-роутер),
из-за чего молча сохраняли спиннеры/404 вместо контента — корень «тонкого покрытия»
документации Marketplace. Плюс две устойчивости харнесса:
- loginAs/loginAsChairman: таймаут 60→150с (холодный Vite компилирует chunk роута
  signin дольше 60с на первом заходе);
- dismissOnboardingDialogs: распознавание онбординг-диалога на стадии «Формируем
  документ…» (кнопки «Подписать» ещё нет — ловим по фразе), иначе кадр ловит спиннер.
- operator/issuance: добавлен dismissOnboardingDialogs.

Доки обновлены реальными данными прогнанной e2e-магистрали Marketplace
(заказ → приёмка 2 подписи → ACCEPTED_TO_COOP → выплата COMPLETED → маркировка
EAN-13 → выдача signiss1/signiss2 → RECEIVED): каталог, мои заказы, сводный заказ,
предложения поставщика, входящие заказы, история выплат, ожидаемые поставки,
маркировка/склад КУ, лента выдач, экосистема, сеть ПВЗ.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Правки по голосовому ревью документации Marketplace:

- orderer-стол «Готово к получению»: заказ доведён по магистрали до
  READY_TO_RECEIVE (без финальной подписи), сценарий перестроен ОТ этой
  страницы (а не от вкладки «Получены»); проза уточнена — финальная подпись
  получения оформляется на столе оператора ПВЗ по штрих-коду.
- board/payouts: лента выплат показывает реальные данные (1 PENDING + 2
  COMPLETED) — данные засеяны магистралью; страница уже авто-грузится onMounted.
- удалены legacy-доки orderer/receive.md и branch-chairman/apl-reception-close.md
  + их nav-записи; висячие reference-ссылки перенаправлены на issuance-finalize /
  apl-reception-create / ready-to-receive (mkdocs --strict: по marketplace чисто).
- YANDEX_MAPS_API_KEY проброшен через EnvVars + generateConfig + fallback envCache
  (ключ только в gitignore'нутом .env; в config.default.js — пустой placeholder);
  карта Яндекса на странице ПВЗ рендерится, баннер «не задан ключ» убран; проза
  branches.md обновлена (карта есть всегда, точка — после геокодинга адреса).
- harness: устойчивая навигация ready-to-receive/pvz-list (ретрай + полный
  перезагруз против гонки core-guard/fetchRoles), ожидание DOM-рендера карты,
  dismiss онбординга на pvz-list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Колонка chairman_signed_at в QTable не отображала значение (нет format-функции,
сырой ISO не рендерился) — добавлен formatDate с ru-локалью (как в board/payouts),
теперь видна дата открытия выдачи. Обновлён скриншот документации.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boot-файл ждал window.chatwootSDK через polling БЕЗ таймаута (await
waitForChatWootReady), поэтому недоступный support.coopenomics.world вешал
всю инициализацию Quasar → бесконечный спиннер. Вынес доинициализацию чата
в отделённую (неожидаемую) задачу + жёсткий 15s таймаут: boot завершается
сразу после app.use(chatwoot), недоступный support чат лишь отключает, но
загрузку приложения не блокирует.
Расширение market давало один смешанный стол из 21 страницы (заказчик +
поставщик + админ вперемешку) и отдельный Стол ПВЗ, причём market-pvz
вообще не объявлялся в backend AppRegistry → его маршруты молча терялись.

- backend extensions.registry.ts: AppRegistry['market'].desktops теперь
  объявляет 4 стола (market/market-supplier/market-pvz/market-admin) —
  фронт прикрепляет маршруты только к объявленным backend столам.
- desktop install.ts: возвращает 4 workspace, 29 страниц разнесены по
  ролям; Стол администратора виден только chairman+member (meta.roles),
  остальные — всем; имена маршрутов сохранены (навигация по имени).
- init-installed-extensions: убран немой catch{} (теперь console.error)
  + warn, когда install вернул workspace, отсутствующий на backend, —
  раньше сломанный/несинхронный стол выглядел как «пустой» без диагностики.

Проверено на стенде: getDesktop = 4 стола, Pinia-стор = children 6/6/8/9,
скриншот навигатора подтверждает рендер страниц по столам.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Активный стол (activeWorkspaceName) ставился только кликом по плитке,
редиректом с index и из localStorage. При прямом заходе по URL или переходе
кнопкой со стола А на маршрут стола Б активным оставался прежний стол — в
шапке и левом меню «висел» не тот стол (видно было на playwright-кадрах как
постоянный «Председатель»).

- DesktopStore.syncActiveWorkspaceFromRoute(matchedNames): находит стол по
  имени родительского маршрута (mainRoute.name) в цепочке to.matched и делает
  его активным без навигации и без спиннера.
- navigation-guard: router.afterEach зовёт синк после каждого перехода.
- init-app: однократный досинк после установки расширений (deep-link при
  холодной загрузке, когда afterEach сработал до прикрепления маршрутов).

Проверено на стенде: прямой заход на /market-supplier|market-admin|market/*
переключает активный стол и левое меню на нужный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Карточки CatalogOfferCard на модерации и в «Мои предложения» нельзя было
открыть — описание обрезано, действий нет. Добавлен widget OfferDetailsDialog
(+ секция витрины дизайн-системы): клик по карточке открывает модалку с полным
описанием, метой (категория/тип отсечки/гарантия/поставщик), причиной отклонения
и slot actions под ролевые кнопки.

- Модерация (председатель): открыть → прочитать → Одобрить / Отклонить
  (добавлена мутация rejectOffer с обязательной причиной — раньше в UI её не было).
- Мои предложения (поставщик): открыть → Редактировать / Снять / Запустить поставку.
- Правка: скрытый роут marketplace-edit-offer переиспользует форму создания в
  режиме редактирования (префилл из listMyOffers, сабмит → marketplaceUpdateOffer).
  Бэкенд сбрасывает статус в PENDING_MODERATION — правка уходит на повторную
  модерацию в рамках того же предложения (баннер предупреждает об этом).

Проверено на живом стенде: диалоги открываются на обоих столах, Редактировать
ведёт на предзаполненную форму. ESLint чист, vue-tsc без ошибок в затронутых файлах.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Из формы установки расширения market убраны неиспользуемые в коде поля
enabled/lastSyncTimestamp/debug; coopAcceptance помечен visible:false как
системное состояние совета; настройки writeoff получили русские label/note
через describeField — по канону расширения capital. Чтобы при установке
приложения председатель видел минимальную понятную форму на русском, без
отладочных и внутренних параметров.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Родительскому объекту writeoff заданы label «Списание скоропорта» и note
через describeField; рендерер формы (ZodForm) показывает getLabel/getHint и
для объектов-групп, поэтому без описания отображалось техническое «writeoff».

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Семантика крона списания исправлена: в кандидаты попадают позиции с УЖЕ
истёкшим сроком годности (expiry_date <= now), а не за N дней до истечения —
списываем испорченное, а не заранее. Параметр expiry_grace_days удалён как
вводящий в заблуждение. auto_proposal_enabled теперь true по умолчанию, чтобы
проект списания формировался без ручной активации. Крон остаётся ежемесячным
(@Cron EVERY_1ST_DAY_OF_MONTH ≈ раз в 30 дней) — устойчив к рестартам, в
отличие от setInterval (лимит ~24.8 сут не покрывает 30-дневный интервал).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Возвращён регулируемый параметр списания, но с корректной семантикой: товар
попадает в проект списания, только пролежав N дней ПОСЛЕ истечения срока
(expiry_date <= now - grace), default 7. Свежепросроченное ещё может быть
забрано получателем — списываем лишь окончательно испорченное. Крон остаётся
ежемесячным (каждое 1-е число).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Опциональный generic-канон для расширений с подключением на уровне кооператива:
- IRegistryExtension получает onboarding_route / onboarding_desktop / isOnboarded(config);
- getDesktop, пока isOnboarded(config)===false, отдаёт ТОЛЬКО onboarding_desktop —
  остальные столы скрыты из переключателя (для market: только Стол администратора);
- desktop install.ts зеркалит решение бэкенда и ограничивает админ-стол одной
  страницей подключения ЦПП и ролью chairman — до решения совета никто, кроме
  председателя, не видит ни столов, ни страниц;
- loadExtensionRoutes возвращает workspace-конфиги; кнопки включения/установки
  редиректят на defaultRoute первого стола — для расширений с онбордингом это
  страница подключения, чтобы администратор не терялся после включения.

Расширения без этих полей работают как раньше (опциональность). Источник
безопасности — backend-резолверы; гейтинг столов — UX-слой поверх них.
Для market: isOnboarded = config.coopAcceptance.accepted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Дописана секция в EXTENSIONS_SCHEMA_SYSTEM.md: контракт реестра
(onboarding_route/onboarding_desktop/isOnboarded), две согласованные половины
гейтинга (backend getDesktop скрывает столы, frontend install.ts ограничивает
стол ролью и одной страницей), редирект при включении, жизненный цикл и
ограничения (нужна перезагрузка после принятия ЦПП), опциональность.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
getDesktop отдаёт DesktopWorkspace.grants (права текущего пользователя), фронт
сверяет meta.requires маршрута с ними (DesktopStore.isPageVisible/isWorkspaceVisible/
hasRouteAccess). Авторизацию считает только backend, фронт отражает. Провайдер
грантов расширения само-регистрируется в @Global ExtensionGrantsRegistry (нет цикла
модулей); getDesktop под OptionalGqlJwtAuthGuard. Онбординг сведён к грантам: до ЦПП
у председателя только Extension:configure → виден лишь Стол админа со страницей
подключения. market переведён на meta.requires; убраны onboarding-поля реестра,
desktop-фильтр и фронтовые beforeEnter/онбординг-ветка. Зачем: убрать дублирующую
логику видимости столов в двух местах и подготовить per-page доступ из бэкенда.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
generate-schema + generate-client + сборка SDK после добавления поля grants.
schema.gql и components/sdk/src/zeus/* теперь содержат grants — фронт штатно
выбирает поле через selectors/desktop/workspaceSelector.ts (канон авторизации
столов активирован end-to-end). dist в .gitignore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- cooptypes 1107.MarketplaceProgramTemplate (Положение ЦПП, placeholder) + factory-генератор
- marketplace подключён к платформенному онбордингу (register-marketplace-onboarding-steps),
  coopAcceptance.accepted выводится из all-steps-done в initialize() (auto-restart по ONBOARDING_COMPLETED)
- desktop: страница онбординга переведена на shared CouncilOnboardingCard + generic onboarding SDK
- дока ядра онбординга: components/context/notes/EXTENSION_ONBOARDING_CORE.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень: на холодной перезагрузке client пересоздаётся без JWT; desktops.loadDesktop() в init-app выполнялся ДО useInitWalletProcess().run()→session.init()→globalStore.init()→client.setToken(). Гостевой getDesktop → grants=[] у market-столов; повторного refetch после авторизации не было. Легаси-столы выживали (видимость реактивна от session.isChairman), grant-столы — нет. session.init идемпотентен, повторный вызов в init-wallet = no-op.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-apl-reception.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-inventory.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-issuance.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-return-claim.resolver.ts
claude added 2 commits 2026-05-28 16:03:55 +00:00
# Conflicts:
#	components/controller/schema.gql
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-apl-reception.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-inventory.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-issuance.resolver.ts
#	components/controller/src/extensions/marketplace/application/resolvers/marketplace-return-claim.resolver.ts
#	components/desktop/extensions/market/install.ts
#	components/desktop/src/pages/Marketplace/OffererMyOffers/ui/OffererMyOffersPage.vue
ant merged commit 06b80527c7 into marketplace2 2026-05-28 16:41:26 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: C9S/mono#46