From ced708851dc0e1f5e4ddae36daf9347fa4c0ffaf Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Mon, 30 Mar 2026 01:03:36 +0300 Subject: [PATCH] Document error visibility model Mark rc.13 priority-one backlog items done Align backlog and error docs in EN and RU --- Error-Handling-RU.md | 27 ++++++++++++++ Error-Handling.md | 26 ++++++++++++++ Framework-Backlog-RU.md | 78 +++++++++++++++++++++++++++++++++++++---- Framework-Backlog.md | 78 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 195 insertions(+), 14 deletions(-) diff --git a/Error-Handling-RU.md b/Error-Handling-RU.md index aae24fd..3ee296e 100644 --- a/Error-Handling-RU.md +++ b/Error-Handling-RU.md @@ -18,6 +18,10 @@ English version: [[Error-Handling]] - отправляет этот текст пользователю; - логирует исходную ошибку через текущий logger. +По умолчанию возвращённые ошибки остаются пользовательскими ради обратной совместимости. Но Laniakea теперь даёт и явную маркировку: +- `AsUserError(err)` явно оставляет ошибку на пользовательском пути; +- `AsInternalError(err)` логирует ошибку, но подавляет автоматический ответ пользователю. + ## Как это выглядит ```go @@ -42,6 +46,12 @@ if err != nil { - ошибка действительно exceptional; - хочешь централизованное логирование и formatting. +Если хочешь сделать намерение явным, можно вернуть: + +```go +return laniakea.AsUserError(errors.New("access denied")) +``` + ## Когда лучше ответить вручную Лучше ответить вручную и вернуть `nil`, когда: @@ -58,6 +68,21 @@ if !allowed { } ``` +## Когда возвращать internal-only ошибку + +Возвращай `AsInternalError(...)`, когда: +- ошибку нужно видеть в логах, но не нужно показывать пользователю; +- это внутренний сбой инфраструктуры или инварианта; +- сырой текст ошибки даст плохой или шумный UX. + +Пример: + +```go +if err := db.WarmCache(); err != nil { + return laniakea.AsInternalError(fmt.Errorf("warm cache: %w", err)) +} +``` + ## Callback-specific поведение Для callback-потока возвращённая ошибка превращается не в обычное сообщение в чат, а в ответ на callback query. @@ -84,6 +109,8 @@ Middleware не возвращает `error`, он возвращает `bool`. - он сам показывает ответ; - возвращает `false`. +Internal-only ошибки идут по тому же logger path, но не создают автоматический ответ пользователю. + ## Что читать дальше - [[FAQ-RU]] diff --git a/Error-Handling.md b/Error-Handling.md index 52b0d47..736b066 100644 --- a/Error-Handling.md +++ b/Error-Handling.md @@ -24,6 +24,10 @@ func ping(ctx *laniakea.MsgContext, db *App) error { When a handler returns an error, the bot routes it through the context error helper instead of ignoring it. +By default, returned handler errors remain user-visible for backward compatibility. Laniakea also provides explicit markers when you want to distinguish between user-facing and internal-only failures: +- `AsUserError(err)` keeps the error on the user-facing path explicitly; +- `AsInternalError(err)` logs the error but suppresses the automatic user reply. + ## What happens when an error is returned The context error flow does three things: @@ -66,6 +70,12 @@ if !allowed { } ``` +If you want to make the intent explicit, you can return: + +```go +return laniakea.AsUserError(errors.New("access denied")) +``` + ### Reply manually and return `nil` Use this when: @@ -80,6 +90,19 @@ if !allowed { } ``` +### Return an internal-only error + +Use this when: +- the failure should be logged for operators, not shown to the user; +- the error indicates an internal invariant problem or infrastructure issue; +- exposing the raw error text would be noisy or misleading UX. + +```go +if err := db.WarmCache(); err != nil { + return laniakea.AsInternalError(fmt.Errorf("warm cache: %w", err)) +} +``` + ## Good places to return errors Returning an error is a good fit for: @@ -150,6 +173,8 @@ Depending on the route, that may be: This is one reason returning errors is useful: it keeps operational visibility aligned with the same handler path that produced the user-facing failure. +Internal-only errors follow the same logging path, but they do not produce the automatic user reply. + Related page: - [[Logging]] @@ -195,6 +220,7 @@ plugin.NewPayload(func(ctx *laniakea.MsgContext, db *App) error { ## Recommendations - Return `error` when you want centralized error formatting and logging. +- Wrap with `AsInternalError(...)` when the centralized path should log but stay silent for the user. - Reply manually and return `nil` when the response is part of normal control flow. - Keep user-facing error strings clear and action-oriented. - Use one consistent style across a plugin when possible. diff --git a/Framework-Backlog-RU.md b/Framework-Backlog-RU.md index 12cf3c8..ec3b8ee 100644 --- a/Framework-Backlog-RU.md +++ b/Framework-Backlog-RU.md @@ -4,23 +4,87 @@ ## Приоритет 1 — Срочно -- Контракт схемы обновлений: обработка обновлений уже есть, но нет формального понятия уровня фреймворка, описывающего, какие поля `MsgContext` гарантированы для каких видов обновлений. -- Модель пользовательских и внутренних ошибок: у фреймворка есть единый поток обработки ошибок, но он ещё плохо различает пользовательские, внутренние, повторяемые и тихие ошибки. -- Модель фиксации конфигурации: у фреймворка уже есть реальные точки фиксации вроде `AddPlugins(...)`, но пока это скорее факт реализации, чем явная верхнеуровневая концепция. - -## Приоритет 2 — Важно - - Модель выполнения webhook: у библиотеки есть хорошая polling-модель, но нет полноценной модели выполнения webhook на уровне фреймворка. - Модель авторизации и политик: middleware могут реализовать аутентификацию и права доступа, но нет явной модели уровня фреймворка для политик доступа, ролей или проверок возможностей. - Модель наблюдаемости: логирование уже сильное, но метрики, трассировка и структурированные хуки фреймворка пока не являются полноценной частью API. -## Приоритет 3 — Стратегически +## Приоритет 2 — Важно - Модель сервисного слоя и графа зависимостей: `DatabaseContext(T)` намеренно минималистичен, но нет более сильной концепции уровня фреймворка для сервисов приложения или зависимостей с ограниченной областью действия. - Контракт композиции плагинов: плагины — хороший способ группировки, но нет явной модели зависимостей плагинов, общих возможностей или контрактов композиции. ## Done +### [1.0.0-rc.13] Модель пользовательских и внутренних ошибок + +Текущее состояние: + +- У фреймворка по-прежнему один централизованный путь ошибок из обработчиков, но теперь он поддерживает явное разделение на пользовательские и внутренние ошибки. +- Неразмеченные возвращённые ошибки остаются пользовательскими ради обратной совместимости. +- Внутренние сбои теперь можно логировать без автоматической отправки их сырого текста пользователю. + +Почему это важно: + +- Единый поток ошибок полезен, но не каждая ошибка должна превращаться в ответ пользователю. +- Сбои инфраструктуры, нарушения внутренних инвариантов и ошибки маршрутизации часто важны для операторов, но только засоряют или портят пользовательский UX. +- Без такой классификации пользователю либо начинают утекать внутренние ошибки, либо приходится полностью обходить централизованный путь. + +Что теперь есть: + +- `AsUserError(...)` и `AsInternalError(...)` для явной классификации возвращаемых ошибок. +- `IsUserError(...)` и `IsInternalError(...)` для проверки этой классификации на стороне фреймворка. +- Обновлённое поведение `MsgContext.Error(...)`: все ошибки по-прежнему логируются, но для внутренних ошибок автоматический ответ пользователю подавляется. +- Регрессионные тесты для message и callback потоков. + +Практическая цель: + +- Сохранить централизованный error flow, но сделать пользовательские и операторские ошибки действительно разными по поведению. + +### [1.0.0-rc.13] Модель фиксации конфигурации + +Текущее состояние: + +- Фреймворк теперь считает конфигурацию бота структурно завершённой после начала первого `Run()` или `RunWithContext(...)`. +- Поздние bot-level попытки мутации больше не применяются частично после старта runtime. +- Границы между регистрацией плагинов, стартом runtime и фиксацией конфигурации теперь оформлены как явное поведение фреймворка и закреплены тестами. + +Почему это важно: + +- Runtime-мутация prefixes, middleware, payload policy, локализации, runners или общих зависимостей слишком трудно поддаётся безопасному пониманию. +- У фреймворка уже были реальные точки фиксации вроде `AddPlugins(...)`; теперь они оформлены как явный контракт, а не как скрытая деталь реализации. + +Что теперь есть: + +- Bot-level конфигурационные методы игнорируют поздние вызовы после старта runtime. +- Правила жизненного цикла и freeze-модели задокументированы как полноценная концепция. +- Регрессионные тесты закрепляют игнорирование поздних мутаций. + +Практическая цель: + +- Сделать старт, runtime и владение конфигурацией достаточно предсказуемыми для стабильного контракта фреймворка. + +### [1.0.0-rc.13] Контракт схемы обновлений + +Текущее состояние: + +- Нормализация обновлений уже существовала, но теперь она описана и протестирована как явный контракт уровня фреймворка. +- Категории маршрутизации и гарантии заполнения `MsgContext` теперь рассматриваются как полноценная часть публичной модели. + +Почему это важно: + +- Код обработчиков должен понимать, на какие поля `MsgContext` можно безопасно опираться в каждом потоке обновлений. +- Без явного контракта обработка обновлений остаётся понятной только через чтение реализации. + +Что теперь есть: + +- Задокументированная модель маршрутизации для command flow, payload flow и generic update handlers. +- Явные комментарии на полях `MsgContext`, описывающие гарантии для update-backed, callback-backed и message-backed контекстов. +- Table-driven регрессионные тесты для нормализованного update contract, включая callback target semantics и non-command update flows. + +Практическая цель: + +- Сделать маршрутизацию обновлений и гарантии `MsgContext` достаточно явными, чтобы на них можно было опираться как на стабильный контракт `1.0`. + ### [1.0.0-rc.12] Conversation / Scene Model Текущее состояние: diff --git a/Framework-Backlog.md b/Framework-Backlog.md index a03c89d..2053c53 100644 --- a/Framework-Backlog.md +++ b/Framework-Backlog.md @@ -4,23 +4,87 @@ This page tracks framework-level backlog items that are about missing concepts i ## Priority 1 — Urgent -- Update schema contract: update handling exists, but there is no formal framework-level concept describing which `MsgContext` fields are guaranteed in which update kinds. -- User-facing vs internal error model: the framework has a unified error flow, but it does not yet distinguish well between user-visible, internal-only, retryable, or silent errors. -- Configuration freeze model: the framework already has real commit points like `AddPlugins(...)`, but this is still more of an implementation truth than an explicit top-level concept. - -## Priority 2 — Important - - Webhook runtime model: the library has a solid polling model, but no first-class webhook execution model at the framework level. - Authorization and policy model: middleware can implement auth and permissions, but there is no explicit framework concept for access policies, roles, or capability checks. - Observability model: logging is strong, but metrics, tracing, and structured framework hooks are still missing as first-class concepts. -## Priority 3 — Strategic +## Priority 2 — Important - Service layer and dependency graph model: `DatabaseContext(T)` is intentionally minimal, but there is no stronger framework concept for application services or scoped dependencies. - Plugin composition contract: plugins are a good grouping unit, but there is no explicit model for plugin dependencies, shared capabilities, or composition contracts. ## Done +### [1.0.0-rc.13] User-Facing vs Internal Error Model + +Current state: + +- The framework still keeps one centralized handler error path, but it now supports explicit classification between user-visible and internal-only errors. +- Unclassified returned errors remain user-visible for backward compatibility. +- Internal-only failures can now be logged without automatically sending their raw text back to the user. + +Why this matters: + +- A single unified error path is useful, but not every failure should become a user reply. +- Infrastructure faults, invariant violations, and internal routing failures are often important for operators while being noisy or misleading for users. +- Without classification, framework users either leak internal errors into chat UX or are forced to bypass the centralized path entirely. + +What is now present: + +- `AsUserError(...)` and `AsInternalError(...)` to classify returned handler errors explicitly. +- `IsUserError(...)` and `IsInternalError(...)` for framework-side inspection. +- Updated centralized `MsgContext.Error(...)` behavior that still logs all errors but suppresses the automatic user reply for internal-only failures. +- Regression coverage for both message and callback flows. + +Practical target: + +- Keep the centralized handler error path intact while making user-facing and operator-facing failure handling meaningfully distinct. + +### [1.0.0-rc.13] Configuration Freeze Model + +Current state: + +- The framework now treats bot configuration as structurally complete once the first `Run()` or `RunWithContext(...)` begins. +- Late bot-level mutation attempts no longer partially apply after runtime startup. +- Plugin registration and runtime configuration boundaries are now documented and tested as explicit framework behavior. + +Why this matters: + +- Runtime mutation of prefixes, middleware, payload policy, localization, runners, or shared dependencies is difficult to reason about safely. +- The framework already had real commit points like `AddPlugins(...)`; this work makes those boundaries explicit instead of leaving them as implementation details. + +What is now present: + +- Bot-level configuration mutators now ignore late calls after runtime start. +- Lifecycle and configuration-freeze rules are documented as a first-class concept. +- Regression tests now lock down ignored late mutations. + +Practical target: + +- Make startup, runtime, and configuration ownership predictable enough to rely on as a stable framework contract. + +### [1.0.0-rc.13] Update Schema Contract + +Current state: + +- Update normalization already existed, but it is now described and tested as an explicit framework-level contract. +- Routing categories and `MsgContext` population guarantees are now treated as a first-class part of the public model. + +Why this matters: + +- Handler code needs to know which `MsgContext` fields are safe to rely on for each update path. +- Without a formal contract, update handling remains understandable only by reading implementation details. + +What is now present: + +- A documented routing model for command flow, payload flow, and generic update handlers. +- Explicit `MsgContext` field comments for update-backed, callback-backed, and message-backed contexts. +- Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows. + +Practical target: + +- Make update routing and `MsgContext` guarantees explicit enough to serve as a stable `1.0` public contract. + ### [1.0.0-rc.12] Conversation / Scene Model Current state: