Концепции, ценность и типичные сценарии клонирования — меньше кода.
Концепции, ценность и типичные сценарии клонирования — меньше кода.
Preparing Ring content
Preparing Ring content
Preparing Ring content
Ring Opportunities это цикл предложения ↔ спроса для каждого клона: участники публикуют предложения и запросы (плюс специализированные типы), Matcher оценивает заинтересованных пользователей, а уведомления приглашают их связаться, не дожидаясь, пока кто-то будет искать в ленте.
Список просмотра на /opportunities это именная лента: каждая карточка показывает безопасного для публики автора, человеческие теги и Like / Save / Hide / Contact. Живые вставки по-прежнему приходят через Tunnel; панель просмотра больше не показывает строку статуса websocket.
Используйте вкладки Founder / Developer в боковой панели документации, чтобы отфильтровать эту страницу. Frontmatter audience управляет блоками на странице; видимость в боковой панели управляется lib/docs/audience-curated-docs.ts.
Старые тексты заявляли фиксированные проценты точности, экономию на найме и «15k+ пользователей». Эти цифры устарели. Актуальные числа берутся из аналитики Admin → Matcher и трафика клона. Пороги Matcher это конфиг (ring-config.json → matcher, переопределяется в настройках платформы), а не SLA для всего королевства.
| Раньше | Эквивалент в Ring |
|---|---|
| Опубликовать вакансию и надеяться, что кто-то найдёт | Опубликовать возможность → Matcher оценивает кандидатов → уведомление с короткими пояснениями |
| Только предложения (работодатель → талант) | Двойная природа: offer и request плюс специализированные типы |
| Одна таксономия объявлений | White-label ограничение opportunities.enabledTypes (16 по умолчанию в Layer1 / конфиге org) |
| Ручное обогащение | Необязательное LLM автозаполнение при создании (OpportunityAutoFillService) |
| Непрозрачное ранжирование | Восемь оцениваемых факторов (MatchFactors в lib/ai/types.ts) + настраиваемый scoreThreshold |
| UUID автора на карточках | Безопасные для публики имя / аватар; username только когда publicProfile равен true |
Чипы sourceMessageId: | Скрытые машинные теги; From chat только для владельца, когда есть происхождение |
| «Live Updates Active / via websocket» в просмотре | Тихий useRealtimeOpportunities; строка статуса остаётся только на странице деталей |
| Отдельные копилки | Коллективные копилки и копилки строителей через Public Pools (путь пожертвований работает; on-chain escrow ограничен) |
Значения Matcher по умолчанию при установке (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env может переопределить автоодобрение через MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (см. features/admin/platform-settings/matcher-config.ts). Ограничивайте число совпадений через MAX_MATCHES_PER_OPPORTUNITY.
Факторы оценки, пути LLM и эвристики и длина пояснений описаны на странице AI Matcher. Пороги выше это значения установки по умолчанию, а не опубликованный KPI точности.
Возможности превращают ваш Ring в локальный маркетплейс намерений: люди публикуют, что могут предоставить и что им нужно. Matcher отправляет уместные приглашения вместо того, чтобы полагаться на прокрутку ленты. Специализированные типы (вакансии, коллективные заказы, услуги по расписанию, программа/инвестиция → CRM, квесты кастомизации Ring) ограничиваются для каждого клона, поэтому вы запускаете только те циклы, которые нужны вашей вертикали.
Связывайте возможности с верифицированными организациями через contactInfo.linkedEntity / organizationId.
Приглашения по совпадению это настоящие уведомления, а не маркетинговые email-рассылки.
Contact открывает . Преобразования задач из чата сохраняют для владельца глубокую ссылку .
Используйте ring-ai-matcher-specialist (или навык Cursor из того же семейства) с промптом, ожидающим одобрения:
«Проверь цикл Opportunities этого клона: выведи
opportunities.enabledTypesиmatcherизring-config.json, подтверди, что env для LLM задан, сделай пробное создание одногоofferи одногоrequest, сообщи MatchFactors и перевёл быautoApprovepending→active. Открой/opportunitiesкак два пользователя и подтверди, что карточки показывают имена (а не UUID), Hide исключает после перезагрузки, а Contact ведёт на/messages?user=. Не меняй platform_settings, пока я не одобрю.»
Чеклист входных данных: ключ клона, доступ администратора, имеющиеся учётные данные LLM, тестовый id организации для organizationId.
Задайте opportunities.enabledTypes и matcher.* в ring-config.json; передеплойте или перезагрузите снимок конфига так, как ваш клон делает это для ring-config.
В Admin → Matcher / AI settings оставьте автоодобрение выключенным; задайте scoreThreshold / maxMatches под толерантность вашей вертикали.
Создайте тестовую возможность через UI выбора типа (или POST /api/opportunities); откройте My Opportunities на /opportunities/my.
Убедитесь, что пользователь с совпадением получил уведомление и что списки поиска обновляются (Tunnel / мягкое обновление) без выдуманного шага переиндексации. Убедитесь, что у списка просмотра нет строки статуса websocket.
Нет. По умолчанию matcher.autoApprove равен false. Когда включено, maybeAutoApproveOpportunity всё равно требует порогов оценки и уверенности LLM (autoApproveMinScore, llmConfidenceGate).
Нет. Считайте исторические маркетинговые проценты устаревшими. Используйте аналитику Admin Matcher для своего тенанта.
Нет. Аутентифицированные зрители видят имя и аватар. Username добавляется только когда publicProfile равен true. Флага postAnonymously нет.
Только когда DB_BACKEND_MODE=firebase-full. Клоны PostgreSQL используют opportunity-db-mapper.ts и SerializedOpportunity.
/opportunities/my-opportunities?Удалён. Каноническая панель это /opportunities/my через ROUTES.MY_OPPORTUNITIES.
sourceMessageId:?Это было машинное происхождение, а не теги пользователя. Новые преобразования записывают metadata.source; карточка скрывает устаревшие закодированные теги и показывает владельцу From chat, когда есть id разговора.
Проверьте доступность LLM, порог оценки Matcher, maxMatches и фильтры блокировки организаций в matcher-notification-filter. Уведомления идут через features/notifications/services/notification-service.
Кэш курсорной ленты имеет ключ для каждого зрителя. Hide хранится в user_content_interactions для этого пользователя; следующий getOpportunitiesForRole с этим viewerUserId применяет not-in.
Специализации возможностей могут содержать JSONB metadata (напр. слоты коллективного заказа). Копилки общественного финансирования и SSOT выплат строителям описаны в Public Pools; не придумывайте API «донат-продуктов» PSP как путь для копилок.
Следующий шаг: контракт REST-маршрутов, поля обогащения списка и пагинация поиска против просмотра.
Зависит от: как syncOpportunityDiscovery инвалидирует кэш и публикует события Tunnel.
Тот же процесс: convertTaskToOpportunity записывает metadata.source для чипа From chat.
Тот же процесс: Contact использует /messages?user=; From chat использует /messages?c=.
Ring Opportunities это цикл предложения ↔ спроса для каждого клона: участники публикуют предложения и запросы (плюс специализированные типы), Matcher оценивает заинтересованных пользователей, а уведомления приглашают их связаться, не дожидаясь, пока кто-то будет искать в ленте.
Список просмотра на /opportunities это именная лента: каждая карточка показывает безопасного для публики автора, человеческие теги и Like / Save / Hide / Contact. Живые вставки по-прежнему приходят через Tunnel; панель просмотра больше не показывает строку статуса websocket.
Используйте вкладки Founder / Developer в боковой панели документации, чтобы отфильтровать эту страницу. Frontmatter audience управляет блоками на странице; видимость в боковой панели управляется lib/docs/audience-curated-docs.ts.
Старые тексты заявляли фиксированные проценты точности, экономию на найме и «15k+ пользователей». Эти цифры устарели. Актуальные числа берутся из аналитики Admin → Matcher и трафика клона. Пороги Matcher это конфиг (ring-config.json → matcher, переопределяется в настройках платформы), а не SLA для всего королевства.
| Раньше | Эквивалент в Ring |
|---|---|
| Опубликовать вакансию и надеяться, что кто-то найдёт | Опубликовать возможность → Matcher оценивает кандидатов → уведомление с короткими пояснениями |
| Только предложения (работодатель → талант) | Двойная природа: offer и request плюс специализированные типы |
| Одна таксономия объявлений | White-label ограничение opportunities.enabledTypes (16 по умолчанию в Layer1 / конфиге org) |
| Ручное обогащение | Необязательное LLM автозаполнение при создании (OpportunityAutoFillService) |
| Непрозрачное ранжирование | Восемь оцениваемых факторов (MatchFactors в lib/ai/types.ts) + настраиваемый scoreThreshold |
| UUID автора на карточках | Безопасные для публики имя / аватар; username только когда publicProfile равен true |
Чипы sourceMessageId: | Скрытые машинные теги; From chat только для владельца, когда есть происхождение |
| «Live Updates Active / via websocket» в просмотре | Тихий useRealtimeOpportunities; строка статуса остаётся только на странице деталей |
| Отдельные копилки | Коллективные копилки и копилки строителей через Public Pools (путь пожертвований работает; on-chain escrow ограничен) |
Значения Matcher по умолчанию при установке (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env может переопределить автоодобрение через MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (см. features/admin/platform-settings/matcher-config.ts). Ограничивайте число совпадений через MAX_MATCHES_PER_OPPORTUNITY.
Факторы оценки, пути LLM и эвристики и длина пояснений описаны на странице AI Matcher. Пороги выше это значения установки по умолчанию, а не опубликованный KPI точности.
Возможности превращают ваш Ring в локальный маркетплейс намерений: люди публикуют, что могут предоставить и что им нужно. Matcher отправляет уместные приглашения вместо того, чтобы полагаться на прокрутку ленты. Специализированные типы (вакансии, коллективные заказы, услуги по расписанию, программа/инвестиция → CRM, квесты кастомизации Ring) ограничиваются для каждого клона, поэтому вы запускаете только те циклы, которые нужны вашей вертикали.
Связывайте возможности с верифицированными организациями через contactInfo.linkedEntity / organizationId.
Приглашения по совпадению это настоящие уведомления, а не маркетинговые email-рассылки.
Contact открывает . Преобразования задач из чата сохраняют для владельца глубокую ссылку .
Используйте ring-ai-matcher-specialist (или навык Cursor из того же семейства) с промптом, ожидающим одобрения:
«Проверь цикл Opportunities этого клона: выведи
opportunities.enabledTypesиmatcherизring-config.json, подтверди, что env для LLM задан, сделай пробное создание одногоofferи одногоrequest, сообщи MatchFactors и перевёл быautoApprovepending→active. Открой/opportunitiesкак два пользователя и подтверди, что карточки показывают имена (а не UUID), Hide исключает после перезагрузки, а Contact ведёт на/messages?user=. Не меняй platform_settings, пока я не одобрю.»
Чеклист входных данных: ключ клона, доступ администратора, имеющиеся учётные данные LLM, тестовый id организации для organizationId.
Задайте opportunities.enabledTypes и matcher.* в ring-config.json; передеплойте или перезагрузите снимок конфига так, как ваш клон делает это для ring-config.
В Admin → Matcher / AI settings оставьте автоодобрение выключенным; задайте scoreThreshold / maxMatches под толерантность вашей вертикали.
Создайте тестовую возможность через UI выбора типа (или POST /api/opportunities); откройте My Opportunities на /opportunities/my.
Убедитесь, что пользователь с совпадением получил уведомление и что списки поиска обновляются (Tunnel / мягкое обновление) без выдуманного шага переиндексации. Убедитесь, что у списка просмотра нет строки статуса websocket.
Нет. По умолчанию matcher.autoApprove равен false. Когда включено, maybeAutoApproveOpportunity всё равно требует порогов оценки и уверенности LLM (autoApproveMinScore, llmConfidenceGate).
Нет. Считайте исторические маркетинговые проценты устаревшими. Используйте аналитику Admin Matcher для своего тенанта.
Нет. Аутентифицированные зрители видят имя и аватар. Username добавляется только когда publicProfile равен true. Флага postAnonymously нет.
Только когда DB_BACKEND_MODE=firebase-full. Клоны PostgreSQL используют opportunity-db-mapper.ts и SerializedOpportunity.
/opportunities/my-opportunities?Удалён. Каноническая панель это /opportunities/my через ROUTES.MY_OPPORTUNITIES.
sourceMessageId:?Это было машинное происхождение, а не теги пользователя. Новые преобразования записывают metadata.source; карточка скрывает устаревшие закодированные теги и показывает владельцу From chat, когда есть id разговора.
Проверьте доступность LLM, порог оценки Matcher, maxMatches и фильтры блокировки организаций в matcher-notification-filter. Уведомления идут через features/notifications/services/notification-service.
Кэш курсорной ленты имеет ключ для каждого зрителя. Hide хранится в user_content_interactions для этого пользователя; следующий getOpportunitiesForRole с этим viewerUserId применяет not-in.
Специализации возможностей могут содержать JSONB metadata (напр. слоты коллективного заказа). Копилки общественного финансирования и SSOT выплат строителям описаны в Public Pools; не придумывайте API «донат-продуктов» PSP как путь для копилок.
Следующий шаг: контракт REST-маршрутов, поля обогащения списка и пагинация поиска против просмотра.
Зависит от: как syncOpportunityDiscovery инвалидирует кэш и публикует события Tunnel.
Тот же процесс: convertTaskToOpportunity записывает metadata.source для чипа From chat.
Тот же процесс: Contact использует /messages?user=; From chat использует /messages?c=.
Ring Opportunities это цикл предложения ↔ спроса для каждого клона: участники публикуют предложения и запросы (плюс специализированные типы), Matcher оценивает заинтересованных пользователей, а уведомления приглашают их связаться, не дожидаясь, пока кто-то будет искать в ленте.
Список просмотра на /opportunities это именная лента: каждая карточка показывает безопасного для публики автора, человеческие теги и Like / Save / Hide / Contact. Живые вставки по-прежнему приходят через Tunnel; панель просмотра больше не показывает строку статуса websocket.
Используйте вкладки Founder / Developer в боковой панели документации, чтобы отфильтровать эту страницу. Frontmatter audience управляет блоками на странице; видимость в боковой панели управляется lib/docs/audience-curated-docs.ts.
Старые тексты заявляли фиксированные проценты точности, экономию на найме и «15k+ пользователей». Эти цифры устарели. Актуальные числа берутся из аналитики Admin → Matcher и трафика клона. Пороги Matcher это конфиг (ring-config.json → matcher, переопределяется в настройках платформы), а не SLA для всего королевства.
| Раньше | Эквивалент в Ring |
|---|---|
| Опубликовать вакансию и надеяться, что кто-то найдёт | Опубликовать возможность → Matcher оценивает кандидатов → уведомление с короткими пояснениями |
| Только предложения (работодатель → талант) | Двойная природа: offer и request плюс специализированные типы |
| Одна таксономия объявлений | White-label ограничение opportunities.enabledTypes (16 по умолчанию в Layer1 / конфиге org) |
| Ручное обогащение | Необязательное LLM автозаполнение при создании (OpportunityAutoFillService) |
| Непрозрачное ранжирование | Восемь оцениваемых факторов (MatchFactors в lib/ai/types.ts) + настраиваемый scoreThreshold |
| UUID автора на карточках | Безопасные для публики имя / аватар; username только когда publicProfile равен true |
Чипы sourceMessageId: | Скрытые машинные теги; From chat только для владельца, когда есть происхождение |
| «Live Updates Active / via websocket» в просмотре | Тихий useRealtimeOpportunities; строка статуса остаётся только на странице деталей |
| Отдельные копилки | Коллективные копилки и копилки строителей через Public Pools (путь пожертвований работает; on-chain escrow ограничен) |
Значения Matcher по умолчанию при установке (ring-config.json → matcher): scoreThreshold 0.7, maxMatches 10, autoApprove false, autoApproveMinScore 0.7, llmConfidenceGate 0.8. Env может переопределить автоодобрение через MATCHER_AUTO_APPROVE / MATCHER_AUTO_APPROVE_MIN_SCORE (см. features/admin/platform-settings/matcher-config.ts). Ограничивайте число совпадений через MAX_MATCHES_PER_OPPORTUNITY.
Факторы оценки, пути LLM и эвристики и длина пояснений описаны на странице AI Matcher. Пороги выше это значения установки по умолчанию, а не опубликованный KPI точности.
Возможности превращают ваш Ring в локальный маркетплейс намерений: люди публикуют, что могут предоставить и что им нужно. Matcher отправляет уместные приглашения вместо того, чтобы полагаться на прокрутку ленты. Специализированные типы (вакансии, коллективные заказы, услуги по расписанию, программа/инвестиция → CRM, квесты кастомизации Ring) ограничиваются для каждого клона, поэтому вы запускаете только те циклы, которые нужны вашей вертикали.
Связывайте возможности с верифицированными организациями через contactInfo.linkedEntity / organizationId.
Приглашения по совпадению это настоящие уведомления, а не маркетинговые email-рассылки.
Contact открывает . Преобразования задач из чата сохраняют для владельца глубокую ссылку .
Используйте ring-ai-matcher-specialist (или навык Cursor из того же семейства) с промптом, ожидающим одобрения:
«Проверь цикл Opportunities этого клона: выведи
opportunities.enabledTypesиmatcherизring-config.json, подтверди, что env для LLM задан, сделай пробное создание одногоofferи одногоrequest, сообщи MatchFactors и перевёл быautoApprovepending→active. Открой/opportunitiesкак два пользователя и подтверди, что карточки показывают имена (а не UUID), Hide исключает после перезагрузки, а Contact ведёт на/messages?user=. Не меняй platform_settings, пока я не одобрю.»
Чеклист входных данных: ключ клона, доступ администратора, имеющиеся учётные данные LLM, тестовый id организации для organizationId.
Задайте opportunities.enabledTypes и matcher.* в ring-config.json; передеплойте или перезагрузите снимок конфига так, как ваш клон делает это для ring-config.
В Admin → Matcher / AI settings оставьте автоодобрение выключенным; задайте scoreThreshold / maxMatches под толерантность вашей вертикали.
Создайте тестовую возможность через UI выбора типа (или POST /api/opportunities); откройте My Opportunities на /opportunities/my.
Убедитесь, что пользователь с совпадением получил уведомление и что списки поиска обновляются (Tunnel / мягкое обновление) без выдуманного шага переиндексации. Убедитесь, что у списка просмотра нет строки статуса websocket.
Нет. По умолчанию matcher.autoApprove равен false. Когда включено, maybeAutoApproveOpportunity всё равно требует порогов оценки и уверенности LLM (autoApproveMinScore, llmConfidenceGate).
Нет. Считайте исторические маркетинговые проценты устаревшими. Используйте аналитику Admin Matcher для своего тенанта.
Нет. Аутентифицированные зрители видят имя и аватар. Username добавляется только когда publicProfile равен true. Флага postAnonymously нет.
Только когда DB_BACKEND_MODE=firebase-full. Клоны PostgreSQL используют opportunity-db-mapper.ts и SerializedOpportunity.
/opportunities/my-opportunities?Удалён. Каноническая панель это /opportunities/my через ROUTES.MY_OPPORTUNITIES.
sourceMessageId:?Это было машинное происхождение, а не теги пользователя. Новые преобразования записывают metadata.source; карточка скрывает устаревшие закодированные теги и показывает владельцу From chat, когда есть id разговора.
Проверьте доступность LLM, порог оценки Matcher, maxMatches и фильтры блокировки организаций в matcher-notification-filter. Уведомления идут через features/notifications/services/notification-service.
Кэш курсорной ленты имеет ключ для каждого зрителя. Hide хранится в user_content_interactions для этого пользователя; следующий getOpportunitiesForRole с этим viewerUserId применяет not-in.
Специализации возможностей могут содержать JSONB metadata (напр. слоты коллективного заказа). Копилки общественного финансирования и SSOT выплат строителям описаны в Public Pools; не придумывайте API «донат-продуктов» PSP как путь для копилок.
Следующий шаг: контракт REST-маршрутов, поля обогащения списка и пагинация поиска против просмотра.
Зависит от: как syncOpportunityDiscovery инвалидирует кэш и публикует события Tunnel.
Тот же процесс: convertTaskToOpportunity записывает metadata.source для чипа From chat.
Тот же процесс: Contact использует /messages?user=; From chat использует /messages?c=.
/messages?user=<createdBy>/opportunities)Посетители и участники видят компактные карточки: чип типа, заголовок, описание в две строки, подпись автора, относительное время публикации, человеческие теги, число лайков и иконки действий.
| Действие | Кто | Что происходит |
|---|---|---|
| Like | Вошедший зритель | Переключает коллекцию likes; сердце заполняется из opportunity.viewer.liked |
| Save | Вошедший зритель | Переключает user_content_interactions (save); состояние берётся из viewer.saved |
| Hide | Вошедший зритель | Отмечает not_interested; в сессии Hidden · Undo; следующий запрос списка исключает id |
| Contact | Subscriber и выше; скрыто на своих постах | Сразу переходит на /messages?user=<createdBy>; Matcher contact_intent работает по возможности и не должен блокировать переход |
| From chat | Только владелец, когда объявление пришло из задачи | Открывает /messages?c=<conversationId> |
Флага анонимной публикации нет. Аутентифицированные зрители видят имя и аватар автора. Username показывается только на публичных профилях. Пустое имя отображается как Member или Private User.
Query-параметры правой панели (q, types, categories, location, priority) теперь применяются на сервере: если присутствует любой из них, GET /api/opportunities запускает сервис поиска вместо простого списка, поэтому фильтры охватывают весь набор, а не только загруженную страницу.
Гости больше не упираются в стену входа на /opportunities, /opportunities/<id>, /entities и /entities/<id>. Они видят только публичные неконфиденциальные объявления. Like, Save, Hide и Contact по-прежнему требуют учётной записи, а открытие Add отправляет гостя на вход с callbackUrl, содержащим выбранный ?type=, поэтому после входа он возвращается к той же форме.
My Opportunities на /opportunities/my это панель владельца (Edit / Delete / View). Счётчики вкладок Saved и Applied сейчас жёстко заданы как 0, не считайте их живыми метриками.
job, offer, request, partnership; держите автоодобрение Matcher выключенным, пока не доверяете оценке.collective_order, scheduled_services, asset_rental, bounty, tender с рейками Wallet / PaymentConductor.ring_customization и program, чтобы квесты кастомизации и программы институций попадали в CRM.opportunities.opportunities.enabledTypes в ring-config.json (не предполагайте, что каждый член union OpportunityType виден в UI).LLM_PROVIDER, OPENAI_API_KEY или ANTHROPIC_API_KEY, LLM_MODEL), прежде чем ожидать автозаполнение / пояснения LLM./opportunities/my (ROUTES.MY_OPPORTUNITIES); устаревший /opportunities/my-opportunities удалён.| Like / save / hide зрителя | get-viewer-interactions.ts ← likes + user_content_interactions |
| Теги / происхождение из чата | lib/opportunity-tags.ts (splitOpportunityTags) |
| Карточка / список ленты | opportunity-feed-card.tsx, opportunity-list.tsx |
| Взаимодействия | app/_actions/opportunity-interactions.ts |
| Ядро Matcher | lib/ai/matcher.ts + OpportunityMatchingService |
| Автозаполнение | features/opportunities/services/auto-fill-service.ts |
| Автоодобрение | features/opportunities/services/auto-approval-service.ts |
| PG JSONB ↔ клиент | features/opportunities/lib/opportunity-db-mapper.ts |
| Синхронизация после изменений | features/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery |
| Программа → CRM | features/opportunities/lib/program-crm-ingest.ts |
| UI Server Actions | app/_actions/opportunities.ts |
| Устаревший Firestore | lib/converters/opportunity-converter.ts только когда DB_BACKEND_MODE=firebase-full |
База данных: типичные рабочие клоны используют DB_BACKEND_MODE=k8s-postgres-fcm (также supabase-fcm). Сервисы возвращают SerializedOpportunity (даты как строки ISO). Не придумывайте DATABASE_MODE=firebase_only.
(protected)/opportunities/page.tsx и GET /api/opportunities передают viewerUserId: session.user.id в getOpportunitiesForRole. Сначала загружаются скрытые id, затем запрос использует оператор DatabaseService not-in для opportunities.id. PostgreSQLAdapter отображает id в opportunities / likes на SQL-колонку (а не data->>'id').
attachOpportunityFeedFields добавляет creator, viewer и likes. Карточки инициализируют useOptimistic из opportunity.viewer (прежнее оптимистичное состояние с постоянной базой никогда не показывало saved/liked). После Like / Save / Hide onInteractionChange обновляет элемент курсорной ленты, чтобы флаги сессии оставались согласованными.
useCursorFeed хранится 24 ч в localStorage. Отпечаток списка это …|viewer=<userId>, поэтому одна учётная запись не наследует like/save/hide другой. Перезагрузки накладывают серверные initialItems на кэшированные строки по id (hooks/use-cursor-feed.ts).
Просмотр сохраняет useRealtimeOpportunities({ autoConnect: true }) для тихой подписки на туннель. Фрагменты создания содержат creator { id, name, avatar }, поэтому вставленные вживую карточки не показываются как «Private User». Страница деталей (opportunity-details.tsx) всё ещё показывает строку Live Updates.
convertTaskToOpportunity записывает metadata.source: { kind: 'chat_task', messageId, conversationId } и tags: []. splitOpportunityTags по-прежнему разбирает устаревшие теги sourceMessageId: / sourceConversationId: / chat_task при рендере. Задачи заполнения исторических данных нет.
public | subscriber | member | confidential | entityentity: лид, адресованный одной организации. Могут читать администраторы платформы, автор и участники связанного organizationId (loadViewerEntityAccessIds в features/entities/lib/entity-roles.ts). Subscribers могут её устанавливать. Публичные списки её никогда не содержат.052_opportunity_visibility_backfill.sql устанавливает отсутствующий visibility в public и отсутствующий isConfidential в false, потому что фильтры списка теперь требуют оба. Примените её один раз на базах, созданных до неё (она поставляется с .down.sql).draft | pending | active | closed | expired | archivedcanCreateOpportunityConfidential / canEditOpportunity)| Группа | Типы (примеры) | Минимальная роль |
|---|---|---|
| Предложения участников | offer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rental | привилегии member |
| Запросы | request | subscriber и выше |
| Специальные для subscriber | cv, scheduled_services, bounty | subscriber и выше |
| Другие члены union | напр. будущие типы, которых нет в наборах включённых/прав | admin и выше (и должны быть включены) |
lib/auth/layout-guards/session-auth-guard.tsx пропускает анонимные запросы ровно для /entities, /opportunities и их путей деталей /<id> (не add или my). Каждый другой защищённый маршрут перенаправляет на вход с callbackUrl, содержащим исходный путь и query.
| Поверхность | Поведение для гостя |
|---|---|
Страница /opportunities и GET /api/opportunities | Роль visitor; результаты отфильтрованы до visibility public (или не задано) и isConfidential !== true |
Страница /opportunities/<id> | Читает строку напрямую; конфиденциальные или непубличные строки перенаправляют на вход |
GET /api/opportunities/<id>, маршруты поиска | По-прежнему возвращают 401 без сессии |
features/opportunities/lib/opportunity-type-policy.ts это пустая заглушка Layer 1. Пакет Layer 2 перезаписывает её, чтобы объявить дополнительные типы с minRole, requiresEntity, видимостью и статусом по умолчанию и хуком buildMetadata. Её читают canCreateOpportunityType и страница добавления.POST /api/opportunities/<id>/sign записывает одну рекомендательную подпись на каждого вошедшего пользователя в metadata.signedUserIds, signatureCount и signatureTarget (по умолчанию 250). Layer 1 поставляет маршрут; UI подписания приходит из пакета.Контракты маршрутов, тело создания и JSON списка описаны на странице Opportunities API. Like / Save / Hide / Contact это Server Actions (app/_actions/opportunity-interactions.ts), а не REST. MCP GET /api/mcp/v1/opportunities сейчас не передаёт viewerUserId.
После изменений syncOpportunityDiscovery инвалидирует теги кэша, выполняет revalidatePath для списка/деталей/my и публикует Tunnel opportunity:*. Это не сбрасывает клиентский useCursorFeed: наложение + отпечаток зрителя обеспечивают актуальность просмотра.
Ключи, описанные в env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Без рабочего провайдера Matcher откатывается к пояснениям без LLM / на основе тегов с меньшей уверенностью.
Тот же процесс: привязка к организациям и верифицированные авторы возможностей.
/messages?user=<createdBy>/opportunities)Посетители и участники видят компактные карточки: чип типа, заголовок, описание в две строки, подпись автора, относительное время публикации, человеческие теги, число лайков и иконки действий.
| Действие | Кто | Что происходит |
|---|---|---|
| Like | Вошедший зритель | Переключает коллекцию likes; сердце заполняется из opportunity.viewer.liked |
| Save | Вошедший зритель | Переключает user_content_interactions (save); состояние берётся из viewer.saved |
| Hide | Вошедший зритель | Отмечает not_interested; в сессии Hidden · Undo; следующий запрос списка исключает id |
| Contact | Subscriber и выше; скрыто на своих постах | Сразу переходит на /messages?user=<createdBy>; Matcher contact_intent работает по возможности и не должен блокировать переход |
| From chat | Только владелец, когда объявление пришло из задачи | Открывает /messages?c=<conversationId> |
Флага анонимной публикации нет. Аутентифицированные зрители видят имя и аватар автора. Username показывается только на публичных профилях. Пустое имя отображается как Member или Private User.
Query-параметры правой панели (q, types, categories, location, priority) теперь применяются на сервере: если присутствует любой из них, GET /api/opportunities запускает сервис поиска вместо простого списка, поэтому фильтры охватывают весь набор, а не только загруженную страницу.
Гости больше не упираются в стену входа на /opportunities, /opportunities/<id>, /entities и /entities/<id>. Они видят только публичные неконфиденциальные объявления. Like, Save, Hide и Contact по-прежнему требуют учётной записи, а открытие Add отправляет гостя на вход с callbackUrl, содержащим выбранный ?type=, поэтому после входа он возвращается к той же форме.
My Opportunities на /opportunities/my это панель владельца (Edit / Delete / View). Счётчики вкладок Saved и Applied сейчас жёстко заданы как 0, не считайте их живыми метриками.
job, offer, request, partnership; держите автоодобрение Matcher выключенным, пока не доверяете оценке.collective_order, scheduled_services, asset_rental, bounty, tender с рейками Wallet / PaymentConductor.ring_customization и program, чтобы квесты кастомизации и программы институций попадали в CRM.opportunities.opportunities.enabledTypes в ring-config.json (не предполагайте, что каждый член union OpportunityType виден в UI).LLM_PROVIDER, OPENAI_API_KEY или ANTHROPIC_API_KEY, LLM_MODEL), прежде чем ожидать автозаполнение / пояснения LLM./opportunities/my (ROUTES.MY_OPPORTUNITIES); устаревший /opportunities/my-opportunities удалён.| Like / save / hide зрителя | get-viewer-interactions.ts ← likes + user_content_interactions |
| Теги / происхождение из чата | lib/opportunity-tags.ts (splitOpportunityTags) |
| Карточка / список ленты | opportunity-feed-card.tsx, opportunity-list.tsx |
| Взаимодействия | app/_actions/opportunity-interactions.ts |
| Ядро Matcher | lib/ai/matcher.ts + OpportunityMatchingService |
| Автозаполнение | features/opportunities/services/auto-fill-service.ts |
| Автоодобрение | features/opportunities/services/auto-approval-service.ts |
| PG JSONB ↔ клиент | features/opportunities/lib/opportunity-db-mapper.ts |
| Синхронизация после изменений | features/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery |
| Программа → CRM | features/opportunities/lib/program-crm-ingest.ts |
| UI Server Actions | app/_actions/opportunities.ts |
| Устаревший Firestore | lib/converters/opportunity-converter.ts только когда DB_BACKEND_MODE=firebase-full |
База данных: типичные рабочие клоны используют DB_BACKEND_MODE=k8s-postgres-fcm (также supabase-fcm). Сервисы возвращают SerializedOpportunity (даты как строки ISO). Не придумывайте DATABASE_MODE=firebase_only.
(protected)/opportunities/page.tsx и GET /api/opportunities передают viewerUserId: session.user.id в getOpportunitiesForRole. Сначала загружаются скрытые id, затем запрос использует оператор DatabaseService not-in для opportunities.id. PostgreSQLAdapter отображает id в opportunities / likes на SQL-колонку (а не data->>'id').
attachOpportunityFeedFields добавляет creator, viewer и likes. Карточки инициализируют useOptimistic из opportunity.viewer (прежнее оптимистичное состояние с постоянной базой никогда не показывало saved/liked). После Like / Save / Hide onInteractionChange обновляет элемент курсорной ленты, чтобы флаги сессии оставались согласованными.
useCursorFeed хранится 24 ч в localStorage. Отпечаток списка это …|viewer=<userId>, поэтому одна учётная запись не наследует like/save/hide другой. Перезагрузки накладывают серверные initialItems на кэшированные строки по id (hooks/use-cursor-feed.ts).
Просмотр сохраняет useRealtimeOpportunities({ autoConnect: true }) для тихой подписки на туннель. Фрагменты создания содержат creator { id, name, avatar }, поэтому вставленные вживую карточки не показываются как «Private User». Страница деталей (opportunity-details.tsx) всё ещё показывает строку Live Updates.
convertTaskToOpportunity записывает metadata.source: { kind: 'chat_task', messageId, conversationId } и tags: []. splitOpportunityTags по-прежнему разбирает устаревшие теги sourceMessageId: / sourceConversationId: / chat_task при рендере. Задачи заполнения исторических данных нет.
public | subscriber | member | confidential | entityentity: лид, адресованный одной организации. Могут читать администраторы платформы, автор и участники связанного organizationId (loadViewerEntityAccessIds в features/entities/lib/entity-roles.ts). Subscribers могут её устанавливать. Публичные списки её никогда не содержат.052_opportunity_visibility_backfill.sql устанавливает отсутствующий visibility в public и отсутствующий isConfidential в false, потому что фильтры списка теперь требуют оба. Примените её один раз на базах, созданных до неё (она поставляется с .down.sql).draft | pending | active | closed | expired | archivedcanCreateOpportunityConfidential / canEditOpportunity)| Группа | Типы (примеры) | Минимальная роль |
|---|---|---|
| Предложения участников | offer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rental | привилегии member |
| Запросы | request | subscriber и выше |
| Специальные для subscriber | cv, scheduled_services, bounty | subscriber и выше |
| Другие члены union | напр. будущие типы, которых нет в наборах включённых/прав | admin и выше (и должны быть включены) |
lib/auth/layout-guards/session-auth-guard.tsx пропускает анонимные запросы ровно для /entities, /opportunities и их путей деталей /<id> (не add или my). Каждый другой защищённый маршрут перенаправляет на вход с callbackUrl, содержащим исходный путь и query.
| Поверхность | Поведение для гостя |
|---|---|
Страница /opportunities и GET /api/opportunities | Роль visitor; результаты отфильтрованы до visibility public (или не задано) и isConfidential !== true |
Страница /opportunities/<id> | Читает строку напрямую; конфиденциальные или непубличные строки перенаправляют на вход |
GET /api/opportunities/<id>, маршруты поиска | По-прежнему возвращают 401 без сессии |
features/opportunities/lib/opportunity-type-policy.ts это пустая заглушка Layer 1. Пакет Layer 2 перезаписывает её, чтобы объявить дополнительные типы с minRole, requiresEntity, видимостью и статусом по умолчанию и хуком buildMetadata. Её читают canCreateOpportunityType и страница добавления.POST /api/opportunities/<id>/sign записывает одну рекомендательную подпись на каждого вошедшего пользователя в metadata.signedUserIds, signatureCount и signatureTarget (по умолчанию 250). Layer 1 поставляет маршрут; UI подписания приходит из пакета.Контракты маршрутов, тело создания и JSON списка описаны на странице Opportunities API. Like / Save / Hide / Contact это Server Actions (app/_actions/opportunity-interactions.ts), а не REST. MCP GET /api/mcp/v1/opportunities сейчас не передаёт viewerUserId.
После изменений syncOpportunityDiscovery инвалидирует теги кэша, выполняет revalidatePath для списка/деталей/my и публикует Tunnel opportunity:*. Это не сбрасывает клиентский useCursorFeed: наложение + отпечаток зрителя обеспечивают актуальность просмотра.
Ключи, описанные в env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Без рабочего провайдера Matcher откатывается к пояснениям без LLM / на основе тегов с меньшей уверенностью.
Тот же процесс: привязка к организациям и верифицированные авторы возможностей.
/messages?user=<createdBy>/opportunities)Посетители и участники видят компактные карточки: чип типа, заголовок, описание в две строки, подпись автора, относительное время публикации, человеческие теги, число лайков и иконки действий.
| Действие | Кто | Что происходит |
|---|---|---|
| Like | Вошедший зритель | Переключает коллекцию likes; сердце заполняется из opportunity.viewer.liked |
| Save | Вошедший зритель | Переключает user_content_interactions (save); состояние берётся из viewer.saved |
| Hide | Вошедший зритель | Отмечает not_interested; в сессии Hidden · Undo; следующий запрос списка исключает id |
| Contact | Subscriber и выше; скрыто на своих постах | Сразу переходит на /messages?user=<createdBy>; Matcher contact_intent работает по возможности и не должен блокировать переход |
| From chat | Только владелец, когда объявление пришло из задачи | Открывает /messages?c=<conversationId> |
Флага анонимной публикации нет. Аутентифицированные зрители видят имя и аватар автора. Username показывается только на публичных профилях. Пустое имя отображается как Member или Private User.
Query-параметры правой панели (q, types, categories, location, priority) теперь применяются на сервере: если присутствует любой из них, GET /api/opportunities запускает сервис поиска вместо простого списка, поэтому фильтры охватывают весь набор, а не только загруженную страницу.
Гости больше не упираются в стену входа на /opportunities, /opportunities/<id>, /entities и /entities/<id>. Они видят только публичные неконфиденциальные объявления. Like, Save, Hide и Contact по-прежнему требуют учётной записи, а открытие Add отправляет гостя на вход с callbackUrl, содержащим выбранный ?type=, поэтому после входа он возвращается к той же форме.
My Opportunities на /opportunities/my это панель владельца (Edit / Delete / View). Счётчики вкладок Saved и Applied сейчас жёстко заданы как 0, не считайте их живыми метриками.
job, offer, request, partnership; держите автоодобрение Matcher выключенным, пока не доверяете оценке.collective_order, scheduled_services, asset_rental, bounty, tender с рейками Wallet / PaymentConductor.ring_customization и program, чтобы квесты кастомизации и программы институций попадали в CRM.opportunities.opportunities.enabledTypes в ring-config.json (не предполагайте, что каждый член union OpportunityType виден в UI).LLM_PROVIDER, OPENAI_API_KEY или ANTHROPIC_API_KEY, LLM_MODEL), прежде чем ожидать автозаполнение / пояснения LLM./opportunities/my (ROUTES.MY_OPPORTUNITIES); устаревший /opportunities/my-opportunities удалён.| Like / save / hide зрителя | get-viewer-interactions.ts ← likes + user_content_interactions |
| Теги / происхождение из чата | lib/opportunity-tags.ts (splitOpportunityTags) |
| Карточка / список ленты | opportunity-feed-card.tsx, opportunity-list.tsx |
| Взаимодействия | app/_actions/opportunity-interactions.ts |
| Ядро Matcher | lib/ai/matcher.ts + OpportunityMatchingService |
| Автозаполнение | features/opportunities/services/auto-fill-service.ts |
| Автоодобрение | features/opportunities/services/auto-approval-service.ts |
| PG JSONB ↔ клиент | features/opportunities/lib/opportunity-db-mapper.ts |
| Синхронизация после изменений | features/opportunities/lib/opportunity-mutation-sync.ts → syncOpportunityDiscovery |
| Программа → CRM | features/opportunities/lib/program-crm-ingest.ts |
| UI Server Actions | app/_actions/opportunities.ts |
| Устаревший Firestore | lib/converters/opportunity-converter.ts только когда DB_BACKEND_MODE=firebase-full |
База данных: типичные рабочие клоны используют DB_BACKEND_MODE=k8s-postgres-fcm (также supabase-fcm). Сервисы возвращают SerializedOpportunity (даты как строки ISO). Не придумывайте DATABASE_MODE=firebase_only.
(protected)/opportunities/page.tsx и GET /api/opportunities передают viewerUserId: session.user.id в getOpportunitiesForRole. Сначала загружаются скрытые id, затем запрос использует оператор DatabaseService not-in для opportunities.id. PostgreSQLAdapter отображает id в opportunities / likes на SQL-колонку (а не data->>'id').
attachOpportunityFeedFields добавляет creator, viewer и likes. Карточки инициализируют useOptimistic из opportunity.viewer (прежнее оптимистичное состояние с постоянной базой никогда не показывало saved/liked). После Like / Save / Hide onInteractionChange обновляет элемент курсорной ленты, чтобы флаги сессии оставались согласованными.
useCursorFeed хранится 24 ч в localStorage. Отпечаток списка это …|viewer=<userId>, поэтому одна учётная запись не наследует like/save/hide другой. Перезагрузки накладывают серверные initialItems на кэшированные строки по id (hooks/use-cursor-feed.ts).
Просмотр сохраняет useRealtimeOpportunities({ autoConnect: true }) для тихой подписки на туннель. Фрагменты создания содержат creator { id, name, avatar }, поэтому вставленные вживую карточки не показываются как «Private User». Страница деталей (opportunity-details.tsx) всё ещё показывает строку Live Updates.
convertTaskToOpportunity записывает metadata.source: { kind: 'chat_task', messageId, conversationId } и tags: []. splitOpportunityTags по-прежнему разбирает устаревшие теги sourceMessageId: / sourceConversationId: / chat_task при рендере. Задачи заполнения исторических данных нет.
public | subscriber | member | confidential | entityentity: лид, адресованный одной организации. Могут читать администраторы платформы, автор и участники связанного organizationId (loadViewerEntityAccessIds в features/entities/lib/entity-roles.ts). Subscribers могут её устанавливать. Публичные списки её никогда не содержат.052_opportunity_visibility_backfill.sql устанавливает отсутствующий visibility в public и отсутствующий isConfidential в false, потому что фильтры списка теперь требуют оба. Примените её один раз на базах, созданных до неё (она поставляется с .down.sql).draft | pending | active | closed | expired | archivedcanCreateOpportunityConfidential / canEditOpportunity)| Группа | Типы (примеры) | Минимальная роль |
|---|---|---|
| Предложения участников | offer, job, partnership, volunteer, mentorship, resource, event, ring_customization, program, collective_order, tender, asset_rental | привилегии member |
| Запросы | request | subscriber и выше |
| Специальные для subscriber | cv, scheduled_services, bounty | subscriber и выше |
| Другие члены union | напр. будущие типы, которых нет в наборах включённых/прав | admin и выше (и должны быть включены) |
lib/auth/layout-guards/session-auth-guard.tsx пропускает анонимные запросы ровно для /entities, /opportunities и их путей деталей /<id> (не add или my). Каждый другой защищённый маршрут перенаправляет на вход с callbackUrl, содержащим исходный путь и query.
| Поверхность | Поведение для гостя |
|---|---|
Страница /opportunities и GET /api/opportunities | Роль visitor; результаты отфильтрованы до visibility public (или не задано) и isConfidential !== true |
Страница /opportunities/<id> | Читает строку напрямую; конфиденциальные или непубличные строки перенаправляют на вход |
GET /api/opportunities/<id>, маршруты поиска | По-прежнему возвращают 401 без сессии |
features/opportunities/lib/opportunity-type-policy.ts это пустая заглушка Layer 1. Пакет Layer 2 перезаписывает её, чтобы объявить дополнительные типы с minRole, requiresEntity, видимостью и статусом по умолчанию и хуком buildMetadata. Её читают canCreateOpportunityType и страница добавления.POST /api/opportunities/<id>/sign записывает одну рекомендательную подпись на каждого вошедшего пользователя в metadata.signedUserIds, signatureCount и signatureTarget (по умолчанию 250). Layer 1 поставляет маршрут; UI подписания приходит из пакета.Контракты маршрутов, тело создания и JSON списка описаны на странице Opportunities API. Like / Save / Hide / Contact это Server Actions (app/_actions/opportunity-interactions.ts), а не REST. MCP GET /api/mcp/v1/opportunities сейчас не передаёт viewerUserId.
После изменений syncOpportunityDiscovery инвалидирует теги кэша, выполняет revalidatePath для списка/деталей/my и публикует Tunnel opportunity:*. Это не сбрасывает клиентский useCursorFeed: наложение + отпечаток зрителя обеспечивают актуальность просмотра.
Ключи, описанные в env.local.template: LLM_PROVIDER, OPENAI_API_KEY, ANTHROPIC_API_KEY, LLM_MODEL, MAX_MATCHES_PER_OPPORTUNITY, MATCHING_MAX_TOKENS. Без рабочего провайдера Matcher откатывается к пояснениям без LLM / на основе тегов с меньшей уверенностью.
Тот же процесс: привязка к организациям и верифицированные авторы возможностей.