Internal Documentation internal
TalkIDE internal documentation

User (ze Workspace tabu) nebo Mara (z konverzace přes tool) nahlásí issue spojený s konkrétním projektem (target=PROJECT). Oba reporteři zapisují stejný datový shape — liší se zdrojem project_id a hodnotou reporter_agent. USER posílá project_id explicitně z FE route; Mara project_id nepředává vůbec — sidecar tool ho resolvuje z ctx automaticky.

  • Phase 3 rozšiřuje Issue API o target=PROJECT. Schema bylo připraveno již v Phase 1 (0019-create-issues-tables.xml), ale FK constraint issues.project_id → projects.id byl omylem vynechán — doplňuje ho nová migrace 0020-add-project-fk-to-issues.xml (immutable po deploymentu na prod).
  • Dva reporteři: USER (Studio uživatel kliká v UI) a MARA (tool call z konverzace). reporter_agent v requestu rozlišuje oba. Phase 1 whitelist MARA only je v #91 rozšířen na MARA | USER.
  • severity = CRITICAL Mara nikdy nenastaví — výhradně admin při triage (UC-09004). USER reportér taktéž nemůže nastavit CRITICAL.
  • Cascade delete: smazání projektu kaskádově smaže všechny jeho PROJECT issues přes DB FK ON DELETE CASCADE. ProjectCascadeDeleter žádnou logiku nemění — FK zajistí vše.
  • Context enrichment: stejný mechanismus jako UC-09001 (K8s events, pod logy) — pro target=PROJECT se dotazuje pouze tenant-<slug> namespace (ne talkide namespace). Detaily viz UC-09001 — Auto-context enrichment.

DB Migration — Phase 3 FK constraint

Nový soubor: src/main/resources/db/changelog/changes/0020-add-project-fk-to-issues.xml

NEEDITUJ existující soubor 0019. Toto je nový append-only changeset.

<changeSet id="0020-add-project-fk-to-issues" author="system">
    <addForeignKeyConstraint
        baseTableName="issues"
        baseColumnNames="project_id"
        constraintName="fk_issues_project"
        referencedTableName="projects"
        referencedColumnNames="id"
        onDelete="CASCADE"/>
</changeSet>

project_id zůstává nullable (Phase 1 PLATFORM issues nemají projekt — migrace funguje pro obě prostředí).


Flow 1 — USER reportuje PROJECT issue z Workspace tabu

sequenceDiagram
    actor User

    User->>+FE: klikne "Nahlásit problém" ve Workspace tabu

    FE->>FE: otevře modal s formulářem (kind, title, description, severity)

    User->>FE: vyplní formulář a odešle

    FE->>FE: validuje formulář (kind not null, title not blank max 80, description not blank)
    alt formulář nevalidní
        FE-->>User: zobrazí chybové hlášky u polí
    end

    FE->>+BE: POST /api/issues <br> IssueCreateRequest { target="PROJECT", project_id={current}, reporter_agent="USER", kind, title, description, severity? }
    Note over FE,BE: Authorization: Bearer {userJWT}

    BE->>BE: parseReporterAgent() — whitelist check: MARA | USER
    BE->>BE: validuj request (target, kind, title max 80, severity != CRITICAL)

    alt target=PROJECT a project_id null
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_ID_REQUIRED)
    end

    BE->>DB: SELECT projects WHERE id=project_id AND tenant_id = <z JWT>
    alt projekt neexistuje
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_NOT_FOUND)
    end
    alt projekt patří jinému tenantu
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_NOT_OWNED)
    end

    BE->>BE: server-side context enrichment (tenant-{slug} namespace)

    BE->>DB: INSERT issues (target=PROJECT, project_id, reporter_user_id, reporter_agent=USER, ...)

    BE-->>-FE: 201 Created { id, url, deduplicated: false }

    FE->>-User: zobrazí toast "Issue nahlášen (#ID)" s odkazem

Flow 2 — MARA reportuje PROJECT issue z konverzace

