Reply Pilot Refactoring Context

This document contains durable context for active Reply Pilot refactoring targets. It follows the external refactoring-plan skill as a workflow guardrail. Project truth remains in AGENTS.md, local docs, source code, and tests.

Work item status lives in docs/refactoring-tracker.md.

Current Target

Target boundary:

  • reply-pilot-app is a replaceable browser-facing UI/BFF.
  • reply-pilot-app calls application state and business operations through reply-pilot-be HTTP/JSON APIs.
  • reply-pilot-app must not own direct PostgreSQL, Gmail, OpenAI, Jira, CME/CmD, lead-import, activity/contact/task/company persistence, or durable app-state storage.
  • reply-pilot-be owns DB access, integration orchestration, business workflows, and stable JSON contracts for the current Flask app and a future UI rewrite.
  • reply-pilot-be now runs as a Java 21 Spring Boot/Maven REST/JSON backend. The legacy Flask backend package, Python backend tests, Python-only dependencies, and obsolete Python backend utilities have been removed.
  • Search follows the same replaceable UI boundary: reply-pilot-app -> reply-pilot-be -> reply-pilot-search.
  • Email runtime data follows the Gmail ownership boundary: Google Gmail API/mailbox -> reply-pilot-gmail -> reply-pilot-be. reply-pilot-gmail owns OAuth material, Pub/Sub credentials, watch/rate-limit state, Gmail API calls, the account-scoped mailbox cache, and /api/reports/email-weekly-counts. It stores inbox and sent-only threads in the same account cache. reply-pilot-be uses internal HTTP and is not the durable cache owner in gmail_service mode.

