Reply Pilot Refactoring Tracker

This is the active status tracker for the app/backend boundary refactor. Durable target context, guardrails, and validation gates live in docs/refactoring-context.md.

Legacy note: the old active tracker lived at docs/refactoring.md. That file is now only a compatibility pointer.

Open Items

To Solve

No unfinished implementation item is currently queued.

Regular Maintenance

  • [ ] M1. Keep the architecture report honest after boundary changes. Run python3 script/architecture-report.py --output reports/architecture-report.md only when implemented code changes alter the architecture baseline, then run script/check-architecture.sh.
  • [ ] M2. Keep this tracker current after each refactor item. Move completed non-maintenance items to ## Done (Archive), keep evidence, and update ## Unfinished Audit.
  • [ ] M3. Keep documentation validation green after docs changes: mkdocs build --strict.

Unfinished Audit

  • Jira task sync, automatic incoming-email Jira operations, and Jira reports intentionally remain on technical credentials because they have no authenticated human actor. JIRA_EMAIL/JIRA_API_TOKEN must remain configured while this allowlist exists.
  • No unfinished item 28 work remains after the follow-up Gmail mailbox cache boundary move. The earlier weekly-report-only move was partial because reply-pilot-be still owned the durable mailbox cache.
  • No separate sent-only Gmail cache or worker remains planned. Sent-only threads are handled as a second pass into the same Gmail-owned account cache.