sequenceDiagram
    actor Mara

    Mara->>Mara: detekuje bug v user-app (target=PROJECT dle decision tree krok 1a)
    Mara->>Mara: dedup check: search_issues(target=PROJECT, query={symptom})

    Note over Mara: Mara NESPECIFIKUJE project — sidecar tool<br/>resolvuje project_id z ctx (předaný do createIssueTrackingMcpServer(ctx))

    Mara->>+BE: tool: report_issue(target="PROJECT", kind, title, description, severity?)
    Note over BE: POST /api/issues<br/>Authorization: Bearer {agentJWT}<br/>project_id: number (doplnil sidecar z ctx)

    BE->>BE: parseReporterAgent() — MARA whitelist OK
    BE->>BE: validuj request (target=PROJECT → project_id required as int)

    alt target=PROJECT a project_id null
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_ID_REQUIRED)
    end

    BE->>DB: SELECT projects WHERE id=project_id AND tenant_id=<z JWT>
    alt projekt neexistuje
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_NOT_FOUND)
    end
    alt projekt patří jinému tenantu
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_NOT_OWNED)
    end

    BE->>BE: server-side context enrichment (tenant-{slug} namespace)

    BE->>DB: INSERT issues (target=PROJECT, project_id=<z requestu>, reporter_agent=MARA, ...)

    BE-->>-Mara: 201 Created { id, url, deduplicated: false }

    Mara-->>User: "Narazila jsem na bug v naší aplikaci. Zaznamenala jsem ho jako TODO issue [#ID](url). Opravím ho v dalším kroku."

Tool API (Mara)

report_issue({
  target: "PLATFORM" | "PROJECT",
  kind: "BUG" | "FEATURE",
  title: string,               // krátký popis, max 80 chars
  description: string,         // markdown, full kontext
  severity?: "LOW" | "MEDIUM" | "HIGH"   // default MEDIUM; CRITICAL nikdy Mara
}): {
  id: string,                  // UUID issue
  url: string,                 // https://talkide.app link
  deduplicated: boolean,
  comment_id?: string
}

Žádný project_slug / project_id param. Sidecar (IssueTrackingTools.ts) zná aktivní projekt z ctx argumentu předaného do createIssueTrackingMcpServer(ctx). Při target=PROJECT sidecar resolvuje project_id: number z ctx a přidává ho do POST requestu na BE — Mara o tom neví.

Server enriched (nepředává Mara): tenant_slug, reporter_user_id, reporter_agent=MARA, session_id, context_jsonb. Sidecar enriched (doplňuje sidecar, ne Mara): project_id.

Mara vždy nejdříve volá search_issues(target=PROJECT) — teprve pokud nenajde duplikát, volá report_issue. Viz UC-09002 a UC-09001 — Mara decision tree.


REST API

POST /api/issues

Autentizace: User JWT nebo Agent JWT (Mara). reporter_agent v requestu musí být v whitelist MARA | USER — jiný agent → 403.

Request (USER flow):

