0.167.4

Changed

  • Навыки, создаваемые агентом через skill_manage, — только индивидуальные (ADR 2026-08-03). Инструмент skill_manage агента ранее создавал глобальные навыки (store.CreateSkill с OwnerAgentID = nil) и лишь привязывал их к текущему агенту. Это противоречило модели владения и создавало IDOR-подобные уязвимости: updateAction/deleteAction работали по skill_id без проверки принадлежности, listAction показывал все навыки (включая индивидуальные других агентов), а getAction позволял читать чужой приватный контент.

Изменения (internal/agent/skill_manage_tool.go):

  • createAction: теперь вызывает store.CreateAgentOwnedSkill (ставит owner_agent_id = agentID) вместо CreateSkill + AddAgentSkill. Навык становится индивидуальным (приватным для агента) с момента создания.
  • Автоматический префикс имени: имя создаваемого/переименовываемого навыка формируется как <agentSlug>-<shortName>, где agentSlug — результат NameSlug(Agent.Name). Гарантирует уникальность на системном UNIQUE-индексе skills.name. Примеры: агент «Семён» → semen-my-skill; «Дмитрий» → dmitriy-my-skill; «My Agent» → my-agent-.... Полное имя валидируется ^[a-z][a-z0-9-]{2,40}$.
  • updateAction/deleteAction: добавлена ownership-проверка (ownedByCurrentAgent) — операция разрешена только если skill.OwnerAgentID == agentID. Builtin, глобальные и чужие навыки недоступны; при отказе возвращается skill not found (fail-closed, не палит существование — согласовано с getAction).
  • getAction: добавлена проверка readableByCurrentAgent — builtin и глобальные общедоступны для чтения, индивидуальные — только свои. Чужой индивидуальный навык возвращает skill not found.
  • listAction: ListSkills заменён на ListAgentSkills(agentID) — показывает только навыки, подключённые к текущему агенту (симметрично блоку «Навыки агента» в UI). Чужие индивидуальные навыки не видны.
  • Обработка ошибок БД: GetSkill/GetSkillByName различают not-found (store.IsNotFoundError) от реальных ошибок БД; UNIQUE-нарушения при create/rename (TOCTOU) переводятся в дружелюбное «skill with name ‘…’ already exists» через store.IsUniqueViolationError.

Транслитерация кириллицы (internal/agent/slug.go, новая утилита): NameSlug переводит имя агента в slug фонетической схемой (русский алфавит + Ё): «Семён»→semen, «Дмитрий»→dmitriy, «Юрий»→yuriy, «Щорс»→shchors. Твёрдый/мягкий знак опускаются. Fallback agent для пустого результата (эмодзи/пробелы). Отдельна от workspace.SanitizeName намеренно — изменение SanitizeName рассинхронило бы существующие workspace-каталоги (см. ADR, альтернатива A).

Builtin-навык skill-creator (миграция 105, SQLite + PG): обновлены description и content — упоминается индивидуальность навыков, автоматический префикс имени, ownership-правила.

Тесты: 32 unit-теста TestNameSlug (positive/negative/edge) + 11 integration-тестов TestSkillManage_* через реальный SQLiteStore (create/update/delete/list/get, ownership, prefix, duplicate, validation).

Известные ограничения (зафиксированы в ADR): agents.name не имеет UNIQUE-констрейнта — два агента с именами, дающими одинаковый slug (например «Ivan» и «Иван»), коллизируют на skills.name; обрабатывается дружелобной ошибкой. Предсуществующая не-атомарность CreateAgentOwnedSkill (orphan при сбое linking) вынесена в отдельное.

Связанный техдолг: (P3) — обернуть CreateAgentOwnedSkill в транзакцию.