Skip to content

Commit 0e816e2

Browse files
authored
Merge pull request #196 from dapi/fix/195-rubyllm-invalidroleerror-webhook
fix: Validate message roles to prevent RubyLLM::InvalidRoleError (#195)
2 parents a2fcce6 + 2eab28d commit 0e816e2

9 files changed

Lines changed: 74 additions & 9 deletions

File tree

CLAUDE.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,36 @@ bin/rails screenshots:dashboard
166166
- **При изменениях:** ROADMAP-ARCHIVE.md обновляется
167167
168168
169-
# Подключение к production базе для диагностики
169+
# Production & Kubernetes доступ
170+
171+
**Кластер:** fury (K8s context: `fury`)
172+
**Namespace:** `valera-production`
173+
**Deployment:** `valera`
174+
**Container:** `ror`
175+
176+
## Подключение к production базе для диагностики
170177
171178
Креды для подключения к production postgresql базы для диагностики находятся в
172179
переменных окружения: PRODUCTION_VALERA_DATABASE_USERNAME, PRODUCTION_VALERA_DATABASE_HOST, PRODUCTION_VALERA_DATABASE_PASSWORD, PRODUCTION_VALERA_DATABASE_NAME
173180
174181
используй их для подключения через psql
175182
183+
## Прямой доступ через kubectl
184+
185+
```bash
186+
# Rails Console
187+
kubectl --context=fury -n valera-production exec -it deploy/valera -c ror -- bundle exec rails console
188+
189+
# Rails Runner
190+
kubectl --context=fury -n valera-production exec deploy/valera -c ror -- bundle exec rails runner 'puts User.count'
191+
192+
# Проверка подов
193+
kubectl --context=fury -n valera-production get pods
194+
195+
# Логи
196+
kubectl --context=fury -n valera-production logs deploy/valera -c ror --tail=100
197+
```
198+
176199
# Авторизация в SaaS для диагностики в разработке
177200

178201
Доступ в administarte осуществляется через playwright mcp по адресу указанном в переменной окружения

app/models/message.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# Represents a single message within a chat conversation
44
class Message < ApplicationRecord
5+
# Valid message roles: RubyLLM::Message::ROLES + app-specific roles (manager).
6+
VALID_ROLES = %w[system user assistant tool manager].freeze
7+
58
acts_as_message touch_chat: :last_message_at
69
has_many_attached :attachments
10+
11+
validates :role, presence: true, inclusion: { in: VALID_ROLES }
712
end

app/views/landing/lp1.html.slim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ section#form.py-20.md:py-32.bg-gradient-to-br.from-gray-900.via-blue-900.to-gray
441441

442442
div
443443
= f.label :phone, "Телефон", class: "block text-sm font-semibold text-gray-700 mb-2"
444-
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50 placeholder-gray-400 text-gray-900", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel", autofocus: true
444+
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50 placeholder-gray-400 text-gray-900", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel"
445445

446446
div
447447
= f.label :name, "Ваше имя", class: "block text-sm font-semibold text-gray-700 mb-2"

app/views/landing/lp_magic_1.html.slim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ section#form.py-20.md:py-32.bg-gradient-to-br.from-slate-900.via-blue-900.to-sla
556556

557557
div
558558
= f.label :phone, "Телефон", class: "block text-sm font-semibold text-slate-700 mb-2"
559-
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-slate-50 placeholder-slate-400 text-slate-900 transition-all", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel", autofocus: true
559+
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-slate-50 placeholder-slate-400 text-slate-900 transition-all", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel"
560560

561561
div
562562
= f.label :name, "Ваше имя", class: "block text-sm font-semibold text-slate-700 mb-2"

app/views/landing/lp_magic_2.html.slim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ section#form.py-28.lg:py-36.bg-gradient-to-br.from-blue-600.via-blue-700.to-blue
426426
= f.hidden_field :utm_campaign, data: { utm_target: "campaign" }
427427

428428
/ Phone input - large
429-
= f.telephone_field :phone, class: "w-full px-6 py-5 text-lg border-2 border-slate-200 rounded-2xl focus:ring-4 focus:ring-blue-100 focus:border-blue-500 bg-slate-50 text-slate-900 transition-all placeholder-slate-400", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel", autofocus: true
429+
= f.telephone_field :phone, class: "w-full px-6 py-5 text-lg border-2 border-slate-200 rounded-2xl focus:ring-4 focus:ring-blue-100 focus:border-blue-500 bg-slate-50 text-slate-900 transition-all placeholder-slate-400", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel"
430430

431431
/ Submit button
432432
= f.submit "Жду звонка", class: "w-full bg-blue-600 text-white font-bold py-5 rounded-2xl text-lg hover:bg-blue-700 active:bg-blue-800 transition-all cursor-pointer shadow-lg shadow-blue-600/30 hover:shadow-xl hover:shadow-blue-600/40 hover:-translate-y-0.5"

app/views/landing/lp_magic_3.html.slim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ section#form.py-20.bg-gradient-to-br.from-orange-500.to-red-600.text-white.relat
403403

404404
div
405405
= f.label :phone, "Телефон", class: "block text-sm font-semibold text-slate-700 mb-2"
406-
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-slate-200 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-orange-500 bg-slate-50 text-slate-900", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel", autofocus: true
406+
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-slate-200 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-orange-500 bg-slate-50 text-slate-900", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel"
407407

408408
div
409409
= f.label :name, "Ваше имя", class: "block text-sm font-semibold text-slate-700 mb-2"

app/views/landing/show.html.slim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ section#form.py-16.md:py-20.px-4.bg-blue-600.text-white
163163
.space-y-5
164164
div.text-left
165165
= f.label :phone, "Телефон", class: "block text-sm font-semibold text-gray-700 mb-2"
166-
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50 placeholder-gray-400", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel", autofocus: true
166+
= f.telephone_field :phone, class: "w-full px-4 py-4 text-lg border-2 border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50 placeholder-gray-400", placeholder: "+7 (999) 123-45-67", required: true, inputmode: "tel"
167167

168168
div.text-left
169169
= f.label :name, "Ваше имя", class: "block text-sm font-semibold text-gray-700 mb-2"

bin/production-rails-runner

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
set -euo pipefail
1515

16-
NAMESPACE="${PRODUCTION_NAMESPACE:-production}"
16+
CONTEXT="${PRODUCTION_CONTEXT:-fury}"
17+
NAMESPACE="${PRODUCTION_NAMESPACE:-valera-production}"
1718
DEPLOYMENT="${PRODUCTION_DEPLOYMENT:-valera}"
1819
CONTAINER="${PRODUCTION_CONTAINER:-ror}"
1920

@@ -30,10 +31,10 @@ fi
3031

3132
if [[ "$1" == "-" ]]; then
3233
# Read from stdin
33-
kubectl exec -i -n "$NAMESPACE" "deployment/$DEPLOYMENT" -c "$CONTAINER" -- \
34+
kubectl --context="$CONTEXT" exec -i -n "$NAMESPACE" "deployment/$DEPLOYMENT" -c "$CONTAINER" -- \
3435
bundle exec rails runner -
3536
else
3637
# Pass argument directly
37-
kubectl exec -n "$NAMESPACE" "deployment/$DEPLOYMENT" -c "$CONTAINER" -- \
38+
kubectl --context="$CONTEXT" exec -n "$NAMESPACE" "deployment/$DEPLOYMENT" -c "$CONTAINER" -- \
3839
bundle exec rails runner "$1"
3940
fi

test/models/message_test.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,40 @@ class MessageTest < ActiveSupport::TestCase
88
assert message.valid?
99
assert message.persisted?
1010
end
11+
12+
test 'valid roles are accepted' do
13+
chat = chats(:one)
14+
Message::VALID_ROLES.each do |role|
15+
message = Message.new(chat: chat, role: role, content: 'Test content')
16+
assert message.valid?, "Role '#{role}' should be valid but got errors: #{message.errors.full_messages}"
17+
end
18+
end
19+
20+
test 'invalid role is rejected' do
21+
chat = chats(:one)
22+
message = Message.new(chat: chat, role: 'invalid_role', content: 'Test content')
23+
assert_not message.valid?
24+
assert message.errors[:role].any?
25+
end
26+
27+
test 'empty role is rejected' do
28+
chat = chats(:one)
29+
message = Message.new(chat: chat, role: '', content: 'Test content')
30+
assert_not message.valid?
31+
assert message.errors[:role].any?
32+
end
33+
34+
test 'nil role is rejected' do
35+
chat = chats(:one)
36+
message = Message.new(chat: chat, role: nil, content: 'Test content')
37+
assert_not message.valid?
38+
assert message.errors[:role].any?
39+
end
40+
41+
test 'VALID_ROLES includes all RubyLLM roles' do
42+
llm_roles = RubyLLM::Message::ROLES.map(&:to_s)
43+
missing = llm_roles - Message::VALID_ROLES
44+
assert missing.empty?,
45+
"Message::VALID_ROLES is missing RubyLLM roles: #{missing.join(', ')}"
46+
end
1147
end

0 commit comments

Comments
 (0)