{
  "target": "PROJECT",
  "project_id": 12345,
  "reporter_agent": "USER",
  "kind": "BUG",
  "title": "Přihlašovací formulář nepřijímá speciální znaky",
  "description": "Po zadání hesla se speciálními znaky (např. `!@#**User (ze Workspace tabu) nebo Mara (z konverzace přes tool) nahlásí issue spojený s konkrétním projektem (`target=PROJECT`). Oba reporteři zapisují stejný datový shape — liší se zdrojem `project_id` a hodnotou `reporter_agent`. USER posílá `project_id` explicitně z FE route; Mara `project_id` nepředává vůbec — sidecar tool ho resolvuje z ctx automaticky.**

* Phase 3 rozšiřuje Issue API o `target=PROJECT`. Schema bylo připraveno již v Phase 1 (`0019-create-issues-tables.xml`), ale FK constraint `issues.project_id → projects.id` byl omylem vynechán — doplňuje ho nová migrace `0020-add-project-fk-to-issues.xml` (immutable po deploymentu na prod).
* Dva reporteři: **USER** (Studio uživatel kliká v UI) a **MARA** (tool call z konverzace). `reporter_agent` v requestu rozlišuje oba. Phase 1 whitelist `MARA only` je v #91 rozšířen na `MARA | USER`.
* `severity = CRITICAL` Mara nikdy nenastaví — výhradně admin při triage (UC-09004). USER reportér taktéž nemůže nastavit CRITICAL.
* Cascade delete: smazání projektu kaskádově smaže všechny jeho PROJECT issues přes DB FK `ON DELETE CASCADE`. `ProjectCascadeDeleter` žádnou logiku nemění — FK zajistí vše.
* Context enrichment: stejný mechanismus jako UC-09001 (K8s events, pod logy) — pro `target=PROJECT` se dotazuje pouze `tenant-<slug>` namespace (ne `talkide` namespace). Detaily viz [UC-09001 — Auto-context enrichment](uc-09001_report-platform-issue#auto-context-enrichment-server-side).

---

## DB Migration — Phase 3 FK constraint

Nový soubor: `src/main/resources/db/changelog/changes/0020-add-project-fk-to-issues.xml`

**NEEDITUJ existující soubor 0019.** Toto je nový append-only changeset.

```xml
<changeSet id="0020-add-project-fk-to-issues" author="system">
    <addForeignKeyConstraint
        baseTableName="issues"
        baseColumnNames="project_id"
        constraintName="fk_issues_project"
        referencedTableName="projects"
        referencedColumnNames="id"
        onDelete="CASCADE"/>
</changeSet>

project_id zůstává nullable (Phase 1 PLATFORM issues nemají projekt — migrace funguje pro obě prostředí).


Flow 1 — USER reportuje PROJECT issue z Workspace tabu

sequenceDiagram
    actor User

    User->>+FE: klikne "Nahlásit problém" ve Workspace tabu

    FE->>FE: otevře modal s formulářem (kind, title, description, severity)

    User->>FE: vyplní formulář a odešle

    FE->>FE: validuje formulář (kind not null, title not blank max 80, description not blank)
    alt formulář nevalidní
        FE-->>User: zobrazí chybové hlášky u polí
    end

    FE->>+BE: POST /api/issues <br> IssueCreateRequest { target="PROJECT", project_id={current}, reporter_agent="USER", kind, title, description, severity? }
    Note over FE,BE: Authorization: Bearer {userJWT}

    BE->>BE: parseReporterAgent() — whitelist check: MARA | USER
    BE->>BE: validuj request (target, kind, title max 80, severity != CRITICAL)

    alt target=PROJECT a project_id null
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_ID_REQUIRED)
    end

    BE->>DB: SELECT projects WHERE id=project_id AND tenant_id = <z JWT>
    alt projekt neexistuje
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_NOT_FOUND)
    end
    alt projekt patří jinému tenantu
        BE-->>FE: 400 Bad Request (ISSUE_PROJECT_NOT_OWNED)
    end

    BE->>BE: server-side context enrichment (tenant-{slug} namespace)

    BE->>DB: INSERT issues (target=PROJECT, project_id, reporter_user_id, reporter_agent=USER, ...)

    BE-->>-FE: 201 Created { id, url, deduplicated: false }

    FE->>-User: zobrazí toast "Issue nahlášen (#ID)" s odkazem

Flow 2 — MARA reportuje PROJECT issue z konverzace

sequenceDiagram
    actor Mara

    Mara->>Mara: detekuje bug v user-app (target=PROJECT dle decision tree krok 1a)
    Mara->>Mara: dedup check: search_issues(target=PROJECT, query={symptom})

    Note over Mara: Mara NESPECIFIKUJE project — sidecar tool<br/>resolvuje project_id z ctx (předaný do createIssueTrackingMcpServer(ctx))

    Mara->>+BE: tool: report_issue(target="PROJECT", kind, title, description, severity?)
    Note over BE: POST /api/issues<br/>Authorization: Bearer {agentJWT}<br/>project_id: number (doplnil sidecar z ctx)

    BE->>BE: parseReporterAgent() — MARA whitelist OK
    BE->>BE: validuj request (target=PROJECT → project_id required as int)

    alt target=PROJECT a project_id null
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_ID_REQUIRED)
    end

    BE->>DB: SELECT projects WHERE id=project_id AND tenant_id=<z JWT>
    alt projekt neexistuje
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_NOT_FOUND)
    end
    alt projekt patří jinému tenantu
        BE-->>Mara: 400 Bad Request (ISSUE_PROJECT_NOT_OWNED)
    end

    BE->>BE: server-side context enrichment (tenant-{slug} namespace)

    BE->>DB: INSERT issues (target=PROJECT, project_id=<z requestu>, reporter_agent=MARA, ...)

    BE-->>-Mara: 201 Created { id, url, deduplicated: false }

    Mara-->>User: "Narazila jsem na bug v naší aplikaci. Zaznamenala jsem ho jako TODO issue [#ID](url). Opravím ho v dalším kroku."

Tool API (Mara)

report_issue({
  target: "PLATFORM" | "PROJECT",
  kind: "BUG" | "FEATURE",
  title: string,               // krátký popis, max 80 chars
  description: string,         // markdown, full kontext
  severity?: "LOW" | "MEDIUM" | "HIGH"   // default MEDIUM; CRITICAL nikdy Mara
}): {
  id: string,                  // UUID issue
  url: string,                 // https://talkide.app link
  deduplicated: boolean,
  comment_id?: string
}

Žádný project_slug / project_id param. Sidecar (IssueTrackingTools.ts) zná aktivní projekt z ctx argumentu předaného do createIssueTrackingMcpServer(ctx). Při target=PROJECT sidecar resolvuje project_id: number z ctx a přidává ho do POST requestu na BE — Mara o tom neví.

Server enriched (nepředává Mara): tenant_slug, reporter_user_id, reporter_agent=MARA, session_id, context_jsonb. Sidecar enriched (doplňuje sidecar, ne Mara): project_id.

Mara vždy nejdříve volá search_issues(target=PROJECT) — teprve pokud nenajde duplikát, volá report_issue. Viz UC-09002 a UC-09001 — Mara decision tree.


REST API

POST /api/issues

Autentizace: User JWT nebo Agent JWT (Mara). reporter_agent v requestu musí být v whitelist MARA | USER — jiný agent → 403.

Request (USER flow): ) formulář zobrazí prázdnou error message bez textu.”, “severity”: “MEDIUM” }


**Request (MARA flow — sidecar doplní project_id z ctx, Mara ho nespecifikuje):**
```json
{
  "target": "PROJECT",
  "project_id": 42,
  "reporter_agent": "MARA",
  "kind": "BUG",
  "title": "SQL query vrací duplicitní řádky při JOIN bez DISTINCT",
  "description": "Metoda `UserRepository.findActiveUsers()` vrací duplicitní záznamy kvůli chybějícímu DISTINCT v JPA query.",
  "severity": "HIGH"
}

project_id v MARA requestu je integer resolvovaný sidecarem (IssueTrackingTools.ts) z ctx — Mara ho nikdy nevyplňuje manuálně.

201 Created (nový issue):

{
  "id": "660f9511-e3ac-42d5-b827-557766551234",
  "url": "https://talkide.app/studio/issues/660f9511-e3ac-42d5-b827-557766551234",
  "deduplicated": false
}

200 OK (dedup hit — stejný issue v posledních 10 minutách):

{
  "id": "660f9511-e3ac-42d5-b827-557766551234",
  "url": "https://talkide.app/studio/issues/660f9511-e3ac-42d5-b827-557766551234",
  "deduplicated": true,
  "comment_id": "771g0622-f4bd-53e6-c938-668877662345"
}

400 Bad Requesttarget=PROJECT bez project_id (sidecar ho vždy doplní pro MARA; USER flow musí poslat explicitně z FE route):

{
  "code": "ISSUE_PROJECT_ID_REQUIRED",
  "message": "project_id is required when target=PROJECT"
}

400 Bad Request — validace (title příliš dlouhý, neplatný kind, apod.):

{
  "code": "VALIDATION",
  "message": "title must not exceed 80 characters"
}

403 Forbiddenseverity=CRITICAL nebo nepovolený reporter_agent:

{
  "code": "FORBIDDEN",
  "message": "CRITICAL severity can only be set by admin during triage"
}

400 Bad Request — projekt neexistuje nebo patří jinému tenantu:

{
  "code": "ISSUE_PROJECT_NOT_FOUND",
  "message": "Project not found"
}
{
  "code": "ISSUE_PROJECT_NOT_OWNED",
  "message": "Project does not belong to the current tenant"
}

Cross-tenant attack prevention: oba kódy (ISSUE_PROJECT_NOT_FOUND i ISSUE_PROJECT_NOT_OWNED) vrací 400 Bad Request (ne 403/404), aby nešlo enumerovat existenci projektů cizích tenantů. BE záměrně nesděluje, zda projekt neexistuje nebo existuje, ale patří jinému tenantu.


Frontend

Workspace tab — “Nahlásit problém” modal

Trigger: Tlačítko “Nahlásit problém” v záhlaví Issues tabu v Workspace.

Formulář:

PoleTypPopis
KindselectBUG / FEATURE — required
Titletext inputKrátký popis, required
DescriptiontextareaMarkdown, required
SeverityselectLOW / MEDIUM / HIGH — optional, default MEDIUM

Po odeslání FE přidá do requestu target=PROJECT, project_id=<aktuální projekt z routy>, reporter_agent=USER.

Validations

FieldConstraintsSizePatternNote
kindnot_nullVýběr ze select; prázdná volba nepřípustná
titlenot_blank1 - 80Trim whitespace
descriptionnot_blankMarkdown; no size limit na FE
severitynullableDefault MEDIUM pokud nevybráno

Backend

Validations

FieldConstraintsSizePatternNote
targetnot_blank, in(PLATFORM, PROJECT)
project_idrequired pokud target=PROJECTintegerUSER: FE posílá z route; MARA: sidecar doplňuje z ctx; chybí → ISSUE_PROJECT_ID_REQUIRED
tenant ownershipprojekt musí patřit tenant_id z JWTISSUE_PROJECT_NOT_OWNED při cross-tenant pokusu
reporter_agentnot_blank, in(MARA, USER)Phase 3 whitelist; jiný agent → 403
kindnot_blank, in(BUG, FEATURE)INCIDENT → 400
titlenot_blank, max 80 chars1 - 80Trim whitespace
descriptionnot_blank
severitynullable, default MEDIUM, in(LOW, MEDIUM, HIGH)CRITICAL → 403

Test Cases

GIVENWHENTHEN
Přihlášený user, existující vlastní projektPOST /api/issues s target=PROJECT, project_id=<vlastní int>, reporter_agent=USER, kind=BUG, title, description201 Created; issue vytvořen s reporter_agent=USER, status=OPEN, severity=MEDIUM
Mara má platný agent JWT, sidecar doplnil project_id z ctxPOST /api/issues s target=PROJECT, project_id=<int z ctx>, reporter_agent=MARA, kind=BUG, title, description201 Created; issue vytvořen s reporter_agent=MARA
target=PROJECT ale chybí project_id (FE bug nebo sidecar selhal)POST /api/issues400 Bad Request, code=ISSUE_PROJECT_ID_REQUIRED
project_id odkazuje na projekt jiného tenantuPOST /api/issues s cizím project_id400 Bad Request, code=ISSUE_PROJECT_NOT_OWNED
project_id odkazuje na neexistující projektPOST /api/issues s neexistujícím project_id (integer, např. 99999)400 Bad Request, code=ISSUE_PROJECT_NOT_FOUND
reporter_agent=KAI (není v whitelist)POST /api/issues403 Forbidden
severity=CRITICAL (jakýkoliv reporter)POST /api/issues s severity=CRITICAL403 Forbidden, code=FORBIDDEN
title delší než 80 znakůPOST /api/issues400 Bad Request, code=VALIDATION
Projekt smazán (CASCADE)DELETE /api/projects/{id} → dotaz na issues WHERE project_id=idDB vrátí prázdný výsledek; issues smazány kaskádou přes FK ON DELETE CASCADE
Stejný (tenant_slug, PROJECT, title hash) v posledních 10 minPOST /api/issues se stejným titulem200 OK, deduplicated=true; přidán komentář; nový issue nevznikne

References


FEEDBACK

Zadání bylo výborně strukturované — dual reporter flow, error kódy i cascade behavior byly popsány konkrétně. Ambiguita project_slug="current" byla vyřešena: Mara žádný project_slug ani project_id nespecifikuje — sidecar (IssueTrackingTools.ts) resolvuje project_id: number z ctx argumentu předaného do createIssueTrackingMcpServer(ctx) a přidává ho do POST requestu transparentně. Tool schema proto neobsahuje project_slug ani project_id jako parametry pro Maru.


Was this page helpful?

Thanks for the feedback.