---
phase: 07-multilanguage
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - migrations/versions/009_multilanguage.py
  - packages/shared/shared/models/tenant.py
  - packages/shared/shared/models/auth.py
  - packages/shared/shared/prompts/system_prompt_builder.py
  - packages/portal/lib/system-prompt-builder.ts
  - packages/shared/shared/email.py
  - packages/shared/shared/api/templates.py
  - packages/shared/shared/api/portal.py
  - tests/unit/test_system_prompt_builder.py
  - tests/integration/test_language_preference.py
  - tests/integration/test_templates_i18n.py
autonomous: true
requirements:
  - I18N-03
  - I18N-04
  - I18N-05
  - I18N-06

must_haves:
  truths:
    - "AI Employees respond in the same language the user writes in"
    - "Agent templates have Spanish and Portuguese translations stored in DB"
    - "Invitation emails are sent in the inviting admin's language"
    - "portal_users table has a language column defaulting to 'en'"
    - "Templates API returns translated fields when locale param is provided"
  artifacts:
    - path: "migrations/versions/009_multilanguage.py"
      provides: "DB migration adding language to portal_users and translations JSONB to agent_templates"
      contains: "portal_users"
    - path: "packages/shared/shared/prompts/system_prompt_builder.py"
      provides: "Language instruction appended to all system prompts"
      contains: "LANGUAGE_INSTRUCTION"
    - path: "packages/portal/lib/system-prompt-builder.ts"
      provides: "TS mirror with language instruction"
      contains: "LANGUAGE_INSTRUCTION"
    - path: "packages/shared/shared/email.py"
      provides: "Localized invitation emails in en/es/pt"
      contains: "language"
    - path: "packages/shared/shared/api/templates.py"
      provides: "Locale-aware template list endpoint"
      contains: "locale"
    - path: "tests/integration/test_language_preference.py"
      provides: "Integration tests for PATCH language preference endpoint"
      contains: "test_"
    - path: "tests/integration/test_templates_i18n.py"
      provides: "Integration tests for locale-aware templates endpoint"
      contains: "test_"
  key_links:
    - from: "packages/shared/shared/prompts/system_prompt_builder.py"
      to: "AI Employee responses"
      via: "LANGUAGE_INSTRUCTION appended in build_system_prompt()"
      pattern: "LANGUAGE_INSTRUCTION"
    - from: "packages/shared/shared/api/templates.py"
      to: "agent_templates.translations"
      via: "JSONB column merge on locale query param"
      pattern: "translations"
---

<objective>
Backend multilanguage foundation: DB migration, system prompt language instruction, localized invitation emails, and locale-aware templates API.

Purpose: Provides the backend data layer and AI language behavior that all frontend i18n depends on. Without this, there is no language column to persist, no template translations to display, and no agent language instruction.
Output: Migration 009, updated system prompt builder (Python + TS), localized email sender, locale-aware templates API, unit tests, integration tests.
</objective>

<execution_context>
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07-multilanguage/07-CONTEXT.md
@.planning/phases/07-multilanguage/07-RESEARCH.md

<interfaces>
<!-- Key types and contracts the executor needs -->

From packages/shared/shared/models/auth.py:
```python
class PortalUser(Base):
    __tablename__ = "portal_users"
    id: Mapped[uuid.UUID]
    email: Mapped[str]
    hashed_password: Mapped[str]
    name: Mapped[str]
    role: Mapped[str]
    created_at: Mapped[datetime]
    updated_at: Mapped[datetime]
    # NEEDS: language: Mapped[str] = mapped_column(String(10), nullable=False, server_default='en')
```

From packages/shared/shared/models/tenant.py (AgentTemplate):
```python
class AgentTemplate(Base):
    __tablename__ = "agent_templates"
    # Existing columns: id, name, role, description, category, persona, system_prompt,
    # model_preference, tool_assignments, escalation_rules, is_active, sort_order, created_at
    # NEEDS: translations: Mapped[dict] = mapped_column(JSON, nullable=False, server_default='{}')
```

From packages/shared/shared/prompts/system_prompt_builder.py:
```python
AI_TRANSPARENCY_CLAUSE = "When directly asked if you are an AI, always disclose that you are an AI assistant."
def build_system_prompt(name, role, persona="", tool_assignments=None, escalation_rules=None) -> str
```

