Files
fission-console/doc/progress.md
T

509 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Progress Log
## 2026-04-14
### План перед работой
- Зафиксировать, что REST `/v2/*` недоступен в текущем кластере Fission v1.22.1 и MVP идет через Kubernetes CRD API.
- Реализовать первый рабочий Terraform ресурс `fission_environment`.
- Добавить минимальный CRUD в клиент для CRD `environments.fission.io`.
- Проверить сборку `go build ./...`.
### Факт выполнения
- Обновлен план в `doc/CODEX_PLAN.md`: путь реализации переведен на CRD API.
- Создан каркас провайдера и заготовки ресурсов.
- Реализован рабочий `fission_environment`:
- schema (name, image, version, poolsize, namespace, id, uid)
- Configure() с получением клиента из provider
- Create/Read/Update/Delete через Kubernetes CRD `environments.fission.io`
- В `internal/client/client.go` добавлены методы:
- CreateEnvironment / GetEnvironment / UpdateEnvironment / DeleteEnvironment
- IsNotFound для обработки удаления/дрейфа state
- Проверка `go build ./...` проходит успешно.
### Следующий шаг
- Реализовать `fission_package` (zip/base64 -> spec.source.literal) через CRD `packages.fission.io`.
### Обновление этапа (fission_package)
- Реализован CRUD в `internal/client/client.go` для `packages.fission.io`:
- `CreatePackage`
- `GetPackage`
- `UpdatePackage`
- `DeletePackage`
- Реализован ресурс `fission_package` в `internal/resources/package_resource.go`:
- schema: `name`, `environment`, `source_dir`, `code_path`, `code_hash`, `build_command`, `namespace`, computed `id`, `uid`, `build_status`, `build_log`
- валидация источника кода: задан ровно один из `source_dir` или `code_path`
- упаковка `source_dir` в zip и кодирование base64 в `spec.source.literal`
- Create/Read/Update/Delete через Kubernetes CRD API
- Проверка сборки: `go build ./...` успешно.
### Следующий шаг
- Реализовать `fission_function` через CRD `functions.fission.io`.
### Обновление этапа (fission_function)
- Реализован CRUD в `internal/client/client.go` для `functions.fission.io`:
- `CreateFunction`
- `GetFunction`
- `UpdateFunction`
- `DeleteFunction`
- Реализован ресурс `fission_function` в `internal/resources/function_resource.go`:
- schema: `name`, `environment`, `package_name`, `entrypoint`, `namespace`, computed `id`, `uid`
- Configure/Create/Read/Update/Delete
- маппинг в CRD `Function` (`spec.environment`, `spec.package.packageref`, `spec.package.functionName`)
- Проверка сборки: `go build ./...` успешно.
### Следующий шаг
- Реализовать `fission_http_trigger` через CRD `httptriggers.fission.io`.
### Обновление этапа (fission_http_trigger)
- Реализован CRUD в `internal/client/client.go` для `httptriggers.fission.io`:
- `CreateHTTPTrigger`
- `GetHTTPTrigger`
- `UpdateHTTPTrigger`
- `DeleteHTTPTrigger`
- Реализован ресурс `fission_http_trigger` в `internal/resources/http_trigger_resource.go`:
- schema: `name`, `function`, `url`, `methods`, `create_ingress`, `host`, `namespace`, computed `id`, `uid`
- Configure/Create/Read/Update/Delete
- маппинг в CRD `HTTPTrigger` (`spec.relativeurl`, `spec.methods`, `spec.functionref`, `spec.createingress`)
- Проверка сборки: `go build ./...` успешно.
### Следующий шаг
- Добавить пример `examples/hello-python/main.tf` и выполнить smoke Terraform apply.
### Обновление этапа (пример Terraform)
- Создан пример `examples/hello-python/main.tf` с использованием ресурсов:
- `fission_environment`
- `fission_package`
- `fission_function`
- `fission_http_trigger`
- Добавлен код функции `examples/hello-python/code/main.py`.
### Следующий шаг
- Подготовить локальную установку бинарника провайдера для `terraform init` и выполнить smoke `terraform apply`.
### Обновление этапа (smoke Terraform apply)
- Собран бинарник провайдера и подключен через Terraform `dev_overrides`.
- Прогнан `terraform plan` для `examples/hello-python`: план успешен, 4 ресурса к созданию.
- Прогнан `terraform apply` и выполнена функциональная проверка.
Исправленные баги по факту smoke:
- `fission_package`: после Create computed-поля (`build_status`, `build_log`) могли оставаться `unknown`.
- Исправлено формирование state в `unstructuredToPackageModel`.
- `fission_function`: CRD требовал обязательный `spec.InvokeStrategy`.
- Добавлен `InvokeStrategy` в payload функции.
- `fission_http_trigger`: несогласованность state (`create_ingress` null -> false).
- В schema добавлены `Computed` + `Default(false)` для `create_ingress`.
- Для `methods` добавлен `Computed`.
Дополнительно:
- Для текущего python MVP `fission_package` переведен на `spec.deployment.literal` (как у рабочего CLI-пакета).
- Проверка endpoint после apply:
- `GET /tf-hello` -> `hello from fission via terraform`.
### Следующий шаг
- Добавить README по запуску dev override и оговорить ограничения текущего MVP (`source_dir` ожидает `main.py`).
### Обновление этапа (README)
- Добавлен `README.md` в корень репозитория.
- Описаны:
- список реализованных ресурсов MVP
- запуск через Terraform dev override
- текущие ограничения MVP
### Обновление этапа (import + тесты)
- Добавлена поддержка `terraform import` для всех 4 ресурсов:
- `fission_environment`
- `fission_package`
- `fission_function`
- `fission_http_trigger`
- Формат import ID: `namespace/name`.
- Добавлены unit-тесты в `terraform/provider/internal/resources/*_test.go`:
- парсинг import ID
- конвертация model <-> unstructured
- валидация package source
- базовая проверка HTTP trigger round-trip
- Прогон валидации:
- `go test ./...` — успешно
- smoke `terraform plan/apply` — успешно
- endpoint `GET /tf-hello``hello from fission via terraform`
### Следующий шаг
- Добавить acceptance-тесты с опциональным запуском по env-флагу.
### Обновление этапа (acceptance тест)
- Добавлен acceptance-тест `terraform/provider/internal/client/client_acceptance_test.go`.
- Тест выполняет живой CRUD в кластере для цепочки:
- Environment -> Package -> Function -> HTTPTrigger
- Запуск теста:
- `ACCEPTANCE_TEST=1 KUBECONFIG=/home/naeel/.kube/config go test ./internal/client -run TestAcceptanceFissionCRUD -v`
- Результат: PASS.
### Следующий шаг
- Расширить acceptance-тест проверкой реального HTTP-вызова функции через ingress.
### Обновление этапа (много функций + суровая нагрузка)
- Добавлен `examples/stress-suite`:
- функции: `fast`, `cpu`, `json`
- маршруты: `/stress/fast`, `/stress/cpu`, `/stress/json`
- Terraform-конфиг на 10 ресурсов (env + 3 package + 3 function + 3 trigger)
- Добавлен скрипт `examples/stress-suite/scripts/load_test.sh` для параллельной нагрузки.
- Реальный прогон после донастройки:
- `900` запросов на endpoint
- `90` параллелизм
- итог: `100%` success на всех трех endpoint-ах.
### Следующий шаг
- Добавить в acceptance-тест проверку HTTP latency/perf budget и порогов SLO.
### Обновление этапа (JS/Go source support)
- В `fission_package` расширена поддержка `source_dir`:
- `main.py`
- `main.js`
- `main.go`
- Добавлены unit-тесты на выбор JS/Go исходников в `package_resource_test.go`.
- Прогон `go test ./...` — успешно.
### Следующий шаг
- Добавить отдельные Terraform примеры для Node.js и Go функций.
---
## 2026-04-15
### Fission Console — Web UI для управления функциями
**Ветка:** `feat/console`
#### Реализовано (14 апреля, вечер → 15 апреля, утро)
**Backend (console/main.go):**
- Go HTTP-сервер с embedded UI
- CRUD API для Fission CRD через dynamic k8s client:
- `POST /api/functions` — создание (Package + Function + HTTPTrigger атомарно с rollback)
- `GET /api/functions` — список всех
- `GET /api/functions/{name}` — детали (с декодированным кодом, route, methods)
- `PUT /api/functions/{name}/code` — обновление кода
- `POST /api/functions/{name}/invoke` — вызов функции через роутер
- `DELETE /api/functions/{name}` — удаление (trigger + function + package)
- Двойная маршрутизация: `/api/*` и `/console/api/*` (для работы через ingress `/console/`)
- CORS middleware, request logging
**UI (console/ui/index.html):**
- SPA-дашборд с карточками (environments, packages, functions, httptriggers, timetriggers)
- Таблица функций с кнопками Edit/Invoke/Delete
- Модал Create: выбор environment, ввод кода, route, methods
- Модал Edit Code: редактирование и сохранение
- Модал Invoke: отправка запроса, отображение статуса/latency/response
- Модал Delete: подтверждение
- Динамический `API_BASE` для работы как с `/` так и с `/console/` prefix
**Тесты (console/main_test.go):**
- 5 unit-тестов:
- `TestNormalizeMethods` — нормализация HTTP-методов
- `TestCreateFunctionValidation` — валидация обязательных полей
- `TestCreateFunctionSuccessAndGetDetails` — полный CRUD flow + Get с декодированным кодом
- `TestUpdateFunctionCode` — обновление кода в package
- `TestInvokeFunctionWithJWTAuth` — invoke с mock-роутером, проверка JWT auth flow
- Используется `dynamicfake.NewSimpleDynamicClientWithCustomListKinds`
**Docker (console/Dockerfile):**
- Multi-stage: `golang:1.26-alpine``alpine:3.20`
- Бинарник ~15MB
**K8s deployment (console/deploy/console.yaml):**
- ServiceAccount `fission-console`
- ClusterRole с доступом к fission.io CRD (get/list/create/update/patch/delete)
- ClusterRoleBinding
- Deployment (1 replica, port 8090, health probes, resource limits)
- Service (ClusterIP:8090)
- Ingress (fission.kube5s.ru/console, nginx, TLS)
#### Invoke auth — проблема и решение
**Проблема:** invoke через console возвращал 401 от Fission router.
**Исследование:**
1. Попробовал SA token из пода → `key is of invalid type` (K8s SA = RS256, router ожидает HS256)
2. Прочитал feature-config: `auth.enabled: true`, endpoint `/auth/login`, JWT expiry 120s
3. Нашёл credentials в секрете `router`: username=admin, password=..., jwtSigningKey=...
4. Протестировал: `POST /auth/login` → получил JWT → invoke с Bearer JWT → 200 OK
**Решение:** console при invoke делает POST `/auth/login` к роутеру, получает JWT, кеширует на 100s.
**Баги по пути:**
- Router возвращает 201 Created (не 200) на login — код проверял строго 200 → добавил `|| 201`
- Кешированный Docker-образ: пересобрал тот же тег v0.2.0, нод использовал старый → перешёл на v0.2.2
**Env переменные для auth:**
- `FISSION_AUTH_USERNAME` → secretKeyRef из `router.username`
- `FISSION_AUTH_PASSWORD` → secretKeyRef из `router.password`
#### E2E результат (все через ingress https://fission.kube5s.ru/console/)
| Операция | Результат |
|----------|-----------|
| health | 200 ok |
| create | 201 Created (function + package + httptrigger) |
| get | 200 (code, route, methods, environment) |
| update code | 200 |
| invoke | **200**, response_raw = ответ функции, latency_ms = 138 |
| delete | 200 (trigger + function + package очищены) |
#### Коммиты
- `6731e89` — feat: deliver fission console UI, k8s deploy, and tests
- `8920ba1` — fix: invoke auth via Fission router JWT login
**Docker Hub:** `naeel/fission-console:v0.2.2`
#### Правила репозитория
Создан `.github/copilot-instructions.md` с правилами:
### Обновление этапа (UI invoke stability: JS)
- Проведена диагностика JS invoke через реальный UI-путь (`/console/api/functions/{name}/invoke`) и роутер/экзекьютор логи.
- Найдена корневая причина JS-таймаутов:
- для `node-env` используется `v2/specialize`
- при `functionName=main` runtime пытался загрузить `/userfunc/deployarchive/main`
- при plain literal `deployarchive` является файлом, не директорией
- после specialize без корректного контракта запросы зависали и упирались в router roundtripper timeout.
- Для `nodejs-acc` зафиксирован runtime image: `ghcr.io/fission/node-env:1.32.5`.
- Для JS-функций выровнен контракт runtime:
- `spec.package.functionName` выставлен в пустой entrypoint (`""`) для default export
- код приведен к формату ответа Node runtime: `return { status: 200, body: "..." }`
- для основных JS-маршрутов включены методы `GET` + `POST`.
- Подтверждена работоспособность через console invoke:
- `fn-js-acc` -> `status=200`, `response_raw=hello-js-ok`
- `fn-js-direct` -> `status=200`, `response_raw=hello-js-ok`
- тестовая матрица `jsm1..jsm4` -> `status=200`.
### Статус после фикса
- Python invoke: работает.
- JS invoke: работает по UI-пути и по прямому GET роутов.
- Go invoke (`fn-go-acc`): остается отдельной runtime-проблемой (вне JS-фикса).
- Маппинг путей (локально ~/remote_dev/ = ВМ ~/terra/)
- Редактирование файлов — разрешено локально
- Команды — ИСКЛЮЧИТЕЛЬНО через SSH (VPN-конфликты)
### Следующий шаг
- UI: автоматическое обновление дашборда после create/delete
- Рассмотреть добавление логов функций (kubectl logs)
- Рассмотреть добавление time triggers в UI
---
## 2026-04-15 (дополнение) — Fix по результатам баг-репорта
### Что исправлено в Terraform provider
- `fission_package`: добавлен `ModifyPlan`, который автоматически пересчитывает `code_hash` по локальному коду (`source_dir`/`code_path`).
- `fission_package`: добавлена валидация существования `environment` до создания/обновления package.
- `fission_function`: добавлена валидация существования `environment` и `package` до create/update.
- `fission_function`: добавлена pre-flight валидация `entrypoint` для Python-исходника (`main.func` должен существовать как `def func(`).
- Добавлены unit-тесты на новый функционал (`code_hash`, entrypoint validation).
### Проверка на живом кластере
- После изменения `examples/hello-python/code/main.py` `terraform plan` теперь показывает `fission_package.hello will be updated in-place` с изменением `code_hash`.
- Конфигурация с несуществующим `environment` теперь падает на этапе apply с ошибкой валидации.
- Конфигурация с неверным `entrypoint` теперь падает на этапе apply с ошибкой валидации.
### Ограничения
- Runtime-проблемы Fission (зависания на cold start/таймауты выполнения) в этом изменении не трогались: исправлен только provider-слой валидации и обнаружения изменений.
## 2026-04-15 (дополнение) — Fix отображения кода в Console UI
### Проблема
- В модальном окне редактирования функции (`Edit Code`) для некоторых пакетов отображались байты ZIP (`PK...`) вместо исходного кода.
### Причина
- `GET /console/api/functions/{name}` декодировал `spec.deployment.literal` только из base64, но не обрабатывал архивированный payload.
### Исправление
- В `console/main.go` добавлено декодирование `literal` с поддержкой ZIP:
- если payload plain text/utf-8 -> отдаётся как есть
- если payload ZIP -> извлекается `main.py`/`main.js`/`main.go` (или первый utf-8 файл)
- Обновлён deployment image: `naeel/fission-console:v0.2.4`.
### Проверка
- `GET /console/api/functions/fn-go-acc` теперь возвращает читаемый Go-код, без `PK...` сигнатур.
## 2026-04-15 (дополнение) — Полный пользовательский прогон UI и invoke-fix
### Что проверено как пользовательский сценарий
- Прогнан массовый invoke через UI API (`POST /console/api/functions/{name}/invoke`) для всех функций в списке.
- Итого: `31` функций, из них `25` успешно отработали, `6` вернули 502/timeout (ожидаемо проблемные/сломанные кейсы).
- Проверен полный edit flow через UI API на рабочей функции:
- `GET function` -> `PUT /code` -> `POST /invoke` -> `PUT /code` (restore) -> `POST /invoke`.
### Найденный UI-баг и исправление
- Баг: после обновления кода через UI следующий invoke мог отдавать старую специализацию/кэш.
- Причина: обновлялся только `Package`, но не обновлялся `Function.spec.package.packageref.resourceversion`.
- Фикс в `console/main.go`:
- после `package update` читается новый `package.resourceVersion`
- выполняется update `Function` с новым `packageref.resourceversion`
- invoke сразу использует новую (или восстановленную) версию кода.
### Проверка фикса
- После update через UI invoke возвращает новый ответ.
- После restore через UI invoke возвращает исходный ответ (без зависания старого кэша).
### Статус `fn-go-acc`
- `fn-go-acc` продолжает падать не из-за UI, а из-за runtime specialization на стороне Fission.
- Подтверждено логами `router/executor`: `GetServiceForFunction ... context canceled` и постоянными readiness-fail у poolmgr pod-ов `go-acc`.
## 2026-04-15 (дополнение) — Финальный фикс `fn-go-acc`
### Симптом и root cause
- `fn-go-acc` стабильно timeout'ился на invoke.
- Изначальный пакет содержал `main.go` как `deployment.literal`, из-за чего go-runtime пытался грузить текст как plugin:
- `plugin.Open("/userfunc/deployarchive/main.go"): invalid ELF header`.
### Что сделано
- Пересобран deploy-артефакт как Go plugin (`main.so`) и упакован в `deploy.zip`.
- Важно: сборка выполнена в том же образе, что у Fission environment builder:
- `ghcr.io/fission/go-builder` (Go 1.25.6), чтобы избежать несовместимости plugin ABI.
- Функция обновлена через Fission CLI:
- `fission function update -n default --name fn-go-acc --env go-acc --entrypoint Handler --deployarchive /tmp/fn-go-acc-fix2/deploy.zip -f`.
### Результат проверки
- `/go-acc` через router с JWT: `hello-go-ok`, `HTTP 200`.
- Executor логи: specialization проходит успешно (`specialized pod`, `added function service`), без `invalid ELF`.
### Контрольный smoke после фикса
- `/auto/ok` -> `HTTP 200`
- `/js-acc` -> `HTTP 200`
- `/js-direct` -> `HTTP 200`
- `/go-acc` -> `HTTP 200`
## 2026-04-15 (дополнение) — Edit Code для `fn-go-acc` снова показывает исходник
### Проблема
- В `Edit Code` для `fn-go-acc` поле `code` было пустым.
- Причина: после `--deployarchive` пакет `pkg-go-acc` перешел на `spec.deployment.type=url`, а в console backend чтение кода шло только из `spec.deployment.literal`.
### Исправление
- В `console/main.go` добавлен fallback-поиск исходника:
- `spec.source.literal`
- `spec.deployment.literal`
- `spec.source.url`
- `spec.deployment.url`
- Добавлена загрузка архива по URL и извлечение исходника из zip (если есть текстовые файлы).
- Добавлен unit-тест `TestGetFunctionUsesSourceLiteralWhenDeploymentLiteralMissing`.
- Собран и выкачен образ `naeel/fission-console:v0.2.6`, деплой обновлен.
### Дополнительно по данным в кластере
- Для `fn-go-acc` обновлен `sourcearchive` (`main.go`), чтобы `pkg-go-acc.spec.source.literal` содержал исходник и был доступен в Edit Code.
### Проверка
- `GET /console/api/functions/fn-go-acc` возвращает непустой `code` (Go source).
- `/go-acc` продолжает отвечать `hello-go-ok`, `HTTP 200`.
## 2026-04-15 (дополнение) — TLS в console, бренд NUBES, проверка `tf-neg-syntax-fn`
### TLS: фактический статус
- `http://fission.kube5s.ru/console/` -> `308 Permanent Redirect` на `https://...`
- `https://fission.kube5s.ru/console/` -> `200`
- Ingress `fission-console` настроен с:
- `nginx.ingress.kubernetes.io/force-ssl-redirect: "true"`
- `tls.secretName: fission-tls`
- `cert-manager.io/cluster-issuer: letsencrypt-prod`
### UI оформление (NUBES)
- В `console/ui/index.html` добавлены:
- favicon (inline SVG)
- брендинг в navbar: mark + wordmark `NUBES` / `FISSION CONSOLE`
- обновлен `<title>` на `NUBES Fission Console`
- Выкат: `naeel/fission-console:v0.2.9`.
### Invoke `tf-neg-syntax-fn` — проверка
- Подтверждено, что у функции синтаксически невалидный код (`def main(: ...`).
- Console invoke теперь возвращает fail-fast ошибку (не «молчание»):
- `{"error":"invoke \"tf-neg-syntax-fn\" timeout after 20s: function specialization likely failed (for example, syntax error)"}`
- Причина на кластере: specialization падает в runtime с `500`, executor делает ретраи.
## 2026-04-15 (дополнение) — Лечение "красного" HTTPS
### Root cause
- На одном host `fission.kube5s.ru` было два ingress:
- `fission-console` с TLS
- `fission-router` без TLS
- Из-за смешанной host-конфигурации TLS мог работать нестабильно/давать красный индикатор в браузере.
### Fix
- Пропатчен `fission/fission-router`:
- добавлен `spec.tls` с `secretName: fission-tls`
- добавлена аннотация `nginx.ingress.kubernetes.io/force-ssl-redirect: "true"`
### Проверка
- `http://fission.kube5s.ru/neg/syntax` -> `308` redirect на HTTPS
- Сертификат endpoint: Let's Encrypt R13, CN/SAN `fission.kube5s.ru`, валиден
- `https://fission.kube5s.ru/console/` -> `HTTP 200`
## 2026-04-15 (дополнение) — Строгая очистка: оставлены только рабочие и с видимым кодом
### Требование
- Убрать всё лишнее и оставить только функции, которые одновременно:
- имеют непустой `code` в `GET /console/api/functions/{name}`
- успешно проходят `POST /console/api/functions/{name}/invoke` со `status=200`
### Что сделано
- Выполнена финальная очистка функций по whitelist.
- После очистки дополнительно проверены все оставшиеся функции через Console API (code + invoke).
- Исправлена ошибка в служебном cleanup-скрипте (битый jsonpath с `{n}`), из-за которой падала пост-обработка orphan-ресурсов.
### Итоговый набор
- `auth-test2`
- `fn-js-direct`
- `fn-js-acc`
- `hello`
- `tf-auto-ok-fn`
- `tf-stress-fast-fn`
### Финальное состояние кластера
- `functions=6`
- `httptriggers=6`
- `packages=6`
### Финальная валидация
- Для каждой из 6 функций: `code=OK`, `invoke=OK`.
## 2026-04-15 (дополнение) — Наращивание до 15 функций + исправление фавикона в live UI
### Запрос
- Увеличить набор до 15 функций, включая error-кейсы.
- Прогнать тесты и чинить код/манифесты при расхождении с ожиданием.
- Исправить favicon в реальном UI (а не только в репозитории).
### Что сделано по функциям
- Подняты функции из `examples`: `hello-python`, `deep-recursion`, `destroy-test`, `frequent-update`, `orphan-test`, `multi-env-1/2/3`.
- Добавлен негативный кейс `tf-neg-syntax-fn` с route `/neg/syntax`.
- Доведено до ровно `functions=15`, `httptriggers=15`.
### Фиксы по коду/манифестам (по результатам тестов)
- `examples/multi-env-1/main.tf`
- исправлен `source_dir` с `"$\{path.module\}/code"` на `"${path.module}/code"`.
- заменен image env с `ghcr.io/fission/python-env:v1.20.0` на `ghcr.io/fission/python-env`.
- `examples/multi-env-2/main.tf`
- аналогичные исправления `source_dir` и image.
- `examples/multi-env-3/main.tf`
- аналогичные исправления `source_dir` и image.
### Тест-матрица (router + JWT)
- Итог: `PASS 15 / FAIL 0`.
- Успешные (`HTTP 200`):
- `/auth-test2`, `/js-acc`, `/js-direct`, `/hello`, `/auto/ok`, `/destroy-test`, `/freq-update`, `/tf-hello`, `/multi-env-1`, `/multi-env-2`, `/multi-env-3`, `/orphan-test`, `/stress/fast`.
- Ожидаемые error-кейсы:
- `/deep-recursion` -> timeout (`curl rc=28`, `HTTP 000`)
- `/neg/syntax` -> timeout (`curl rc=28`, `HTTP 000`)
- Доп. проверка через Console invoke:
- `tf-multi-env-1-fn` -> `status=200`
- `tf-hello-fn` -> `status=200`
- `tf-neg-syntax-fn` и `tf-deep-recursion-fn` -> ожидаемая fail-fast ошибка invoke timeout.
### Фавикон (live)
- Причина «старого фавикона»: в кластере работал старый образ `naeel/fission-console:v0.3.0`.
- Собран и выкачен новый образ: `naeel/fission-console:v0.3.1`.
- Deployment обновлен и успешно прокатан.
- Проверено в live HTML: отдается
- `<link rel="icon" type="image/png" href="https://nubes.ru/themes/custom/nubes_2025/favicon.png">`.