Current verified state as of 2026-06-13:

  • Standard non-test app startup requires BACKEND_API_BASE_URL.
  • App prompts, reply drafts, app user profile, app configuration, Jira assignee mapping, simple-auth nonce persistence, read models, and planned mutation slices are backend-backed in standard runtime.
  • App views.py is route-map-like and delegates to workflow modules.
  • reply-pilot-app no longer has direct PostgreSQL-capable production store code, no longer builds a production task_store extension, no longer exposes REPLY_PILOT_DB_* runtime config, and no longer depends on psycopg.
  • Test-only in-memory stores remain in the app package to preserve app workflow tests without backend or database services.
  • App-local Gmail/OpenAI implementation fallback was removed from standard runtime and app package code; Gmail/OpenAI calls are backend-owned.
  • Direct app-to-search runtime access was removed; app search UI calls backend /api/search, and backend calls reply-pilot-search.
  • reply-pilot-be has a Java Spring Boot/Maven implementation track with /healthz, metadata, AI prompt, reply draft, app user profile, app configuration, Jira assignee, simple-auth nonce, and search proxy endpoint coverage, plus company/person and activity/contact/email-thread read-model endpoint coverage, plus task read-model endpoint coverage, plus company create/update/merge/visibility and requirement-review mutation endpoint coverage, plus person, party contact, company identifier, and company-person unlink mutation endpoint coverage, plus task mutation, Jira proxy/task-sync, AI draft generation, inbox/email/attachment read, mailbox watch/import, email draft/send, lead import, and worker job endpoint coverage, validated against the current backend contract. Java now has concrete JDBC worker beans for CME company sync, AI requirement classification, requirement aggregation, requirement monitoring/report, and historical requirement backfill.
  • Backend tests are maintained in the Java/Maven/JUnit 5 test suite under reply-pilot-be/src/test/java. There is no active Python backend test suite.
  • Java has a production JDBC ActivityEmailImportRepository for base activity-email persistence: activity, activity_email, activity_participant, activity_email_attachment, and activity_email_link. It also ports deterministic incoming-email extracted facts/evidence into party_feed_fact, party_supplier_identifier_fact, party_requirement_evidence, and party_requirement_eval_queue, and transitions already linked email_thread_reply Jira tasks to Drafting Reply for newly imported incoming external replies. For unlinked incoming supplier replies with one resolved supplier company, Java also creates the automatic Email Thread Reply task after successful email import commit and uses the documented default reply-assignee priority.
  • Java lead import now persists sent outbound email activity through the caller-owned JDBC connection inside JdbcLeadImportRepository.markImported, so the sent email activity, recipient contact link, lead item update, activity note, and batch progress refresh share one DB transaction. Gmail send and Jira issue creation remain external side effects outside DB rollback semantics.
  • Backend Jira task sync/read models now include delegated Reply Pilot Task issues: sync JQL includes the configured issue type, Jira reads customfield_10269 by default, local cache stores the delegated task type in task_jira_reply_pilot_task.task_type_value, task APIs expose task_type_value, and default task list reads hide Done delegated tasks unless the request explicitly filters by status or Jira key.
  • Backend read-model company/task endpoints now apply the delegated-task read-only visibility exception: an app user who is the cached Jira assignee of an active reply_pilot_task can read only that linked company and active task; this does not affect mutation authorization, unassigned delegated tasks, unrelated companies, or Done delegated tasks.
  • Backend task mutation endpoints now support delegated Reply Pilot Task resolve/close actions. Resolve is limited to the cached Jira assignee, stores the entered text in Jira description, reassigns the task to the locally stored requester, and keeps In Progress; close is limited to the requester and transitions the Jira task to Done without changing assignee.
  • App task views now support delegated Reply Pilot Task issues. The app keeps reply_pilot_task as its own Jira work type, carries backend-provided task_type_value, renders delegated task detail/resolve pages with linked company, Jira description, and read-only Jira comments, and calls backend resolve/close mutations instead of Jira directly.
  • Search now indexes delegated Reply Pilot Task issues, including task_type_value, and includes active delegated tasks in assigned-scope task search for the cached assignee. Done delegated tasks stay indexed for explicit search through normal company scope or company.view_all, but they do not grant the active delegated-assignee search exception.
  • reply-pilot-be Docker/Compose runtime is configured to build and run the Java 21 Spring Boot jar. Docker daemon validation is tracked in item 26.1. Python Flask backend route-map cleanup and legacy Python backend code removal are no longer active migration targets.
  • Current verified state as of 2026-07-10: Gmail mailbox data is owned by reply-pilot-gmail. The Gmail module writes account-scoped cache files under data/accounts/<GMAIL_CACHE_ACCOUNT_ID>/emails/, exposes internal cache endpoints for BE reads, computes GET /api/reports/email-weekly-counts?weeks=104&include_current=true from that Gmail-owned cache/runtime data, and reply-pilot-jira-reports defaults to calling the Gmail module endpoint. In EMAIL_SYNC_BACKEND=gmail_service mode, reply-pilot-be reads the mailbox cache, history cursor, attachment metadata, and attachment bytes only through Gmail-module HTTP.
  • Current verified state as of 2026-07-10: the Gmail cache can be extended with sent-only threads without adding a second cache or worker. Full sync performs an inbox pass followed by an in:sent pass with skip_cached_threads=true, then imports the unified cache into the activity model. Operators can trigger the same sent-only pass through POST /api/mailbox/sync/sent.
  • Current verified state as of 2026-07-30: all production Jira requests caused by an authenticated browser action use the same app user's delegated 3LO access. The exhaustive call-site audit leaves only Jira task sync, automatic incoming-email Jira operations, and Jira reports on technical credentials. The authorization request now includes read:jira-user, read:jira-work, write:jira-work, and offline_access. Existing grants need a new consent before they receive the added read scope. After re-consent, RP-3509 confirmed the human actor as creator, reporter, comment and attachment author, and change-history author for unassign, reassignment, summary, description, and transition to Done. A no-header Jira task-sync smoke also confirmed that the technical background path remains operational.

Planned delegated Jira task workflow target:

  • Add support for Jira issue type Reply Pilot Task for delegated tasks. The Jira admin edit URL is https://internet-handel.atlassian.net/secure/admin/EditIssueType!default.jspa?id=10206.
  • Delegated Jira tasks use Jira status values In Progress and Done. In Progress tasks remain visible/actionable; Done tasks are hidden from the default task overview only. They may still be reachable through other explicit task lookup paths when normal authorization allows it.
  • Delegated Jira tasks carry a Jira custom field taskType with dropdown values. The Jira custom field id is customfield_10269; the confirmed option labels are General, Meeting organization, and Registration (B2B).
  • Delegated task creation can choose an assignee immediately. Jira remains the source of truth for assignee, status, task type, summary, description, and comments; Reply Pilot stores only the local cache and workflow links it needs.
  • Each delegated task links to one company. Jira description must contain a human link to the company, but Reply Pilot's database value is the source of truth for workflow and authorization. A user assigned to an active delegated task may read that linked company even when normal company scope would not allow it. That exception must not grant access to any other company, to unassigned tasks, or to companies linked only to Done delegated tasks.
  • The task requester is the app user who created the delegated task in Reply Pilot. Jira does not currently hold this value, so Reply Pilot must store the requester in its own task cache/model.
  • Delegated tasks appear in the existing task views like other Jira tasks.
  • The assignee can open a resolve page from the task, enter text, and send the task back to the requester while keeping Jira status In Progress. The entered text is stored as part of the Jira ticket description.
  • The requester can close the task, moving Jira status to Done without changing the current assignee.
  • The delegated task detail/resolve page shows the linked company and existing Jira comments. Comments remain read-only in Reply Pilot and should use the existing Jira comment display component already used by the current task UI.

