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-appis a replaceable browser-facing UI/BFF.reply-pilot-appcalls application state and business operations throughreply-pilot-beHTTP/JSON APIs.reply-pilot-appmust not own direct PostgreSQL, Gmail, OpenAI, Jira, CME/CmD, lead-import, activity/contact/task/company persistence, or durable app-state storage.reply-pilot-beowns DB access, integration orchestration, business workflows, and stable JSON contracts for the current Flask app and a future UI rewrite.reply-pilot-benow 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-gmailowns 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-beuses internal HTTP and is not the durable cache owner ingmail_servicemode.
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.pyis route-map-like and delegates to workflow modules. reply-pilot-appno longer has direct PostgreSQL-capable production store code, no longer builds a productiontask_storeextension, no longer exposesREPLY_PILOT_DB_*runtime config, and no longer depends onpsycopg.- 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 callsreply-pilot-search. reply-pilot-behas 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
ActivityEmailImportRepositoryfor base activity-email persistence:activity,activity_email,activity_participant,activity_email_attachment, andactivity_email_link. It also ports deterministic incoming-email extracted facts/evidence intoparty_feed_fact,party_supplier_identifier_fact,party_requirement_evidence, andparty_requirement_eval_queue, and transitions already linkedemail_thread_replyJira tasks toDrafting Replyfor newly imported incoming external replies. For unlinked incoming supplier replies with one resolved supplier company, Java also creates the automaticEmail Thread Replytask 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 Taskissues: sync JQL includes the configured issue type, Jira readscustomfield_10269by default, local cache stores the delegated task type intask_jira_reply_pilot_task.task_type_value, task APIs exposetask_type_value, and default task list reads hideDonedelegated 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_taskcan read only that linked company and active task; this does not affect mutation authorization, unassigned delegated tasks, unrelated companies, orDonedelegated tasks. - Backend task mutation endpoints now support delegated
Reply Pilot Taskresolve/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 keepsIn Progress; close is limited to the requester and transitions the Jira task toDonewithout changing assignee. - App task views now support delegated
Reply Pilot Taskissues. The app keepsreply_pilot_taskas its own Jira work type, carries backend-providedtask_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 Taskissues, includingtask_type_value, and includes active delegated tasks in assigned-scope task search for the cached assignee.Donedelegated tasks stay indexed for explicit search through normal company scope orcompany.view_all, but they do not grant the active delegated-assignee search exception. reply-pilot-beDocker/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 underdata/accounts/<GMAIL_CACHE_ACCOUNT_ID>/emails/, exposes internal cache endpoints for BE reads, computesGET /api/reports/email-weekly-counts?weeks=104&include_current=truefrom that Gmail-owned cache/runtime data, andreply-pilot-jira-reportsdefaults to calling the Gmail module endpoint. InEMAIL_SYNC_BACKEND=gmail_servicemode,reply-pilot-bereads 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:sentpass withskip_cached_threads=true, then imports the unified cache into the activity model. Operators can trigger the same sent-only pass throughPOST /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, andoffline_access. Existing grants need a new consent before they receive the added read scope. After re-consent,RP-3509confirmed the human actor as creator, reporter, comment and attachment author, and change-history author for unassign, reassignment, summary, description, and transition toDone. 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 Taskfor delegated tasks. The Jira admin edit URL ishttps://internet-handel.atlassian.net/secure/admin/EditIssueType!default.jspa?id=10206. - Delegated Jira tasks use Jira status values
In ProgressandDone.In Progresstasks remain visible/actionable;Donetasks 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
taskTypewith dropdown values. The Jira custom field id iscustomfield_10269; the confirmed option labels areGeneral,Meeting organization, andRegistration (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
Donedelegated 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
Donewithout 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
428with codejira_oauth_required; the app keeps entered form data and offers the existing Jira connection flow. - The effective
app_user_idcomes from backend-authenticatedUserAuthorization, 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
JiraIssueClientimplementation. ReuseJiraOAuthAccessProvider, the existing token refresh behavior, and the delegatedapi.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
JiraIssueClientoperation 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
JiraOAuthRequiredExceptionto HTTP428withjira_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
UserAuthorizationand 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-bodyoperator_user_idremains compatibility data and cannot select credentials. Missing or invalid OAuth keeps the shared428contract, 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_TOKENremain 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
historyMetadatamay 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.mddocs/refactoring-context.mddocs/refactoring-tracker.mddocs/jira-oauth.mddocs/module-boundaries.mddocs/web-app.mddocs/backend.mddocs/integrace.mddocs/architecture-reporting.mddocs/container-runtime-contract.mddocs/remote-server.mddocs/database.mddocs/activity-model.mddocs/permissions.md- source code and tests in
reply-pilot-app/andreply-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/, andreply-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.mddocs/project-layout.mddocs/auth-contract.mddocs/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.pymodules route-map-like where project rules require that shape; this currently applies to browser-facing Flask modules such asreply-pilot-app, not toreply-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-appas BFF/UI. Integration orchestration, persistence, and reusable business operations should live inreply-pilot-be. - Do not add direct
reply-pilot-app->reply-pilot-searchruntime calls. - For Docker, runtime, remote server, DB schema, activity/contact model, or UI
changes, read the matching project docs listed in
AGENTS.mdbefore 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_assignedorcompany.view_allsemantics 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
AuthorizationServiceand pass the authenticatedapp_user_idexplicitly 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_requiredas 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/testsmvn -f reply-pilot-be/pom.xml verifyfor backend migration slicesscript/check-architecture.shmkdocs build --strictfor documentation changes- DB migration validation from
AGENTS.mdonly when DB schema changes rp <diagram.plantuml>for changed PlantUML diagrams- focused delegated Jira tests in
HttpJiraIssueClientTest,JiraOAuthServiceTest,TaskMutationServiceTest,TaskMutationControllerTest,TaskAttachmentServiceTest,TaskAttachmentControllerTest,LeadImportServiceTest,LeadImportControllerTest, andJiraProxyControllerTest - focused app tests for OAuth-required error mapping, form preservation, Jira connection links, task mutations, attachment flows, and lead import