Done (Archive)

  • [x] 30.8 Close the delegated Jira migration with an exhaustive call-site audit, documentation, and live Jira acceptance.
  • Evidence: the production call-site audit classifies every browser-triggered Jira read/write as delegated with the backend-authenticated app user. The only technical-account allowlist is Jira task sync, automatic incoming-email Jira operations, and Jira reports.
  • Evidence: the OAuth authorization request includes read:jira-user, read:jira-work, write:jira-work, and offline_access. Create preserves an already returned Jira key if its follow-up enrichment read requires a new consent, preventing false 428 responses and duplicate retries.
  • Evidence: after re-consent, live status and /myself succeeded. RP-3509 was created through Reply Pilot and Jira recorded the same human user as creator, reporter, comment author, attachment author, unassign and reassignment author, summary/description update author, and transition author. The task finished in Done. Earlier invalid-format live testing returned 428 jira_oauth_required; a no-header background task-sync smoke succeeded through the technical no-actor path.
  • Evidence: Jira OAuth, backend, web-app, integration, module README, profile microcopy, refactoring context, and the PlantUML/PNG flow all describe the final boundary.
  • Validation: backend mvn -q verify, all 289 app tests, all 21 Jira reports tests, regenerated architecture report plus script/check-architecture.sh, strict MkDocs build, regenerated PlantUML PNG, and git diff --check passed.

  • [x] 30.7 Delegate all remaining user-facing Jira reads while preserving the background allowlist.

  • Evidence: Jira proxy myself, user lookup, issue/search/assigned/stale/ detail/transition/comment read routes now require the backend-authenticated app user and pass that actor through JiraProxyService to the delegated Jira client. Missing, malformed, or unusable OAuth credentials use the shared HTTP 428 jira_oauth_required contract and never retry with the technical account.
  • Evidence: explicit task refresh passes the actor returned by the existing task authorization check into the Jira issue read. Direct and delegated task access therefore use the authenticated caller's OAuth token, not a task owner or request-body identity.
  • Evidence: task overview/detail, related-task, comment, transition, and Supplier Onboarding read workflows preserve available local data and offer the existing Jira connection action when Jira returns 428. One request emits at most one connection prompt, and a missing token suppresses the global remote task badge instead of turning unrelated pages into errors.
  • Evidence: Jira task synchronization, automatic incoming-email Jira operations, and Jira reports remain the explicit no-human-actor technical allowlist. Regression coverage asserts that task sync and incoming-email automation do not acquire an arbitrary app-user actor.
  • Validation: focused backend Jira proxy/task refresh/service tests and focused app OAuth/read UI tests passed; the full Flask suite passed with 289 tests, the Jira reports suite passed with 21 tests, full backend mvn -q verify, mkdocs build --strict, and git diff --check passed. script/check-architecture.sh still reports the pre-existing stale reports/architecture-report.md baseline documented in the unfinished audit; this slice did not regenerate the broader dirty-tree report.

  • [x] 30.6 Secure and delegate lead-import and generic Jira mutation APIs.

  • Evidence: the lead-import action controller requires authenticated UserAuthorization, ignores request-body operator_user_id as identity, and passes the authenticated appUserId as operator and Jira actor. Lead-import Jira user lookup, issue creation, optional transition, and final description update use that same actor. A delegated OAuth failure is rethrown to the shared 428 jira_oauth_required handler after preserving the existing item-error recording and external-side-effect ordering.
  • Evidence: generic Jira create, update, transition, comment, and unassign routes now require the existing trusted app-user context and use its actor for every Jira write. A repository-wide production string-literal audit found no consumers outside reply-pilot-app; consumers outside this repository cannot be proven absent and must now send the trusted authentication context. The existing app HTTP clients already attach that header.
  • Evidence: the lead-import app workflow handles the shared OAuth exception, renders HTTP 428, keeps the submitted operator note and email fields, and offers the existing Jira connection flow. No new client, credential store, or technical-account fallback was added.
  • Validation: focused LeadImportServiceTest,LeadImportControllerTest,JiraProxyControllerTest passed with 13 tests; the controller test wires the real JiraProxyService because no standalone JiraProxyServiceTest exists. Focused app OAuth/client/lead-import coverage passed with 11 tests; full mvn -q verify, the full Flask suite with 286 tests, mkdocs build --strict, and git diff --check passed. script/check-architecture.sh still reports the pre-existing stale reports/architecture-report.md baseline documented in the unfinished audit; this slice did not regenerate the broader dirty-tree report.

  • [x] 30.5 Delegate Jira attachment operations.

  • Evidence: each existing attachment controller action retains its task authorization check and passes the resulting UserAuthorization.appUserId() to the attachment service. Settings, listing, streamed upload, same-filename replacement deletion, rollback deletion, download metadata verification, and binary content download use that actor; no delegated failure retries with technical credentials.
  • Evidence: replacement failures keep the previous filename behavior and attempt to remove the newly uploaded attachment with the same actor. OAuth failure during replacement preserves the shared HTTP 428 jira_oauth_required contract after the delegated rollback attempt. Existing size/type validation, task authorization, response headers, and multipart streaming remain unchanged.
  • Evidence: the Flask attachment client sends the authenticated actor only in the trusted backend header, including its streamed multipart request. Attachment list, upload, and download workflows handle the shared OAuth exception and offer the existing Jira connection flow instead of returning a 500.
  • Validation: focused backend TaskAttachmentServiceTest,TaskAttachmentControllerTest,HttpJiraIssueClientTest passed with 17 tests; focused app attachment/client/OAuth coverage passed with 11 tests; full mvn -q verify, full Flask suite with 284 tests, mkdocs build --strict, and git diff --check passed. script/check-architecture.sh still reports the pre-existing stale reports/architecture-report.md baseline documented in the unfinished audit; this slice did not regenerate the broader dirty-tree report.

  • [x] 30.4 Propagate the authenticated actor through the remaining user-triggered task/company mutations.

  • Evidence: the six existing task mutation controller actions retain their authorization checks and pass the resulting UserAuthorization.appUserId() separately from requester and assignee business data. Company reassignment, Supplier Onboarding creation/edit, Reply Pilot Task creation, task reassignment, and manual Email Thread Reply task creation use that actor for Jira user lookup, issue reads, creation, updates, and assignment.
  • Evidence: the Flask task mutation and email clients expose the shared jira_oauth_required error, and affected company, reassignment, task, and email workflows offer the existing Jira connection flow without changing their browser or backend routes. Supplier Onboarding and Reply Pilot Task post-create link failures preserve the existing partial-success warning instead of inviting duplicate issue creation.
  • Validation: focused backend TaskMutationServiceTest,TaskMutationControllerTest passed with 40 tests; focused app client/workflow coverage passed with 11 tests; full mvn -q verify, full Flask suite with 283 tests, mkdocs build --strict, and git diff --check passed. script/check-architecture.sh still reports the pre-existing stale reports/architecture-report.md baseline documented in the unfinished audit; this slice did not regenerate the broader dirty-tree report.

  • [x] 30.3 Delegate task workflows that already carry an authenticated actor.

  • Evidence: Supplier Onboarding reply uses one authenticated app user for issue/status/comment reads, comment creation, assignment, and transition; Reply Pilot resolve uses that actor for assignee lookup, comment, and issue update; Email Thread Reply update uses it for lookup, update, and every requested transition, not only report-attributed statuses.
  • Evidence: Reply Pilot close, move-to-waiting, Email Thread Reply close, and the post-forward move-to-waiting path retain their actor-aware transition metadata. Assignee/requester ids remain business data and never select the OAuth credential. Supplier reply comment identity is loaded from the authenticated app user rather than trusted request payload fields.
  • Evidence: an OAuth reconnect requirement after a Supplier comment or after an email send returns the existing partial-success warning instead of inviting a retry of an already completed external side effect. Existing authorization, status checks, and local cache synchronization remain unchanged.
  • Validation: focused backend TaskMutationServiceTest,TaskMutationControllerTest,EmailSendControllerTest passed with 51 tests; seven corresponding Flask workflow tests passed; full mvn -q verify, full Flask suite with 281 tests, mkdocs build --strict, and git diff --check passed. script/check-architecture.sh still reports the pre-existing stale reports/architecture-report.md baseline documented in the unfinished audit; this slice did not regenerate the broader dirty-tree report.

  • [x] 30.2 Make OAuth-required handling one consistent backend/app contract.

  • Evidence: JiraOAuthRequiredExceptionHandler is the single backend mapping to HTTP 428 with status=error, code jira_oauth_required, and the original reason; controller-local copies were removed.
  • Evidence: missing, malformed, missing-refresh, rejected-refresh, and delegated Jira 401 paths converge on JiraOAuthRequiredException; no delegated request falls back to the technical account.
  • Evidence: task mutation, Jira backend, attachment, and lead-import HTTP clients require both HTTP 428 and the contract code before exposing the shared JiraOAuthRequiredError.
  • Evidence: Reply Pilot resolve, Supplier Onboarding reply, and Email Thread Reply edit preserve submitted values and render the shared Jira connection prompt; existing actor-aware transition actions offer the same connection flow without changing their success routes or payloads.
  • Validation: mvn -q -Dtest=JiraOAuthServiceTest,JiraProxyControllerTest,TaskMutationControllerTest test passed; focused app client/workflow/template selection passed with 11 tests; full mvn -q verify, full app suite with 281 tests, and mkdocs build --strict passed.

  • [x] 30.1 Add an explicit delegated Jira request path for every supported API operation.

  • Evidence: JiraIssueClient now exposes required actor-aware variants for issue reads/searches, user lookup, create/update, assign/unassign, transition, comments, and attachment settings/list/upload/download/delete; the existing no-actor variants remain the explicit technical-account path.
  • Evidence: actor-aware methods are not allowed to default to technical methods, so an implementation cannot compile while silently omitting the delegated path.
  • Evidence: HttpJiraIssueClient resolves one JiraDelegatedAccess through the existing JiraOAuthAccessProvider, reuses it across every request in the operation, and sends Bearer authentication to api.atlassian.com/ex/jira/{cloudId}/rest/api/3; the same request access object also covers multipart upload and binary download.
  • Evidence: delegated HTTP 401 responses become JiraOAuthRequiredException; no delegated method retries with Basic credentials. Existing technical methods still use the configured Basic authentication and Jira base URL for background callers.
  • Validation: mvn -q -Dtest=HttpJiraIssueClientTest,JiraOAuthServiceTest test passed, covering Bearer/cloud URL selection, token refresh, delegated transition metadata, delegated attachment operations, delegated 401, and the technical Basic-auth path.
  • Validation: full mvn -q test passed in reply-pilot-be.
  • Validation: mkdocs build --strict and git diff --check passed.

  • [x] 29. Add sent-only Gmail threads to the single Gmail-owned cache.

  • Evidence: POST /api/mailbox/snapshot accepts optional skip_cached_threads; when true, reply-pilot-gmail reads cached thread IDs from its index, lists Gmail thread IDs for the requested query, skips cached IDs before threads.get, and merges new sent-only threads into the same account-scoped cache.
  • Evidence: reply-pilot-be supports sync_mode=sent_backfill and POST /api/mailbox/sync/sent; both route through the existing mailbox import state machine and worker step endpoint.
  • Evidence: full mailbox sync is now sequential: inbox snapshot with reset, then in:sent with page size 20 and skip_cached_threads=true, then the activity import runs against the unified cache.
  • Evidence: Gmail watch defaults and encrypted local/prod Gmail env now use GMAIL_WATCH_LABEL_IDS=INBOX,SENT; ensureWatch re-registers when saved labels, filter behavior, or topic differ from current configuration.
  • Evidence: incremental history keeps cache-relevant threads when any message in the thread has INBOX or SENT.
  • Validation: mvn -f reply-pilot-gmail/pom.xml clean verify, mvn -f reply-pilot-be/pom.xml clean verify, and python3 -m pytest reply-pilot-worker/tests passed during implementation.
  • Validation: script/check-architecture.sh, mkdocs build --strict, and git diff --check passed; GMAIL_WATCH_LABEL_IDS was verified through SOPS for local and prod Gmail env files.

  • [x] 28. Move Gmail mailbox cache ownership back to the Gmail module.

  • Evidence: the old weekly-report-only state was partial; the completed boundary is now Google Gmail API/mailbox -> reply-pilot-gmail -> reply-pilot-be.
  • Evidence: reply-pilot-gmail owns the account-scoped physical cache under data/accounts/<GMAIL_CACHE_ACCOUNT_ID>/emails/, including index, thread snapshots, attachment metadata, attachment bytes, remote page token, and history cursor.
  • Evidence: Gmail attachment relative_path values remain logical paths such as emails/attachments/<thread>/<file>, so DB/debug-context behavior does not need a migration.
  • Evidence: reply-pilot-gmail exposes internal cache read endpoints for BE inbox/detail/attachment reads and the Java Spring Boot GET /api/reports/email-weekly-counts endpoint computes from Gmail-owned cache/runtime data.
  • Evidence: reply-pilot-jira-reports defaults JIRA_REPORTS_EMAIL_BASE_URL to http://reply-pilot-gmail:5000 in config, Compose, scripts, docs, and env template.
  • Evidence: reply-pilot-be uses an HTTP-backed cache adapter in EMAIL_SYNC_BACKEND=gmail_service mode, does not create a Gmail cache file repository bean in that mode, and proxies attachment bytes from the Gmail module instead of reading local BE files.
  • Validation: focused Gmail/BE cache tests passed during implementation; full validation is tracked by the implementation task outcome.

  • [x] 27.7 Align search, docs, and architecture checks after delegated task workflow changes.

  • Evidence: reply-pilot-search indexes reply_pilot_task Jira issues, includes delegated task_type_value in task search text/subtitles, and stores delegated_task_assignee_app_user_id only for active delegated tasks.
  • Evidence: assigned-scope task search now allows active delegated tasks for the cached assignee in addition to unscoped tasks and tasks owned through normal company assignment.
  • Evidence: Done delegated tasks remain indexed for explicit search through normal company scope or company.view_all, but they do not get the active delegated-assignee search exception.
  • Evidence: docs/backend.md, docs/permissions.md, docs/web-app.md, reply-pilot-search/README.md, and docs/refactoring-context.md document the final search/visibility behavior.
  • Evidence: reports/architecture-report.md was regenerated after the search implementation change, and script/check-architecture.sh passed against the updated baseline.
  • Validation: focused search validation python3 -m pytest reply-pilot-search/tests/test_runtime.py -k 'task_scope or task_documents' passed with 2 tests; full search validation python3 -m pytest reply-pilot-search/tests passed with 19 tests; python3 script/architecture-report.py --output reports/architecture-report.md, script/check-architecture.sh, mkdocs build --strict, and git diff --check passed.

  • [x] 27.6 Add delegated task UI workflow.

  • Evidence: reply-pilot-app keeps reply_pilot_task as its own Jira work type, reads backend-provided task_type_value into task list/detail models, and shows delegated task context in the existing task overview.
  • Evidence: delegated task detail renders the linked company, Jira description, and read-only Jira comments through the extracted _jira_comments.html component shared with the current supplier task UI.
  • Evidence: assignees can open a one-textarea resolve page that calls backend POST /api/tasks/{taskId}/reply-pilot-task/resolve; requesters can close returned tasks through a POST action that calls POST /api/tasks/{taskId}/reply-pilot-task/close.
  • Evidence: app routes remain thin views.py route-map entries delegated to reply_pilot_app.workflows.tasks, and mutation controls follow the UI rule that data-changing actions are submitted with buttons.
  • Evidence: docs/web-app.md and docs/refactoring-context.md describe the app delegated task UI and backend-owned mutation boundary.
  • Validation: focused app validation python3 -m pytest reply-pilot-app/tests/test_task_mutation_client.py reply-pilot-app/tests/test_app.py -k 'reply_pilot_task or task_mutation_client_maps_task_mutation_payloads or supplier_onboarding_reply_page_renders_summary_and_comments_newest_first' passed with 6 tests; full app validation python3 -m pytest reply-pilot-app/tests passed with 249 tests; mkdocs build --strict and git diff --check passed.

  • [x] 27.2 Add delegated task cache schema.

  • Evidence: migration reply-pilot-db/migrations/0041_add_reply_pilot_task_subtype.sql adds reply_pilot_task to task_jira_work_type and creates task_jira_reply_pilot_task with task_type_value, requester_app_user_id, timestamps, FK rollback, and no Jira dropdown check constraint.
  • Evidence: reply-pilot-db/migrations/db.changelog.xml includes the new changeset, and docs/database.md, docs/backend.md, docs/permissions.md, and docs/refactoring-context.md describe the delegated task cache, requester ownership, and narrow authorization rules.
  • Evidence: Docker was running with healthy reply-pilot-db, and the local DB ended in the migrated state after update, rollback, and re-update.
  • Validation: ./scripts/db-liquibase.sh validate, ./scripts/db-liquibase.sh update, ./scripts/db-liquibase.sh rollback-count 1, and ./scripts/db-liquibase.sh update all passed from reply-pilot-db/; mvn -f reply-pilot-be/pom.xml verify passed with 212 tests; mkdocs build --strict and git diff --check passed.

  • [x] 27.5 Implement delegated task mutations.

  • Evidence: TaskMutationController exposes POST /api/tasks/{taskId}/reply-pilot-task/resolve and POST /api/tasks/{taskId}/reply-pilot-task/close; both require an authenticated app user and delegate ownership checks to the backend service instead of adding a new RBAC permission.
  • Evidence: TaskMutationService resolves only for the cached Jira assignee of an active delegated task, appends the submitted text to Jira/plain-text description, reassigns Jira/local cache to the stored requester app user, and preserves In Progress.
  • Evidence: requester close transitions Jira/local cache to Done without changing assignee, and wrong-assignee/wrong-requester paths return 403.
  • Evidence: JdbcTaskMutationRepository loads delegated mutation targets and checks active assignee ownership through task_jira.user_id or the app user's Jira account profile; tests cover service, controller, and JDBC behavior.
  • Evidence: docs/backend.md, docs/permissions.md, docs/database.md, and docs/refactoring-context.md document the delegated task mutation contract. DB migration validation for the subtype table remains tracked separately by blocked item 27.2.
  • Validation: mvn -f reply-pilot-be/pom.xml -Dtest=TaskMutationServiceTest,TaskMutationControllerTest,JdbcTaskMutationRepositoryTest test passed with 24 tests; mvn -f reply-pilot-be/pom.xml verify passed with 212 tests; mkdocs build --strict and git diff --check passed.

  • [x] 27.4 Implement task-derived company visibility.

  • Evidence: JdbcReadModelRepository now grants a read-only delegated task exception inside company/task read-model scope checks when the current app user is the cached Jira assignee of an active reply_pilot_task linked to that company.
  • Evidence: the delegated exception does not change mutation authorization; it excludes unrelated companies, unassigned delegated tasks, and Done delegated tasks.
  • Evidence: task read paths use task-level delegated scope, so company access from one active assigned delegated task does not expose other delegated tasks on the same company.
  • Evidence: docs/permissions.md, docs/backend.md, docs/database.md, and docs/refactoring-context.md record the read-only delegated visibility rule and the remaining search alignment boundary.
  • Validation: mvn -f reply-pilot-be/pom.xml -Dtest=JdbcReadModelRepositoryTest test passed with 6 tests; mvn -f reply-pilot-be/pom.xml verify passed with 208 tests; mkdocs build --strict and git diff --check passed.

  • [x] 27.3 Extend backend Jira sync and read models for delegated tasks.

  • Evidence: JiraTaskSyncService now includes configured Reply Pilot Task issues in task-sync JQL, maps them to local work type reply_pilot_task, copies assignee/status/summary/description as before, and normalizes blank delegated task type values to General.
  • Evidence: HttpJiraIssueClient requests configured JIRA_REPLY_PILOT_TASK_TYPE_CUSTOM_FIELD_ID (default customfield_10269) in search/detail reads and parses Jira dropdown objects/lists into JiraIssue.replyPilotTaskTypeValue.
  • Evidence: JdbcJiraTaskSyncRepository upserts task_jira_reply_pilot_task.task_type_value for reply_pilot_task rows and removes stale subtype rows if an issue is no longer a delegated task.
  • Evidence: backend task list/detail/company-task read models expose task_type_value; the default task list hides Done delegated tasks unless the request explicitly filters by status or Jira key, while explicit lookup and company task lists remain discoverable.
  • Evidence: /api/meta//api/jira/meta, .env.example, reply-pilot-be/README.md, and docs/backend.md document the delegated task issue type and custom-field configuration. Existing read-only Jira comments remain available through the existing Jira comments API for the UI component slice.
  • Validation: mvn -f reply-pilot-be/pom.xml -Dtest='JiraTaskSyncServiceTest,JdbcJiraTaskSyncRepositoryTest,JdbcReadModelRepositoryTest,JiraProxyControllerTest,HttpJiraIssueClientTest,HealthControllerTest' test passed with 23 tests; mvn -f reply-pilot-be/pom.xml verify passed with 207 tests; mkdocs build --strict and git diff --check passed.

  • [x] 27.1 Finalize delegated Jira task contract.

  • Evidence: user confirmed Jira issue type Reply Pilot Task with admin edit URL https://internet-handel.atlassian.net/secure/admin/EditIssueType!default.jspa?id=10206.
  • Evidence: user confirmed Jira taskType field customfield_10269 and options General, Meeting organization, and Registration (B2B).
  • Evidence: user confirmed company link appears in Jira description while Reply Pilot DB company id is workflow/authorization source of truth; requester is the Reply Pilot app user who created the task; resolve text is stored in Jira description; Jira comments remain read-only through the existing comment component; Done is hidden only from default task view.
  • Validation: mkdocs build --strict passed.

  • [x] 26.1 Finish Java backend runtime cutover validation.

  • Evidence: Docker Desktop was started locally and docker build -t reply-pilot-be:runtime-cutover-check reply-pilot-be successfully built the Java 21 Spring Boot runtime image from reply-pilot-be/Dockerfile.
  • Evidence: the previously running reply-pilot-be container was still the old Gunicorn/Python runtime, so Compose was rebuilt and force-recreated from the current Java Dockerfile with HOST_UID=$(id -u) HOST_GID=$(id -g) docker compose up --build -d --force-recreate in reply-pilot-be.
  • Evidence: the recreated container runs java -jar /app/reply-pilot-be.jar as non-root host user 501:20, Docker reports it healthy, Spring Boot logs show Java 21.0.11 on Tomcat port 5000, and curl -fsS http://127.0.0.1:9091/healthz returned {"status":"ok"}.
  • Validation: mvn -f reply-pilot-be/pom.xml clean verify passed with 169 Java tests; python3 -m pytest reply-pilot-app/tests passed with 213 tests; script/check-architecture.sh, mkdocs build --strict, and git diff --check passed.

  • [x] 26.2 Remove legacy Python backend implementation and test harness.

  • Evidence: removed the legacy backend package reply-pilot-be/reply_pilot_be, deleted reply-pilot-be/tests, and removed Python-only backend runtime leftovers: gunicorn.conf.py, requirements.txt, requirements-dev.txt, scripts/be-generate-gmail-token.py, and scripts/repair-contaminated-lead-import-party.py.
  • Evidence: backend documentation now records Java 21 Spring Boot/Maven/JUnit as the maintained backend implementation and validation surface. The app README points Gmail OAuth token generation to reply-pilot-gmail/scripts/gmail-generate-token.sh.
  • Evidence: reports/architecture-report.md now lists reply-pilot-be with no Python package, no Python production files, no Python test files, no Python script files, and no Python runtime/dev dependencies.
  • Validation: mvn -f reply-pilot-be/pom.xml clean verify passed with 169 Java tests; python3 -m pytest reply-pilot-app/tests passed with 213 tests; script/check-architecture.sh, mkdocs build --strict, and git diff --check passed. A working-tree scan for .py files under reply-pilot-be outside build/data/log paths returned no files.

  • [x] 25.10 Resolve Java lead-import outbound email transaction boundary.

  • Evidence: LeadImportService now sends the optional outbound Gmail message and passes the resulting CachedEmailItem to LeadImportRepository.markImported instead of importing activity in a separate repository transaction.
  • Evidence: JdbcLeadImportRepository.markImported links the sent recipient email contact to the imported party, calls ActivityEmailImportRepository.importEmails(Connection, ...), creates the lead-import activity note, marks the lead item imported, and refreshes batch progress in one caller-owned DB transaction.
  • Evidence: JdbcActivityEmailImportRepository now supports transactional email import on a supplied JDBC connection and rejects automatic reply-task side effects in that mode, because those task side effects are post-commit behavior for incoming supplier replies.
  • Evidence: direct JDBC tests cover the successful transaction, recipient contact linking, no non-transactional importer call, and rollback of the imported sent email marker/contact link/item/batch updates when the transactional importer fails.
  • Evidence: the touched outreach/contact upsert paths are JDBC-portable and savepoint-guarded so duplicate-key races do not leave a PostgreSQL transaction aborted before fallback update.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest=JdbcLeadImportRepositoryTest,LeadImportServiceTest,JdbcActivityEmailImportRepositoryTest test passed with 10 tests. Full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 169 tests. Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests; app validation python3 -m pytest reply-pilot-app/tests passed with 213 tests. Architecture and documentation validation script/check-architecture.sh and mkdocs build --strict passed. Whitespace validation git diff --check and a direct trailing-whitespace scan of Java/doc files passed.

  • [x] 25.9 Port concrete historical requirement backfill worker bean to Java.

  • Evidence: Java now provides JdbcRequirementBackfillWorker as the concrete RequirementBackfillWorker bean behind POST /api/requirements/backfill/run. It mirrors the Python party selection modes for party_id, after_party_id, and service-level party_ids fan-out, selects incoming email threads for active organization parties, and preserves the existing backfill result payload keys.
  • Evidence: the worker first synchronizes cached email thread artifacts through ActivityEmailImportRepository.syncEmailArtifacts; when cache lookup misses or cached artifact sync fails, it falls back to DB-derived thread messages and DB-derived attachments without inserting duplicate base email activity rows.
  • Evidence: Java AI classification and requirement aggregation workers now expose party-scoped processing methods for backfill. The scoped aggregation path claims only selected-party queue rows and intentionally skips the global ruleset-version full rebuild.
  • Evidence: direct JDBC tests cover paged historical backfill with one cached thread and one DB fallback thread, empty target selection, explicit DB fallback attachments in the activity importer, party-scoped AI candidate processing, and party-scoped aggregation queue claiming.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest=JdbcActivityEmailImportRepositoryTest,JdbcRequirementBackfillWorkerTest,JdbcRequirementAiClassificationWorkerTest,JdbcRequirementAggregationWorkerTest test passed with 18 tests. Full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 167 tests; Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests; app-side validation python3 -m pytest reply-pilot-app/tests passed with 213 tests. Architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict, git diff --check, and direct trailing-whitespace scan of touched Java/doc files passed.

  • [x] 25.8 Port concrete requirement monitoring/report worker bean to Java.

  • Evidence: Java now provides JdbcRequirementMonitoringWorker as the concrete RequirementMonitoringWorker bean behind GET /api/requirements/report. It loads requirement state counts, manual-review quality metrics, AI evidence metrics, and aggregation queue backlog directly from the backend DataSource.
  • Evidence: the worker mirrors the Python report payload shape, including state-count totals/rows, precision/recall/accuracy/value-match metrics, OpenAI configured/model fields, AI evidence counts, worker status payload, worker error totals, last AI status/detail/update time, and unavailable worker-status reasons.
  • Evidence: RequirementMonitoringSettings mirrors WORKER_STATUS_BASE_URL and WORKER_STATUS_TIMEOUT_SECONDS, while OpenAIClientSettings supplies the AI configured/model fields.
  • Evidence: direct JDBC tests cover DB-derived report metrics with a local /statusz HTTP server and the not-configured worker-status fallback.
  • Validation: full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 163 tests; Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests. Architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.7 Port concrete requirement aggregation worker bean to Java.

  • Evidence: Java now provides JdbcRequirementAggregationWorker as the concrete RequirementAggregationWorker bean behind POST /api/requirements/evaluate/run. It claims due party_requirement_eval_queue rows, uses PostgreSQL FOR UPDATE SKIP LOCKED where available, increments attempts, and keeps H2-compatible SQL for focused JDBC tests.
  • Evidence: the worker ports the Python aggregation rules for ENLISTMENT_TABLE, FEED, and SUPPLIER_IDENTIFIER, including source bonuses, confidence thresholds, competing candidate review states, final feed URL/delivery-mode projection, supplier identifier projection, fulfilled timestamp handling, and party_requirement_state upsert without overwriting manual overrides.
  • Evidence: per-item failures roll back to a savepoint, defer only the failed queue row, and continue the batch. RequirementAggregationSettings mirrors the existing APP_DATA_DIR, REQUIREMENT_EVAL_LOCK_TTL_SECONDS, REQUIREMENT_AGGREGATION_RULESET_VERSION, and REQUIREMENT_RULESET_STATE_FILE behavior, including one-time full-company requeue when the aggregation ruleset version changes.
  • Evidence: direct JDBC tests cover successful FEED aggregation, manual override preservation, unsupported requirement deferral with continuation, and ruleset-version rebuild queueing/state-file persistence.
  • Validation: full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 161 tests; Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests. Architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.6 Port concrete AI requirement classification worker bean to Java.

  • Evidence: Java now provides JdbcRequirementAiClassificationWorker as the concrete RequirementAiClassificationWorker bean. It scans deterministic candidates for ENLISTMENT_TABLE, FEED, and SUPPLIER_IDENTIFIER, skips candidates already materialized for the current REQUIREMENT_AI_PROMPT_VERSION, loads email/source context, calls the structured OpenAI JSON client with the committed schema payload, validates the fixed AI payload shape, replaces AI-created evidence/fact rows, and enqueues party_requirement_eval_queue work after successful writes.
  • Evidence: RequirementAiClassificationSettings mirrors the existing APP_DATA_DIR, REQUIREMENT_AI_SCHEMA_DIR, and REQUIREMENT_AI_PROMPT_VERSION configuration. HttpOpenAIJsonClient now supports input files for JSON-schema calls so attachment-backed classifications can upload cached files like the Python reference path.
  • Evidence: direct JDBC tests cover successful FEED classification with an attachment input file, SUPPLIER_IDENTIFIER fact materialization without writing requirement evidence rows, and invalid AI payload handling without partial DB writes.
  • Validation: full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 157 tests; Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests. Architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.5 Replace the historical CME matching worker with Java full sync.

  • Evidence: JdbcCmeCompanySyncWorker imports only dodavatel and osloveni_dodavatele into party_cme_source; the old queue, match read model, endpoint, worker job, and configuration were removed.
  • Validation: full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed with 153 tests; Python backend compatibility validation python3 -m pytest reply-pilot-be/tests passed with 227 tests. Architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.4 Port automatic incoming supplier reply task creation to Java.

  • Evidence: TaskMutationService now implements the domain EmailThreadReplyTaskAutomation port. For incoming messages without an existing thread task, it creates Email Thread Reply Jira issues, uses the Python source priority for default assignee resolution (party_organization, app_configuration, configured Jira account, configured assignee email, otherwise unassigned), transitions new issues to Drafting Reply, stores task_jira_email_thread_reply, and backfills the app task link into Jira.
  • Evidence: existing thread tasks found by task_jira_email_thread_reply.external_thread_id are transitioned to Drafting Reply through the same automation port instead of creating a duplicate task.
  • Evidence: JdbcActivityEmailImportRepository now collects automatic reply task requests only when a newly imported incoming external message did not update any linked reply task and resolved to exactly one supplier company. It runs the automation after the email-import transaction commits, so the Java task service can read company rows created by the import while failed Jira automation still does not roll back the email import.
  • Evidence: docs/backend.md now records the actual Python/Java behavior and removes the stale claim that automatic reply-task creation depends on a Supplier Onboarding task.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest='JdbcActivityEmailImportRepositoryTest,TaskMutationServiceTest,ArchitectureRulesTest' test passed; clean full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed; full Python backend validation python3 -m pytest reply-pilot-be/tests passed; architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.3 Port linked incoming-reply Jira task transition to Java activity importer.

  • Evidence: JdbcActivityEmailImportRepository now receives JiraIssueClient and, when a newly imported incoming external email belongs to an existing task_jira_email_thread_reply link, transitions each linked email_thread_reply Jira issue to Drafting Reply and syncs task_jira.status.
  • Evidence: the Java importer preserves the Python fallback where a failed transition still updates local status if a subsequent Jira issue load shows the issue is already in Drafting Reply.
  • Evidence: this item intentionally covers linked-task status automation only. Automatic creation of a new Email Thread Reply task for an unlinked incoming supplier reply remains in the unfinished audit.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest='JdbcActivityEmailImportRepositoryTest,TaskMutationServiceTest,ArchitectureRulesTest' test passed; clean full Java validation mvn -f reply-pilot-be/pom.xml clean verify passed; full Python backend validation python3 -m pytest reply-pilot-be/tests passed; architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.2 Port deterministic requirement/fact extraction to Java activity importer.

  • Evidence: JdbcActivityEmailImportRepository now mirrors the deterministic incoming-email extraction layer for non-internal senders. It replaces its own deterministic-email-parser rows in party_feed_fact and party_supplier_identifier_fact, replaces its own deterministic-enlistment-detector rows in party_requirement_evidence, and queues ENLISTMENT_TABLE, FEED, and SUPPLIER_IDENTIFIER aggregation work in party_requirement_eval_queue.
  • Evidence: the Java detector writes explicit feed URL facts, labeled IČO/DIČ/GLN/vendor-code identifier facts, and deterministic enlistment-table evidence from matching spreadsheet attachment filenames or body markers. While porting the DIČ detector, Java intentionally avoids the Python regex bug where \s can consume the next email line into the VAT value.
  • Evidence: H2 repository coverage verifies deterministic fact/evidence insertion, aggregation queueing, and idempotent replacement on reimport of the same Gmail message.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest='JdbcActivityEmailImportRepositoryTest,ArchitectureRulesTest' test passed; full Java validation mvn -f reply-pilot-be/pom.xml verify passed; full Python backend validation python3 -m pytest reply-pilot-be/tests passed; architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 25.1 Add production Java JDBC activity-email import repository.

  • Evidence: Java now provides JdbcActivityEmailImportRepository as the production Spring ActivityEmailImportRepository implementation. It transactionally imports cached email thread messages into activity, activity_email, activity_participant, contact mechanism and party identity rows, and synchronizes activity_email_attachment and activity_email_link rows from the backend email cache.
  • Evidence: the adapter supports idempotent message updates by provider = 'gmail' and external_message_id, replaces participants and artifacts on re-import, and implements syncEmailArtifacts for the mailbox attachment phase.
  • Evidence: this item intentionally covered base activity-email/artifact persistence only. Deterministic requirement/fact extraction is covered by later item 25.2; incoming-reply Jira automation from Python ActivityImportStore remains in the unfinished audit.
  • Validation: focused Java validation mvn -f reply-pilot-be/pom.xml -Dtest='JdbcActivityEmailImportRepositoryTest,ArchitectureRulesTest' test passed; full Java validation mvn -f reply-pilot-be/pom.xml verify passed; full Python backend validation python3 -m pytest reply-pilot-be/tests passed; architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 24.21 Final Java endpoint parity audit.

  • Evidence: Java now includes the previously missing POST /api/emails/{emailId}/debug/generate route through DebugReplyController; it preserves instruction validation, missing email handling, selected context normalization, attachment file inclusion, OpenAI text generation, response envelope, and reply_subject behavior.
  • Evidence: reply-pilot-be/tests/test_java_route_parity.py parses Flask api_bp decorators and Java Spring controller mappings and verifies all 112 current Flask backend JSON/health routes are represented by Java controllers. The test expands Java's email-thread catch-all dispatch route and records the requirement-review path-shape equivalence for current single-segment requirement codes.
  • Evidence: docs/backend.md now records the Java endpoint parity audit, family-level route inventory, route-shape notes, and remaining non-endpoint migration caveats. No endpoint-level deferrals remain in the audit.
  • Validation: focused Java tests mvn -f reply-pilot-be/pom.xml -Dtest='DebugReplyControllerTest,AiDraftControllerTest,HttpOpenAIJsonClientTest' test passed; focused route/compatibility validation python3 -m pytest reply-pilot-be/tests/test_java_route_parity.py reply-pilot-be/tests/test_api_compatibility.py -q passed; full Java validation mvn -f reply-pilot-be/pom.xml verify passed; full Python backend validation python3 -m pytest reply-pilot-be/tests passed; architecture validation python3 script/architecture-report.py --output reports/architecture-report.md and script/check-architecture.sh passed; documentation and whitespace validation mkdocs build --strict and git diff --check passed.

  • [x] 24.20 Port worker job endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/cme/company-sync/run, POST /api/requirements/ai-classify/run, POST /api/requirements/evaluate/run, POST /api/requirements/backfill/run, and GET /api/requirements/report through WorkerJobController, WorkerJobService, and typed domain worker ports.
  • Evidence: the Java endpoint behavior preserves compatible limit parsing, party_id/after_party_id/party_ids parsing, multi-party backfill result merging, success envelopes, not-configured 503 responses, expected worker 400 responses, and unexpected worker 500 error prefixes.
  • Evidence: CME sync, requirement AI, requirement aggregation, or historical backfill worker bean is claimed by this item. Without a configured port bean, the new endpoints intentionally return the same not-configured errors as the Flask reference.
  • Validation: focused Java tests mvn -f reply-pilot-be/pom.xml -Dtest='WorkerJobControllerTest' test passed; full Java validation mvn -f reply-pilot-be/pom.xml verify passed; Flask compatibility validation python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q passed; docs validation mkdocs build --strict and git diff --check passed.

  • [x] 24.19 Port lead import endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/lead-imports/files, POST /api/lead-imports/batches, GET /api/lead-imports/batches/{batchId}, GET /api/lead-imports/batches/{batchId}/items/{itemId}, POST /api/lead-imports/batches/{batchId}/items/{itemId}/actions, and POST /api/lead-imports/batches/{batchId}/items/{itemId}/draft.
  • Evidence: the Java lead import service handles server-side JSONL inbox to processing/done/failed file movement, batch/item persistence, pending-item draft generation through OPENAI_MODEL_LEAD_IMPORT, prompt id/snapshot storage, item errors, skip/do-not-contact/import actions, party/contact upsert, outreach policy, activity notes, Jira Supplier Onboarding task creation with lead-import labels, optional outbound Gmail send, and compatible validation/error envelopes.
  • Evidence: outbound email activity recording uses the existing Java ActivityEmailImportRepository hook when configured; it is not a single DB transaction with lead item update, and the production JDBC activity importer remains tracked in the unfinished audit.
  • Validation: focused Java tests mvn -f reply-pilot-be/pom.xml test -Dtest=LeadImportControllerTest,LeadImportServiceTest passed; full Java validation mvn -f reply-pilot-be/pom.xml verify passed; Flask compatibility validation python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q passed.

  • [x] 24.18 Port email draft/send endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/emails/drafts/outbound, POST /api/emails/{emailId}/drafts/reply, POST /api/emails/send/outbound, and POST /api/emails/{emailId}/send/reply with compatible JSON payloads, multipart reply payloads, validation messages, missing-email handling, response envelopes, and attachment count/size limits.
  • Evidence: the Gmail HTTP adapter now forwards outbound/reply draft and send calls to reply-pilot-gmail, resolves reply_to_message_id from thread history, and emits multipart form-data for reply attachments.
  • Evidence: successful reply sends update the local email cache immediately; sent replies and outbound sends call the pluggable ActivityEmailImportRepository when present. This slice does not add the production JDBC activity-email importer, so DB contact-history persistence remains tracked in the unfinished audit.
  • Evidence: compatibility harness now covers email draft/send endpoint contracts, including multipart attachment limit behavior, against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='EmailSendControllerTest,HttpGmailServiceEmailSyncClientTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 24.17 Port mailbox watch/import endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/mailbox/watch, POST /api/mailbox/watch/ensure, POST /api/mailbox/watch/stop, POST /api/mailbox/watch/notify, POST /api/mailbox/watch/pull, POST /api/mailbox/sync/full, POST /api/mailbox/import, GET /api/mailbox/import/status, and POST /api/mailbox/import/step with compatible envelopes, status codes, notification history comparison, full-sync alias behavior, and rate-limit JSON fields.
  • Evidence: the Gmail HTTP adapter now forwards watch status/ensure/stop, notification record/pull/ack calls, maps 429 retry metadata, and preserves the existing email-cache sync bridge.
  • Evidence: mailbox import status/state is persisted through mailbox-import-status.json, mailbox-import-state.json, and a process lock; import orchestration is pluggable through ActivityEmailImportRepository. This slice does not add the production JDBC activity-email importer, so database artifact import remains an explicit unfinished audit item instead of a hidden success.
  • Evidence: compatibility harness now covers mailbox watch/import endpoint contracts against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='MailboxControllerTest,HttpGmailServiceEmailSyncClientTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 24.16 Port inbox, email, and attachment read endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/inbox, GET /api/emails, GET /api/emails/{emailId}, GET /api/emails/{emailId}/attachments, and GET /api/emails/{emailId}/attachments/{filename} with compatible inbox pagination, email list filtering, detail payloads, attachment metadata, binary attachment serving, JSON error envelopes, and file-cache path safety.
  • Evidence: Java now reads the existing email cache layout under data/emails, preserves legacy emails.json, computes attachment content hashes, preserves remote page cursors during single-thread refreshes, and adds a Gmail-service HTTP sync adapter for read-side detail fetch, snapshot page fetch, and attachment refresh behavior.
  • Evidence: compatibility harness now covers inbox/list/detail/attachment metadata/binary download behavior plus missing email and missing attachment errors against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='EmailReadControllerTest,HttpGmailServiceEmailSyncClientTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 24.15 Port AI draft generation endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/supplier-onboarding/draft and POST /api/email-thread-task/draft with compatible success envelopes, OpenAI JSON schema requests, supplier lead-import model selection, validation messages, empty-AI-response handling, and OpenAI client error mapping.
  • Evidence: the Java OpenAI JSON client now supports /responses JSON schema calls with strict text.format, parsed-output extraction, output-text JSON fallback, model/response-id propagation, and missing-key/upstream error handling.
  • Evidence: compatibility harness now covers supplier onboarding and email-thread task draft success payloads plus missing company/thread context validation against the current Flask reference backend.
  • Validation: full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 24.14 Port Jira proxy and task-sync endpoints to Java.

  • Evidence: Java Spring Boot now implements Jira proxy endpoints for GET /api/jira/myself, GET /api/jira/users/by-email, GET /api/jira/issues/{issueKey}, PUT /api/jira/issues/{issueKey}, GET /api/jira/issues/{issueKey}/transitions, POST /api/jira/issues/{issueKey}/transition, GET /api/jira/issues/{issueKey}/comments, POST /api/jira/issues/{issueKey}/comments, POST /api/jira/issues/{issueKey}/unassign, GET /api/jira/issues/search, GET /api/jira/issues/assigned, GET /api/jira/issues/stale, POST /api/jira/issues, and POST /api/jira/task-sync/run with compatible request parsing, response envelopes, Jira upstream error mapping, validation messages, pagination token handling, and environment-label behavior.
  • Evidence: Java task-sync service now builds full/incremental Jira JQL, honors safety overlap, maps Jira assignees to app users, writes Jira task cache fields and sync state through JDBC, and records skipped/failed runs.
  • Evidence: compatibility harness now covers Jira user lookup, issue detail, transitions, comments, search/assigned/stale lists, create/update, transition/unassign, task-sync run, and validation behavior against the current Flask reference backend.
  • Validation: full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 24.13 Port task and email-thread reply mutation endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/companies/{partyId}/supplier-onboarding-tasks, PATCH /api/tasks/{taskId}/company, POST /api/tasks/{taskId}/email-threads, PATCH /api/tasks/{taskId}/supplier-onboarding, PATCH /api/tasks/{taskId}/email-thread-reply, POST /api/tasks/{taskId}/refresh-from-jira, POST /api/tasks/{taskId}/supplier-onboarding/reply, POST /api/tasks/{taskId}/move-reply-to-waiting, and POST /api/email-threads/{emailId}/reply-tasks with compatible response envelopes, duplicate reply-task conflicts, validation/error mapping, Jira issue/user/transition/comment calls, app task links, assignee profile sync, and JDBC writes to task, task_jira, task_jira_supplier_onboarding, and task_jira_email_thread_reply.
  • Evidence: compatibility harness now covers supplier-onboarding task creation, task company/thread links, supplier-onboarding updates/replies, email-thread reply updates, Jira refresh, move-to-waiting, manual email-thread reply task creation, and duplicate-thread conflict behavior against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='TaskMutationServiceTest,TaskMutationControllerTest,JdbcTaskMutationRepositoryTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q.

  • [x] 01. Lock intended boundary.

  • Evidence: docs/module-boundaries.md documents target boundary, allowed transitional calls, and app DB/Gmail/OpenAI debt.

  • [x] 02. Inventory AI prompt usage.

  • Evidence: current prompt routes, workflows, templates, prompt types, payload fields, tests, and backend endpoint needs were captured before implementation.

  • [x] 03. Add backend AI prompt API.

  • Evidence: backend prompt service/workflow/routes and API tests were added.

  • [x] 04. Switch app AI prompt UI to backend.

  • Evidence: app prompt CRUD uses backend-backed prompt store when BACKEND_API_BASE_URL is configured.

  • [x] 05. Migrate reply draft storage.

  • Evidence: backend reply-draft API exists and app uses HttpReplyDraftStore in standard runtime.

  • [x] 06. Migrate user profile storage.

  • Evidence: user profile reads/writes go through backend app-state endpoints via HttpUserProfileStore.

  • [x] 07. Migrate app configuration and Jira assignee mapping.

  • Evidence: app configuration and Jira assignable-user/profile mapping go through backend app-state endpoints via HTTP adapters.

  • [x] 08. Inventory read models before moving them.

  • Evidence: route/workflow reads, template fields, backend endpoint gaps, and safe migration order were captured before implementation.

  • [x] 09. Move company and person read pages.

  • Evidence: company/person list and detail read paths are backend-backed in standard runtime.

  • [x] 10. Move activity and email detail read pages.

  • Evidence: activity/email detail and related company-by-thread reads are backend-backed in standard runtime.

  • [x] 11. Move task read pages.

  • Evidence: task list/detail and related task lookup reads are backend-backed in standard runtime.

  • [x] 12. Inventory mutations.

  • Evidence: mutation inventory classified existing backend-backed flows, remaining task-store debt, backend endpoint gaps, and safe migration order.

  • [x] 13. Move company/person/contact mutations.

  • Evidence: company update, company merge, visibility, and requirement review use record_mutation_client.py and backend /api/companies/... mutation endpoints. Person/contact/identifier/link mutations use backend clients.

  • [x] 14. Move task and Jira mutations.

  • Evidence: supplier onboarding task creation/editing, task company selection, email-thread reply task creation/editing, email-thread linking, supplier onboarding replies, Jira refresh, and reply-task status movement delegate to backend email/task mutation APIs.

  • [x] 15. Remove app email/Gmail/OpenAI fallback production code.

  • Evidence: reply-pilot-app no longer contains gmail_store.py or openai_client.py; app factory requires BACKEND_API_BASE_URL outside tests, builds backend-backed draft/debug clients in standard runtime, and no longer wires an openai_client extension. Reply debug generation delegates to backend email service APIs. Test-only local email cache behavior remains isolated to generic cache-service fixtures.
  • Validation: python3 -m pytest reply-pilot-app/tests passed; python3 -m pytest reply-pilot-be/tests passed; script/check-architecture.sh passed after regenerating reports/architecture-report.md; mkdocs build --strict passed.

  • [x] 16. Move simple-auth nonce persistence.

  • Evidence: app owns browser auth/session flow, but used nonce persistence is backend-backed through POST /api/simple-auth/nonces/check and POST /api/simple-auth/nonces/mark-used.

  • [x] 17. Move search behind backend.

  • Evidence: backend exposes app-facing GET /api/search and delegates to reply-pilot-search through a backend search client. App search client now uses BACKEND_API_BASE_URL; app runtime config, Compose, and scripts no longer expose SEARCH_API_BASE_URL or SEARCH_TIMEOUT_SECONDS. Boundary docs and PlantUML diagrams now show reply-pilot-app -> reply-pilot-be -> reply-pilot-search.
  • Validation: focused backend proxy tests python3 -m pytest reply-pilot-be/tests/test_api.py -q -k "api_search" passed; focused app search/create-app tests python3 -m pytest reply-pilot-app/tests/test_app.py -q -k "search_page or create_app_uses_backend_email_thread_task_draft_client_when_backend_configured" passed. Full validation also passed: python3 -m pytest reply-pilot-app/tests; python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh; mkdocs build --strict.

  • [x] 18. Remove remaining app DB/runtime dependency debt.

  • Evidence: app directory_store, record_store, and task_store no longer contain PostgreSQL-backed production store classes or psycopg imports. Standard app runtime requires backend HTTP read/task APIs; task_store wiring is test-only. reply-pilot-app no longer declares psycopg, exposes REPLY_PILOT_DB_* runtime config, or documents app-to-DB runtime access.
  • Validation: python3 -m pytest reply-pilot-app/tests -q passed; python3 -m pytest reply-pilot-be/tests -q passed; python3 script/architecture-report.py --output reports/architecture-report.md regenerated the baseline; script/check-architecture.sh passed; mkdocs build --strict passed.

  • [x] 19.1-19.7 Close Python backend route-map cleanup as obsolete.

  • Evidence: on 2026-06-12, project direction changed: further Python Flask backend route-map refactoring is no longer needed because Java backend endpoint parity is the active migration path and Python backend code will be removed later. This closes slices 19.1-19.7 for lead import, inbox/email/attachment, mailbox, CME/requirement worker, email draft/send, debug reply generation, and final Flask route-map audit.
  • Evidence: this is not an implementation-complete claim for the Flask route cleanup. Remaining Python views.py implementation logic is intentionally left in place until later Python backend removal.
  • Validation: tracker/context-only change; mkdocs build --strict and git diff --check passed.

  • [x] 20. Align refactoring ledger with the refactoring-plan skill.

  • Evidence: active ledger moved to docs/refactoring.md; root refactoring.md is only a compatibility pointer; active tasks use checkbox status, stable IDs, concise prompt-free entries, unfinished audit, maintenance items, and done archive.
  • Validation: mkdocs build --strict, script/check-architecture.sh, and git diff --check -- refactoring.md docs/refactoring.md mkdocs.yml passed.

  • [x] 21. Apply updated prompt-free refactoring-plan ledger rules and re-audit item statuses.

  • Evidence: open items 15, 17, 18, and 19 were re-audited against current code and documentation; full prompt blocks were removed from active ledger entries; current statuses remain open as documented.
  • Validation: mkdocs build --strict, script/check-architecture.sh, and git diff --check -- docs/refactoring.md refactoring.md mkdocs.yml passed.

  • [x] 22. Split refactoring plan into context and tracker documents.

  • Evidence: docs/refactoring-context.md now owns target boundary, source-of-truth docs, guardrails, and validation gates; docs/refactoring-tracker.md now owns open items, maintenance items, unfinished audit, and done archive. Legacy refactoring files are pointers.
  • Validation: mkdocs build --strict passed; script/check-architecture.sh passed; diff whitespace check for the refactoring docs and mkdocs.yml passed.

  • [x] 24.1 Port AI prompt endpoints to Java.

  • Evidence: Java Spring Boot implements GET /api/ai-prompt-types, GET /api/ai-prompts, GET /api/ai-prompts/{promptId}, POST /api/ai-prompts, PATCH /api/ai-prompts/{promptId}, and DELETE /api/ai-prompts/{promptId} with DTOs, service validation, not-found handling, and JDBC persistence against the existing ai_prompt_type and ai_prompt tables.
  • Evidence: Python compatibility harness now covers AI prompt JSON payloads, status codes, validation messages, legacy prompt_text input, filtering, update, delete, and missing-resource behavior against the Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='AiPromptControllerTest,AiPromptServiceTest,JdbcAiPromptRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; architecture report was regenerated and script/check-architecture.sh passed.

  • [x] 24.2 Restore Java migration tracker before the next endpoint slice.

  • Evidence: active tracker now lists remaining Java Spring Boot endpoint migration slices 24.3-24.21 based on docs/backend.md, Flask route decorators in reply-pilot-be/reply_pilot_be/views.py, current compatibility harness coverage, and existing Java code under reply-pilot-be/src.
  • Evidence: the unfinished audit now states the actual migration state: /healthz and AI prompt endpoints are implemented in Java; all other endpoint families require focused ports with compatibility contracts.
  • Validation: mkdocs build --strict passed; diff whitespace check for docs/refactoring-context.md and docs/refactoring-tracker.md passed.

  • [x] 24.3 Port backend status and metadata endpoints to Java.

  • Evidence: Java Spring Boot now implements /api/meta and /api/jira/meta from BackendMetadataSettings with compatible status, email, OpenAI, Jira, CME, lead-import, and email_import_status: null fields. /healthz remains compatible through the same controller.
  • Evidence: compatibility harness now checks /api/jira/meta against the current Flask reference, and Java tests cover default metadata plus configured Jira/OpenAI/email-import metadata.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='HealthControllerTest,BackendMetadataSettingsTest,BackendRuntimeSettingsTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.4 Port reply draft storage endpoints to Java.

  • Evidence: Java Spring Boot now implements GET/POST /api/reply-drafts and GET/PATCH/DELETE /api/reply-drafts/{draftId} with service validation, not-found handling, HTTP status mapping, and JDBC persistence against public.email_replay_draft.
  • Evidence: compatibility harness now covers reply draft create, list, detail, update, validation errors, missing update/delete, delete, and missing detail against the current Flask reference.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='ReplyDraftControllerTest,ReplyDraftServiceTest,JdbcReplyDraftRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.5 Port app-state and Jira assignee storage endpoints to Java.

  • Evidence: Java Spring Boot now implements GET/POST /api/user-profiles, GET/PATCH /api/user-profiles/{loginKey}, GET/PATCH /api/app-configuration, GET /api/jira/assignees, GET /api/jira/assignees/{appUserId}/account-id, and PUT /api/jira/assignees/{appUserId} with compatible JSON payloads, validation messages, not-found handling, DB-connectivity error mapping, and JDBC persistence against public.app_user, public.app_configuration, and public.app_user_jira_profile.
  • Evidence: compatibility harness now covers user profile create/list/detail and Gmal box link update, app configuration load/update, Jira assignable list, Jira account-id lookup, Jira profile upsert, missing profile, and app configuration validation against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='AppStateControllerTest,AppStateServiceTest,JdbcAppStateRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.6 Port simple-auth nonce endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/simple-auth/nonces/check and POST /api/simple-auth/nonces/mark-used with compatible used/marked JSON payloads, validation messages, DB-connectivity error mapping, duplicate-mark handling, and expiry purge semantics against public.simple_auth_nonce.
  • Evidence: compatibility harness now covers initial check, mark-used, duplicate mark, expiry boundary behavior, missing nonce, invalid now_ts, missing expires_at, and non-positive expires_at against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='SimpleAuthNonceControllerTest,SimpleAuthNonceServiceTest,JdbcSimpleAuthNonceRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.7 Port backend search proxy endpoint to Java.

  • Evidence: Java Spring Boot now implements GET /api/search, normalizes q, type, and limit, caps limit at 50, forwards requests to reply-pilot-search through HttpSearchClient using SEARCH_API_BASE_URL and SEARCH_TIMEOUT_SECONDS, preserves successful search JSON payloads, and maps search-client failures to HTTP 503 error payloads.
  • Evidence: compatibility harness now covers search success and search-client failure against the current Flask reference backend with a fake search client.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='SearchControllerTest,SearchServiceTest,HttpSearchClientTest,SearchClientSettingsTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.8 Port company and person read-model endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/companies, GET /api/companies/{partyId}, GET /api/companies/{partyId}/tasks, GET /api/people, GET /api/people/{partyId}, and GET /api/parties/{partyId}/activities with compatible pagination, filtering, nested company/person detail payloads, not-found messages, company tasks, party activity lists, and JDBC persistence against the existing party/activity/task tables.
  • Evidence: compatibility harness now covers company/person list and detail, company tasks, party activities, and missing company/person responses against the current Flask reference backend with in-memory read-model stores.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='ReadModelControllerTest,ReadModelServiceTest,JdbcReadModelRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed.

  • [x] 24.9 Port activity, email-thread, and contact read-model endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/activity-emails, GET /api/activity-emails/{activityId}, GET /api/contact-methods/{contactMechId}, GET /api/activities/{activityId}, GET /api/email-threads/{externalThreadId}/companies, GET /api/email-threads/{externalThreadId}/requirements, GET /api/contact-emails/companies, and POST /api/company-mentions with compatible JSON payloads, not-found messages, contact-email parsing, slash-capable email-thread id routing, company mention matching, and JDBC persistence against the existing contact/activity/requirement tables.
  • Evidence: compatibility harness now covers activity email list/detail, contact detail, activity detail, email-thread company and requirement lookups, contact-email company lookup, company mentions, and missing contact/email/activity responses against the current Flask reference backend with in-memory read-model stores.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='ReadModelControllerTest,ReadModelServiceTest,JdbcReadModelRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed; mkdocs build --strict passed.

  • [x] 24.10 Port task read-model endpoints to Java.

  • Evidence: Java Spring Boot now implements GET /api/tasks, GET /api/tasks/{taskId}, GET /api/email-threads/{externalThreadId}/task, and GET /api/email-threads/tasks with compatible pagination, status-code filter normalization including the legacy Drafting Replay typo, assigned-user filtering through Jira account profile, task detail email_threads, optional company filter for email-thread task lookup, sorted thread-id-with-task responses, and missing task errors.
  • Evidence: compatibility harness now covers task list, task detail, email-thread task lookup with and without company filters, unmatched thread task lookup, thread-id batch lookup, and missing task responses against the current Flask reference backend with in-memory read-model stores.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='ReadModelControllerTest,ReadModelServiceTest,JdbcReadModelRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed; mkdocs build --strict passed.

  • [x] 24.11 Port company mutation endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/companies, PATCH /api/companies/{partyId}, POST /api/companies/{targetPartyId}/merge, POST /api/companies/{partyId}/visibility, and POST /api/companies/{partyId}/requirements/{requirementCode}/review with compatible response envelopes, validation messages, duplicate conflict payloads, not-found handling, DB-connectivity error mapping, and JDBC transaction behavior for company create/update/merge/visibility plus manual requirement review facts/state updates.
  • Evidence: compatibility harness now covers company create success, validation errors, duplicate conflicts, update, missing update, visibility, requirement review, invalid review, merge validation, and merge success against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='CompanyMutationControllerTest,CompanyMutationServiceTest,JdbcCompanyMutationRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed; mkdocs build --strict passed.

  • [x] 24.12 Port person/contact/identifier mutation endpoints to Java.

  • Evidence: Java Spring Boot now implements POST /api/people, PATCH /api/people/{partyId}, POST /api/parties/{partyId}/contacts, PATCH /api/parties/{partyId}/contacts/{contactMechId}, DELETE /api/parties/{partyId}/contacts/{contactMechId}, POST /api/companies/{partyId}/identifiers, DELETE /api/companies/{partyId}/identifiers/{identifierId}, and DELETE /api/companies/{companyId}/people/{personId} with compatible response envelopes, validation messages, duplicate/conflict candidate payloads, not-found handling, DB-connectivity error mapping, JDBC transactions, contact soft unlink, identifier delete, and company-person CONTACT_FOR unlink behavior.
  • Evidence: compatibility harness now covers person create/update, validation errors, duplicate person email conflicts, party contact add/update/delete and duplicate-contact conflicts, company identifier add/delete and duplicate conflicts, and company-person unlink success and missing-link errors against the current Flask reference backend.
  • Validation: focused Java tests passed: mvn -f reply-pilot-be/pom.xml -Dtest='PartyMutationServiceTest,PartyMutationControllerTest,JdbcPartyMutationRepositoryTest,ArchitectureRulesTest' test; full Java validation passed: mvn -f reply-pilot-be/pom.xml verify; focused compatibility passed: python3 -m pytest reply-pilot-be/tests/test_api_compatibility.py -q; full backend Python validation passed: python3 -m pytest reply-pilot-be/tests; script/check-architecture.sh passed; mkdocs build --strict passed.

Latest broad validation recorded before this tracker split:

  • python3 -m pytest reply-pilot-app/tests passed.
  • python3 -m pytest reply-pilot-be/tests passed.
  • script/check-architecture.sh passed.