Planned user-delegated Jira execution target as of 2026-07-30:

  • Every Jira API call caused by an authenticated user's browser action must use that app user's Atlassian 3LO access token. This includes issue creation, update, assignment, unassignment, transition, comments, attachments, Jira user lookup, issue/status/comment reads, searches, and explicit refreshes.
  • An interactive Jira operation must never silently fall back to the technical Jira account. Missing, expired, invalid, undecryptable, or unusable delegated credentials return HTTP 428 with code jira_oauth_required; the app keeps entered form data and offers the existing Jira connection flow.
  • The effective app_user_id comes from backend-authenticated UserAuthorization, never from an assignee, requester, operator, or arbitrary user id supplied in a request payload.
  • Every Jira call inside one interactive workflow uses the same authenticated actor. A delegated comment followed by assignment or transition must not mix the user token and technical-account credentials.
  • Keep one JiraIssueClient implementation. Reuse JiraOAuthAccessProvider, the existing token refresh behavior, and the delegated api.atlassian.com/ex/jira/{cloudId} request path; do not add a second parallel Jira client or ambient thread-local user context.
  • Transport item 30.1 is implemented: every supported JiraIssueClient operation has an explicit actor-aware Bearer variant, including multipart attachment upload and binary download, while no-actor variants retain the technical-account path. Items 30.3 through 30.7 use those variants for all browser-triggered Jira call sites.
  • Error-contract item 30.2 is implemented: one backend controller advice maps every JiraOAuthRequiredException to HTTP 428 with jira_oauth_required; Jira-related app HTTP clients expose one shared recoverable exception, and affected forms preserve submitted values while offering the existing Jira connection flow.
  • Task-workflow item 30.3 is implemented: Supplier Onboarding reply, Reply Pilot resolve/close, Email Thread Reply update/close, and move-to-waiting use one backend-authenticated actor for every Jira call in the workflow. Payload assignee/requester ids do not select credentials, Supplier reply author identity comes from the authenticated app user, and existing partial-success warnings remain in place after irreversible comment/email side effects.
  • Task/company mutation item 30.4 is implemented: company reassignment, Supplier Onboarding creation/edit, Reply Pilot Task creation, task reassignment, and manual Email Thread Reply task creation use the backend-authenticated app user's OAuth token for every Jira lookup, read, create, update, and assignment in the workflow. Assignee and requester ids remain business data and do not select credentials; the app handles the shared OAuth-required contract and offers the existing Jira connection flow.
  • Attachment item 30.5 is implemented: attachment settings/listing, streamed upload, same-filename replacement deletion, rollback deletion, metadata verification, and binary download all use the backend-authenticated app user's OAuth token. One replacement attempt keeps the same actor throughout, OAuth failure never falls back to the technical account, and the app offers the existing Jira connection flow for list, upload, and download failures.
  • Lead-import/generic-mutation item 30.6 is implemented: lead import derives the operator id from backend-authenticated UserAuthorization and uses that actor for Jira user lookup, create, transition, and update. Generic Jira create/update/transition/comment/unassign routes require the same authenticated context and pass its actor to Jira; request-body operator_user_id remains compatibility data and cannot select credentials. Missing or invalid OAuth keeps the shared 428 contract, and the lead-import page preserves submitted form values while offering the Jira connection flow.
  • Remaining-read item 30.7 is implemented: Jira proxy myself, user lookup, issue/search/assigned/stale/detail/transition/comment reads and explicit task refresh require the backend-authenticated app user and use that actor's OAuth token. App read workflows retain available local data when Jira requires OAuth and offer one deduplicated Jira connection prompt; the global task badge omits its remote count instead of failing the page.
  • Existing technical-account Jira methods remain available only for operations without a human actor: Jira task synchronization, automatic incoming-email task creation or transition, and Jira reporting reads. These callers form an explicit allowlist that was re-audited and accepted in items 30.7 and 30.8.
  • JIRA_EMAIL/JIRA_API_TOKEN remain configured while the background allowlist exists. The migration does not imply removing the technical integration.
  • Reuse the encrypted credential envelope already stored in app_user_jira_profile.jira_oauth_credentials_ciphertext; no additional DB schema is expected for this migration.
  • Existing Reply Pilot transition historyMetadata may remain as supplemental workflow/reporting metadata, but it is not user impersonation. The Bearer token must determine the real Jira actor.
  • Live Jira acceptance must verify creator, reporter, comment author, attachment author, and change-history actor separately. Do not claim reporter attribution from OAuth authentication alone until Jira behavior is observed.