From packages/portal/lib/system-prompt-builder.ts:
```typescript
export interface SystemPromptInput { name: string; role: string; persona?: string; ... }
export function buildSystemPrompt(data: SystemPromptInput): string
```

From packages/shared/shared/email.py:
```python
def send_invite_email(to_email: str, invitee_name: str, tenant_name: str, invite_url: str) -> None
# NEEDS: language: str = "en" parameter added
```

From packages/shared/shared/api/templates.py:
```python
class TemplateResponse(BaseModel):
    id: str; name: str; role: str; description: str; category: str; persona: str; ...
    @classmethod
    def from_orm(cls, tmpl: AgentTemplate) -> "TemplateResponse"

@templates_router.get("/templates", response_model=list[TemplateResponse])
async def list_templates(caller, session) -> list[TemplateResponse]
# NEEDS: locale: str = Query("en") parameter, merge translations before returning
```

From migrations/versions/ — latest is 008_web_chat.py, so next is 009.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: DB migration 009 + ORM updates + system prompt language instruction</name>
  <files>
    migrations/versions/009_multilanguage.py
    packages/shared/shared/models/auth.py
    packages/shared/shared/models/tenant.py
    packages/shared/shared/prompts/system_prompt_builder.py
    packages/portal/lib/system-prompt-builder.ts
    tests/unit/test_system_prompt_builder.py
  </files>
  <behavior>
    - Test: build_system_prompt("Mara", "Support Rep") output contains LANGUAGE_INSTRUCTION string
    - Test: build_system_prompt with full args (persona, tools, escalation) contains LANGUAGE_INSTRUCTION
    - Test: build_system_prompt with minimal args (name, role only) contains LANGUAGE_INSTRUCTION
    - Test: LANGUAGE_INSTRUCTION appears after identity section and before AI_TRANSPARENCY_CLAUSE
  </behavior>
  <action>
    1. Create migration 009_multilanguage.py:
       - Add `language` column (String(10), NOT NULL, server_default='en') to portal_users
       - Add `translations` column (JSON, NOT NULL, server_default='{}') to agent_templates
       - Backfill translations for all 7 existing seed templates with Spanish (es) and Portuguese (pt) translations for name, description, and persona fields. Use proper native business terminology — not literal machine translations. Each template gets a translations JSON object like: {"es": {"name": "...", "description": "...", "persona": "..."}, "pt": {"name": "...", "description": "...", "persona": "..."}}
       - downgrade: drop both columns

    2. Update ORM models:
       - PortalUser: add `language: Mapped[str] = mapped_column(String(10), nullable=False, server_default='en')`
       - AgentTemplate: add `translations: Mapped[dict] = mapped_column(JSON, nullable=False, server_default='{}')`

    3. Add LANGUAGE_INSTRUCTION to system_prompt_builder.py:
       ```python
       LANGUAGE_INSTRUCTION = (
           "Detect the language of each user message and respond in that same language. "
           "You support English, Spanish, and Portuguese."
       )
       ```
       Append to sections list BEFORE AI_TRANSPARENCY_CLAUSE (transparency clause remains last).

    4. Add LANGUAGE_INSTRUCTION to system-prompt-builder.ts (TS mirror):
       ```typescript
       const LANGUAGE_INSTRUCTION = "Detect the language of each user message and respond in that same language. You support English, Spanish, and Portuguese.";
       ```
       Append before the AI transparency clause line.

    5. Extend tests/unit/test_system_prompt_builder.py with TestLanguageInstruction class:
       - test_language_instruction_present_in_default_prompt
       - test_language_instruction_present_with_full_args
       - test_language_instruction_before_transparency_clause
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct && python -m pytest tests/unit/test_system_prompt_builder.py -x -v</automated>
  </verify>
  <done>
    - Migration 009 creates language column on portal_users and translations JSONB on agent_templates with es+pt seed data
    - LANGUAGE_INSTRUCTION appears in all system prompts (Python and TS)
    - All existing + new system prompt tests pass
  </done>
</task>

