[apps-catalog umbrella] mono — Federation MWP (E9 demo pilot + E10 runtime federation + 9.3.b publish) #64
Reference in New Issue
Block a user
Delete Branch "apps-catalog"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Зонтичный PR для всей mono-стороны apps-catalog
Включает E9 (demo pilot), E10 (Runtime Federation backend) и 9.3.b (publishPackage/publishRelease). Все ранее открытые саб-PR свёрнуты сюда merge-commit-ом (история по фичам сохранена).
Свёрнутые саб-PR
Содержимое (по эпикам)
E10 Runtime Federation:
9.3.b publish mutations:
Дальше будет докинуто сюда (текущая работа):
Порядок merge
Epic 9 (mono часть) — финальный статус
Закрывает stories 9.3 + 9.4 + 9.5 на стороне mono. Парный PR в apps-catalog — C9S/apps-catalog#2.
Завершено в этом PR:
components/desktop/extensions/developer/— стол разработчика с 4 страницами-заглушками (packages/publish/subscribers/pricing), все требуют рольchairman.components/desktop/src/processes/init-installed-extensions/remote-loader.ts— каркас remote-loader для удалённых пакетов.components/desktop/src/processes/init-installed-extensions/index.ts— второй проход remote-extensions после bundle.components/desktop/src/pages/ExtensionStore/ExtensionsShowcase/ExtensionsShowcase.vue— hint-banner Epic 9 (объясняет, что remote-пакеты появятся).E2E pilot smoke (admin-side в apps-catalog) — PASS (4/5 hard, 5 soft-warn audit-routing). Полная backend-цепочка
seed → setglobals → setpricing → register package → create release → watcher aliveработает без warn-разрывов после 9.6.c (фикс CachedPricingReadAdapter критического пути).Отложено в V2:
Story 9.5.b mono часть: добавляет модуль расширения `apps-catalog-proxy` с GraphQL Query.appsCatalogRemotePackages, который проксирует запрос на apps-catalog/ca-admin /v1/public/packages. Архитектурно — выбран альтернатива C (см. apps-catalog docs/epics/E9-demo-app-pilot.md, V2 follow-ups): браузер ходит в каталог через mono controller, а не напрямую в ca-admin. Поэтому: - admin-secret apps-catalog'а живёт в env controller'а, не в SPA; - нет CORS — desktop запрос на свой же origin; - единый канон mono GraphQL (никаких raw HTTP в desktop). Компоненты: - AppsCatalogRemotePackageDTO (@ObjectType): packageId/publisher/ compatibleSubnets/lastActiveVersion + UI-поля title/description/ rubPerMonth (в V1 description и rubPerMonth — заглушки; в V2 будут из package manifest и apps-catalog pricing). - AppsCatalogHttpService: axios-клиент к ca-admin с Authorization: Bearer admin-key из APPS_CATALOG_API_KEY. Degraded mode (пустой список без ошибки), если env пустой — чтобы dev/CI без apps-catalog не валили desktop boot. - AppsCatalogProxyResolver: @Query, @UseGuards(GqlJwtAuthGuard) — каталог видят только авторизованные пайщики. - Регистрация в ExtensionsModule. Config: - Новые env APPS_CATALOG_URL и APPS_CATALOG_API_KEY (оба optional). - Зеркальные поля в config.apps_catalog.{url, api_key}. TODO (требует ручного запуска на остановленном контейнере, см. CLAUDE.md: 'pnpm generate-schema / generate-client — не запускать локально'): - pnpm -F controller generate-schema → пересоздать schema.gql. - pnpm -F controller generate-client → graphql-zeus в SDK. - pnpm -F sdk build → unbuild dist. Без codegen фронт ещё не сможет дёрнуть резолвер через Zeus.При nullable=true декоратор @Field NestJS GraphQL не умеет вывести тип из TS-reflection-метаданных (TS пишет в metadata-ключ Object). Без явного type fn (() => String) — UndefinedTypeError на старте. Фикс: - @Field({ nullable: true }) → @Field(() => String, { nullable: true }) для lastActiveVersion: string | null. - .env.example добавлены APPS_CATALOG_URL и APPS_CATALOG_API_KEY с готовыми значениями для dev (URL = DNS-имя ca-admin в mono-shared сети). Verify: coopback стартует чисто (Nest application successfully started), DTO виден через introspection (__type AppsCatalogRemotePackageDTO → поля packageId/publisher/title/description/rubPerMonth/lastActiveVersion/ compatibleSubnets), guard срабатывает (без JWT → 401 Unauthorized).mono controller: - AppsCatalogHttpService.fetchInstallScript(scope, name) — fetch text install.js из ca-admin /v1/public/packages/:scope/:name/install.js; degraded mode (null) при отсутствии env-config'а apps-catalog'а. - Новый REST controller AppsCatalogInstallScriptController `GET /v1/apps-catalog/install/:scope/:name` под HttpJwtAuthGuard: валидирует scope/name regex'ом, проксирует на ca-admin, отдаёт text/javascript + Cache-Control: no-store. Admin-API key остаётся на backend'е. - AppsCatalogProxyModule: добавлен controller в module.controllers[]. mono desktop: - remote-loader.ts: реальный fetch + CJS-eval. fetchInstalledRemotePackages читает Queries.Extensions.AppsCatalogRemotePackages, парсит packageId на (scope, name). loadRemoteExtension качает install.js по REST через axios responseType:'text' с JWT desktop'а, оборачивает в `new Function('module','exports','require', code)`, вызывает `module.exports.install()` → IWorkspaceConfig[]. - Try/catch per item (NFR15-стиль): падение одного пакета не валит остальные. require() сразу throw — пакеты должны быть CJS без внешних зависимостей (sandbox — Story 11.x). - sha256 пока не проверяем (release-таблица будет в 9.4.c). Demo path: voskhod publishes @voskhod/demo-app → ca-admin отдаёт examples/demo-app/install.js → controller-proxy → desktop boot тянет + eval'ит + получает workspace «Demo» с роутом demo-hello.Epic 10 Story 10.1 — coopback становится первым federation v2 subgraph'ом. Локальная core-схема компилируется с federation директивами (`autoSchemaFile.federation: 2`). Endpoint остаётся на /v1/graphql; non-federated клиенты (desktop) не должны заметить переключения — Apollo Federation добавляет лишь служебные `_service { sdl }` и `_entities` query. Gateway-режим (Apollo Gateway + IntrospectAndCompose) зайдёт отдельной story 10.1b — поверх этого subgraph'а + registry в Postgres. На MVP пока один subgraph, gateway не нужен. Зависимости: - + @apollo/subgraph ^2 (peer для federation 2 в @nestjs/apollo) @key директивы на core entity (User/Cooperative/Account/Wallet) добавятся по мере вытаскивания первых extension'ов — отдельными точечными PR.Epic 10 Story 10.2 — SDK для разработки backend-расширений Apollo Federation v2 subgraph'ов экосистемы кооператива. Что внутри: - ExtensionFederationModule.forRoot() — готовый GraphQL модуль с ApolloFederationDriver + autoSchemaFile.federation: 2 - ExtensionAuthModule.forRoot({ secret }) — passport-jwt стратегия, валидирует токен который Apollo Gateway forward'ит в Authorization-header. - ExtensionJwtAuthGuard — guard для GraphQL resolver'ов - SharedCooperator/SharedCooperative/SharedAccount — federation v2 stub'ы на core-entity для cross-extension references (@key + @external) - HealthController GET /_health — обязательный для orchestrator'а - loadExtensionConfig() — fail-fast загрузчик env'ов Templates: - Dockerfile multi-stage (builder + runtime + wget healthcheck) - docker-compose.snippet.yaml — шаблон service'а для orchestrator'а Контракт фиксирован Epic 10 PRD (apps-catalog/docs/epics/E10-federation-runtime.md) и Architecture v3 (blago req 8b-architecture-v3-runtime-federation).Epic 10 Story 10.2b — минимальный subgraph на extension-sdk доказывает что Federation v2 driver + JWT module + healthcheck реально работают вместе. Smoke результат (SUBGRAPH_PORT=33001 JWT_SECRET=test-secret COOPNAME=voskhod): - GET /_health → {"status":"ok","version":"unknown","uptimeSeconds":N} - POST /v1/graphql { helloFromExtension(name:"мир") { greeting fromExtension } } → {"greeting":"Здравствуй, мир!","fromExtension":"hello-subgraph@0.1.0"} - POST /v1/graphql { _service { sdl } } → returns federation v2 SDL с @key/@external/@requires/@shareable директивами Также в этом коммите: - extension-sdk переключён с unbuild на обычный tsc (unbuild esbuild не поддерживает декораторы для target ES2020). main: dist/index.js, CommonJS. - @types/passport-jwt в devDeps. Cross-extension entity references (SharedCooperator @key + ResolveField) живут в chatcoop subgraph'е (story 10.8) — там есть локальная сущность ChatThread которая ссылается на пайщика. Этот PoC только доказывает базовую механику.Epic 10 Story 10.3 — отдельный per-tenant Nest-сервис, объединяющий: 1. Apollo Federation Gateway (ApolloGatewayDriver) на /v1/graphql. 2. REST orchestrator API на /v1/internal/{extensions,composition}/*. 3. Subgraph registry в Postgres (TypeORM entity SubgraphRegistryEntity). Что работает: - Bootstrap seed'ит запись core-subgraph'а (coopback) → IntrospectAndCompose стартует с одним subgraph'ом → gateway проксирует все queries в core. - JWT forwarding: Authorization-header forward'ится в каждый subgraph через RemoteGraphQLDataSource.willSendRequest. - POST /v1/internal/extensions/install { packageId, version, url } — upsert в registry. - DELETE /v1/internal/extensions/uninstall/:packageId — deactivate. - POST /v1/internal/composition/refresh — 202 Accepted (реальный recompose по polling'у IntrospectAndCompose, ≤10 сек). - GET /v1/internal/composition/status — текущие subgraph'ы. Trade-off MVP: - subgraphs читаются ОДИН раз на bootstrap (Apollo 2.9 IntrospectAndCompose ожидает статичный массив). Добавление НОВОГО subgraph'а → требует рестарта контейнера gateway-orchestrator. Warmup ~5 сек, делается orchestrator-pipeline при install'е (Story 10.4). - Реальный docker pull/up/healthcheck — Story 10.4 (Install pipeline). - Динамическая supergraph composition без рестарта — Story 10.3b (custom SupergraphManager). - On-chain auto-update watcher — Story 10.5. ENV: GATEWAY_PORT, DATABASE_URL, CORE_SUBGRAPH_URL, COOPNAME, JWT_SECRET.apps-catalog: integration research + bundle/remote coexistence planto [apps-catalog umbrella] mono — Federation MWP (E9 demo pilot + E10 runtime federation + 9.3.b publish)Story 10.4 — pipeline установки subgraph-расширения: 1. (опц.) OCI token у CA-auth → docker pull образа; 2. (опц.) docker compose up сервиса; 3. healthcheck poll по url subgraph'а до 200 OK; 4. upsert в subgraph_registry (Apollo Gateway за ≤10с подхватит); 5. на failure — composeDown rollback + discriminated outcome {applied|failed}. InstallController стал тонкой обёрткой над InstallOrchestratorService. Внешние зависимости (docker shell / fetch / CA-auth) изолированы за портами; тесты (7/7 green) гоняют сценарий через ин-мемори моки без реального docker daemon'а и сети. Что ОСТАЁТСЯ для отдельной ветки под этим зонтиком: - wiring approve→install REST из mono controller (mutation approvePackage); - desktop notification после успешного install'а; - E2E на реальном compose + chatcoop subgraph (Story 10.8).Story 9.9 «Стол восхода: approve pending packages» — backend-сторона. GraphQL contract: - Query.appsCatalogPendingModerations(status?: ModerationStatusEnum, limit?: Int) → [ModerationRequestDTO]. По умолчанию SUBMITTED. Chairman-only. - Mutation.approveModeration(data: ApproveModerationInputDTO!) → ApproveModerationResultDTO. Discriminated outcome: applied / pendingChain (423) / conflict (409) / requiresOverride (403) / failed. - Mutation.rejectModeration(data: RejectModerationInputDTO!) → RejectModerationResultDTO. Discriminated outcome: applied / conflict / failed. HTTP-service: 3 новых метода (listSubmittedModerations / approveModeration / rejectModeration) на ca-admin endpoints, добавленных параллельным apps-catalog PR #7. Mapping snake_case → camelCase; degraded mode на list — пустой массив, на mutations — failed с явной причиной (чтобы UI не вводился в заблуждение «approved»). ReleaseScope передаётся как InputType с union-style полями (type + subnets?/coopnames?); резолвер валидирует соответствие type → непустой массив перед отправкой на ca-admin. Зависимость: apps-catalog PR #7 (GET /v1/admin/moderation) должен быть смёрджен в feat/E9-demo-app-pilot до того, как этот endpoint можно использовать в e2e. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>