Source Of Truth

Use these project docs and code areas before planning or executing refactor items:

  • AGENTS.md
  • docs/refactoring-context.md
  • docs/refactoring-tracker.md
  • docs/jira-oauth.md
  • docs/module-boundaries.md
  • docs/web-app.md
  • docs/backend.md
  • docs/integrace.md
  • docs/architecture-reporting.md
  • docs/container-runtime-contract.md
  • docs/remote-server.md
  • docs/database.md
  • docs/activity-model.md
  • docs/permissions.md
  • source code and tests in reply-pilot-app/ and reply-pilot-be/
  • task and Jira workflow code in reply-pilot-app/reply_pilot_app/workflows/, reply-pilot-app/reply_pilot_app/jira_work_types.py, reply-pilot-be/src/main/java/cz/replypilot/be/controller/, reply-pilot-be/src/main/java/cz/replypilot/be/service/, and reply-pilot-be/src/main/java/cz/replypilot/be/adapter/db/
  • Jira integration contracts and implementation in reply-pilot-be/src/main/java/cz/replypilot/be/domain/JiraIssueClient.java, reply-pilot-be/src/main/java/cz/replypilot/be/adapter/http/HttpJiraIssueClient.java, reply-pilot-be/src/main/java/cz/replypilot/be/service/JiraOAuthService.java, and their focused backend/app tests

Expected generic-skill docs missing in this repository:

  • docs/development/code-quality-charter.md
  • docs/project-layout.md
  • docs/auth-contract.md
  • docs/data-storage.md

Do not invent their contents. Use the existing project docs above until those documents are intentionally added.

Guardrails

  • Preserve existing app routes, templates, redirects, public API contracts, and user-visible behavior unless a tracker item explicitly changes them.
  • Do not deploy, commit, edit encrypted secrets, or change DB schema unless a tracker item explicitly requires it.
  • Keep Flask views.py modules route-map-like where project rules require that shape; this currently applies to browser-facing Flask modules such as reply-pilot-app, not to reply-pilot-be.
  • Do not reintroduce a Python backend implementation or Python backend test harness for reply-pilot-be; backend behavior belongs in Java code and Maven/JUnit validation.
  • Keep reply-pilot-app as BFF/UI. Integration orchestration, persistence, and reusable business operations should live in reply-pilot-be.
  • Do not add direct reply-pilot-app -> reply-pilot-search runtime calls.
  • For Docker, runtime, remote server, DB schema, activity/contact model, or UI changes, read the matching project docs listed in AGENTS.md before editing.
  • Delegated task company visibility must be a narrow task-derived exception: active Jira assignment to the current user may grant read access to only the linked company for that active delegated task. Do not broaden company.view_assigned or company.view_all semantics to all task-linked companies.
  • Keep Jira as the source of truth for delegated task status, assignee, comments, description, and custom fields. Backend code owns Jira calls and local cache updates; the Flask app must call backend APIs instead of Jira directly.
  • Keep Reply Pilot as the source of truth for delegated-task requester and the linked company id used by workflow and authorization, even though the Jira description also carries a human-readable company link.
  • Never select delegated Jira credentials from a request-body user id. Resolve the actor through AuthorizationService and pass the authenticated app_user_id explicitly through controller, service, and Jira client calls.
  • Do not silently retry an interactive Jira call with technical credentials after delegated OAuth fails. Preserve HTTP 428 jira_oauth_required as the recoverable UI contract.
  • Keep technical-account Jira use limited to the documented background allowlist. Do not globally switch the default Jira request helper to Bearer authentication because background operations have no user token.
  • Any new permission, changed permission semantics, seeded role change, DB schema change, or UI workflow change must update the matching project docs in the same implementation slice.

Validation Gates

  • python3 -m pytest reply-pilot-app/tests
  • mvn -f reply-pilot-be/pom.xml verify for backend migration slices
  • script/check-architecture.sh
  • mkdocs build --strict for documentation changes
  • DB migration validation from AGENTS.md only when DB schema changes
  • rp <diagram.plantuml> for changed PlantUML diagrams
  • focused delegated Jira tests in HttpJiraIssueClientTest, JiraOAuthServiceTest, TaskMutationServiceTest, TaskMutationControllerTest, TaskAttachmentServiceTest, TaskAttachmentControllerTest, LeadImportServiceTest, LeadImportControllerTest, and JiraProxyControllerTest
  • focused app tests for OAuth-required error mapping, form preservation, Jira connection links, task mutations, attachment flows, and lead import