# Humanized Stepan — CHANGELOG v2.8 **Version:** v2.8 **Date:** 2026-02-25 **Базується на:** v2.7.2 (PII-safe telemetry, recent_topics horizon, invariant tests) --- ## Summary - **Multi-user farm model**: `FarmProfile` тепер зберігається під ключем `farm_profile:agromatrix:chat:{chat_id}` — shared для всіх операторів в одному чаті. - **UserProfile** залишається per-user (`user_profile:agromatrix:{user_id}`) — стиль, recent_topics, interaction_summary окремі для кожного. - **Lazy migration**: перший запит з `user_id` автоматично мігрує старий legacy-ключ `farm_profile:agromatrix:{user_id}` у новий chat-ключ (write-through, без ручного втручання). - **Conflict policy**: якщо chat-profile вже існує і відрізняється від legacy — не перезаписуємо; лише tlog `farm_profile_conflict`. - **FarmProfile v5**: додані нові поля (`farm_name`, `field_ids`, `crop_ids`, `active_integrations`, `iot_sensors`, `alert_thresholds`, `seasonal_context`). - **Backward-compat**: `load_farm_profile(chat_id)` без `user_id` — не крашить, повертає default. --- ## Key features (деталі) ### Нові fact-ключі | Тип | Ключ | Scope | |---|---|---| | UserProfile | `user_profile:agromatrix:{user_id}` | per-user (без змін) | | FarmProfile (v2.8) | `farm_profile:agromatrix:chat:{chat_id}` | per-chat (новий) | | FarmProfile (legacy) | `farm_profile:agromatrix:{user_id}` | deprecated, мігрується lazy | ### Lazy Migration Flow ``` load_farm_profile(chat_id, user_id) │ ├── cache hit (chat-key)? → return ├── memory-service chat-key? → return + cache ├── memory-service legacy-key (user_id)? │ ├── YES → copy to chat-key (write-through) + return migrated profile │ │ tlog: farm_profile_migrated │ └── NO → default farm_profile(chat_id) ``` ### Conflict Policy При явній міграції через `migrate_farm_profile_legacy_to_chat()`: - Якщо chat-profile існує і **суттєво відрізняється** (crops/field_ids/region/season_state) → NOT overwritten - `tlog: farm_profile_conflict reason=legacy_diff` - Повертається існуючий chat-profile Критерій суттєвої відмінності (`_farm_profiles_differ`): порівнює `crops`, `field_ids`, `fields`, `region`, `season_state`, `active_integrations`. ### FarmProfile v5 — нові поля ```json { "_version": 5, "chat_id": "...", "farm_name": null, "field_ids": [], "crop_ids": [], "active_integrations": [], "iot_sensors": [], "alert_thresholds": {}, "seasonal_context": {}, "region": null, "crops": [], "fields": [], "season_state": null } ``` --- ## Backward Compatibility | Аспект | Деталі | |---|---| | `load_farm_profile(chat_id)` | Без `user_id` — не крашить (legacy path пропускається) | | `load_farm_profile(chat_id, user_id)` | Новий API; `user_id` потрібен тільки для lazy migration | | `save_farm_profile(chat_id, profile)` | API без змін (тепер під chat-key автоматично) | | Legacy ключ | Не видаляється, існує в memory-service до явного очищення | | `_version` FarmProfile | 1 → 5; non-breaking (нові поля, старі залишаються) | --- ## Non-goals / not included - Немає автоматичного merge при конфлікті. - Немає видалення legacy ключів (тільки read-migrate). - Немає зміни light/deep логіки, тональності, банків фраз. - Немає нових ендпоінтів або інфра-змін. --- ## Tests **Результат:** 161/161 зелених (без регресій з v2.7.2) | Файл | Нових тестів | Опис | |---|---|---| | `tests/test_stepan_v28_farm.py` | 24 | Multi-user farm: ключі, міграція, конфлікт, acceptance | ```bash # Тільки v2.8 farm тести python3 -m pytest tests/test_stepan_v28_farm.py -v # Всі Stepan тести python3 -m pytest tests/test_stepan_v28_farm.py tests/test_stepan_telemetry.py \ tests/test_stepan_invariants.py tests/test_stepan_acceptance.py \ tests/test_stepan_light_reply.py tests/test_stepan_memory_followup.py -v ``` --- ## Rollback ```bash git checkout HEAD~1 -- crews/agromatrix_crew/memory_manager.py \ crews/agromatrix_crew/run.py docker compose -f docker-compose.node1.yml up -d --build dagi-gateway-node1 ``` Після rollback до v2.7.x: farm_profile знову читатиметься зі старого legacy-ключа (якщо є в cache/memory-service). Новий chat-ключ залишиться в memory-service, але не буде використовуватись.