<task type="auto">
  <name>Task 2: Localized invitation emails + locale-aware templates API + language preference endpoint + integration tests</name>
  <files>
    packages/shared/shared/email.py
    packages/shared/shared/api/templates.py
    packages/shared/shared/api/portal.py
    tests/integration/test_language_preference.py
    tests/integration/test_templates_i18n.py
  </files>
  <action>
    1. Update send_invite_email() in email.py:
       - Add `language: str = "en"` parameter
       - Create localized subject lines dict: {"en": "You've been invited...", "es": "Has sido invitado...", "pt": "Voce foi convidado..."}
       - Create localized text_body and html_body templates for all 3 languages
       - Select the correct template based on language param, fallback to "en"
       - Update the invitations API endpoint that calls send_invite_email to pass the inviter's language (read from portal_users.language or default "en")

    2. Update templates API (templates.py):
       - Add `locale: str = Query("en")` parameter to list_templates() and get_template()
       - In TemplateResponse.from_orm(), add a `locale` parameter
       - When locale != "en" and tmpl.translations has the locale key, merge translated name/description/persona over the English defaults before returning
       - Keep English as the base — translations overlay, never replace the stored English values in DB

    3. Add language preference PATCH endpoint in portal.py:
       - PATCH /api/portal/users/me/language — accepts {"language": "es"} body
       - Validates language is in ["en", "es", "pt"]
       - Updates portal_users.language for the current user
       - Returns {"language": "es"} on success
       - Guard: any authenticated user (get_portal_caller)

    4. Update the verify auth endpoint (/api/portal/auth/verify) to include `language` in its response so Auth.js JWT can carry it.

    5. Create tests/integration/test_language_preference.py (Wave 0 — I18N-02):
       - Use the existing integration test pattern (httpx AsyncClient against the FastAPI app)
       - test_patch_language_valid: PATCH /api/portal/users/me/language with {"language": "es"} returns 200 and {"language": "es"}
       - test_patch_language_invalid: PATCH with {"language": "fr"} returns 422 or 400
       - test_patch_language_persists: PATCH to "pt", then GET /api/portal/auth/verify includes language="pt"
       - test_patch_language_unauthenticated: PATCH without auth returns 401

    6. Create tests/integration/test_templates_i18n.py (Wave 0 — I18N-04):
       - Use the existing integration test pattern (httpx AsyncClient against the FastAPI app)
       - test_list_templates_default_locale: GET /api/portal/templates returns English fields (no locale param)
       - test_list_templates_spanish: GET /api/portal/templates?locale=es returns Spanish-translated name/description/persona
       - test_list_templates_portuguese: GET /api/portal/templates?locale=pt returns Portuguese-translated fields
       - test_list_templates_unsupported_locale: GET /api/portal/templates?locale=fr falls back to English
       - test_template_translations_overlay: Verify translated fields overlay English, not replace — English base fields still accessible in DB
  </action>
  <verify>
    <automated>cd /home/adelorenzo/repos/konstruct && python -m pytest tests/unit -x -q && python -m pytest tests/integration/test_language_preference.py tests/integration/test_templates_i18n.py -x -v</automated>
  </verify>
  <done>
    - send_invite_email() accepts language param and sends localized emails in en/es/pt
    - GET /api/portal/templates?locale=es returns Spanish-translated template fields
    - PATCH /api/portal/users/me/language persists language preference
    - /api/portal/auth/verify response includes user's language field
    - Integration tests for language preference endpoint pass (4 tests)
    - Integration tests for locale-aware templates endpoint pass (5 tests)
  </done>
</task>

</tasks>

<verification>
- All existing unit tests pass: `pytest tests/unit -x -q`
- Integration tests pass: `pytest tests/integration/test_language_preference.py tests/integration/test_templates_i18n.py -x -v`
- Migration 009 is syntactically valid (imports, upgrade/downgrade functions present)
- system_prompt_builder.py contains LANGUAGE_INSTRUCTION
- system-prompt-builder.ts contains LANGUAGE_INSTRUCTION
- email.py send_invite_email has language parameter
- templates.py list_templates has locale parameter
</verification>

<success_criteria>
- Migration 009 adds language to portal_users and translations JSONB to agent_templates
- All 7 seed templates have es+pt translations backfilled
- AI Employees will respond in the user's language via system prompt instruction
- Templates API merges translations by locale
- Language preference PATCH endpoint works
- All unit tests pass
- All integration tests pass (language preference + templates i18n)
</success_criteria>

<output>
After completion, create `.planning/phases/07-multilanguage/07-01-SUMMARY.md`
</output>
