hotfix(paliadin): ship user_id on /chat/turn (aichat tenant-DB requirement)

m reported "ai chat seems not to be wired anymore" + the frontend
showed "Verbindung verloren. Antwort wird nachgereicht…".

Root cause: aichat on mRiver added a tenant-DB layer that demands
`user_id` on every /chat/turn request:

  {"error":{"code":"bad_request",
            "message":"user_id is required when a tenant DB is
                       configured","retryable":false}}

aichat itself is healthy (/chat/health 200, paliadin session ok:true,
last successful turn was ~2.6h ago). The paliad side built and shipped
an aichatTurnRequest without user_id, so every turn since the tenant-DB
flip 400s; paliad's SSE relay receives no upstream data and closes
empty, producing the user-visible "Verbindung verloren".

Fix: add UserID to aichatTurnRequest (json: user_id, mandatory now),
populate from req.UserID.String() at the call site. The userID was
already in scope (used for JWT mint + username lookup); the struct just
wasn't shipping it.

Regression test in TestRunTurn_HappyPath_ViaCallHTTP asserts
captured.UserID == request UUID so a future struct edit that drops the
field fails CI instead of production.
This commit is contained in:
mAi
2026-05-21 21:21:32 +02:00
parent 7c7030c5bf
commit 17d149c09e
2 changed files with 15 additions and 2 deletions

View File

@@ -217,6 +217,7 @@ func (s *AichatPaliadinService) RunTurn(ctx context.Context, req TurnRequest) (*
body := aichatTurnRequest{ body := aichatTurnRequest{
Persona: s.cfg.Persona, Persona: s.cfg.Persona,
Username: username, Username: username,
UserID: req.UserID.String(),
SessionID: req.SessionID, SessionID: req.SessionID,
Message: sanitiseForTmux(req.UserMessage), Message: sanitiseForTmux(req.UserMessage),
JWT: jwt, JWT: jwt,
@@ -611,8 +612,13 @@ func (s *AichatPaliadinService) clearPrimed(session string) {
// ============================================================================= // =============================================================================
type aichatTurnRequest struct { type aichatTurnRequest struct {
Persona string `json:"persona"` Persona string `json:"persona"`
Username string `json:"username"` Username string `json:"username"`
// UserID is the paliad user UUID, required by aichat now that a
// tenant DB is configured ("user_id is required when a tenant DB
// is configured"). Without it /chat/turn 400s and the SSE relay
// closes empty → "Verbindung verloren" on the frontend.
UserID string `json:"user_id"`
SessionID string `json:"session_id,omitempty"` SessionID string `json:"session_id,omitempty"`
Message string `json:"message"` Message string `json:"message"`
JWT string `json:"jwt,omitempty"` JWT string `json:"jwt,omitempty"`

View File

@@ -501,6 +501,7 @@ func TestRunTurn_HappyPath_ViaCallHTTP(t *testing.T) {
body := aichatTurnRequest{ body := aichatTurnRequest{
Persona: s.cfg.Persona, Persona: s.cfg.Persona,
Username: s.usernameFor(context.Background(), uid), Username: s.usernameFor(context.Background(), uid),
UserID: uid.String(),
Message: "Hello", Message: "Hello",
JWT: jwtTok, JWT: jwtTok,
Meta: buildAichatMeta(TurnRequest{PageOrigin: "/dashboard"}), Meta: buildAichatMeta(TurnRequest{PageOrigin: "/dashboard"}),
@@ -516,6 +517,12 @@ func TestRunTurn_HappyPath_ViaCallHTTP(t *testing.T) {
if captured.Username != "user-aaaaaaaa" { if captured.Username != "user-aaaaaaaa" {
t.Errorf("username = %q; want user-aaaaaaaa (nil DB fallback)", captured.Username) t.Errorf("username = %q; want user-aaaaaaaa (nil DB fallback)", captured.Username)
} }
// Regression for the 2026-05-21 outage: aichat now requires user_id
// when a tenant DB is configured; missing → 400 → SSE drop on the
// frontend ("Verbindung verloren"). The struct must carry it.
if captured.UserID != uid.String() {
t.Errorf("user_id = %q; want %q", captured.UserID, uid.String())
}
if captured.Message != "Hello" { if captured.Message != "Hello" {
t.Errorf("message = %q; want Hello", captured.Message) t.Errorf("message = %q; want Hello", captured.Message)
} }