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.reply-pilot-workeruses Java 21 and SpringThreadPoolTaskScheduler, with separate@Componentjob classes, JDK HTTP client/server and Jackson JSON. Seven periodic jobs use@Scheduled(fixedDelay)measured from completion; Gmail watch and import use Spring triggers for their dynamic retry deadlines. Status/metrics and the HTTP status server are separate from job execution. Preserve all nine HTTP job flows, per-job non-overlap, Gmail retry/import state, env defaults,/healthz,/statusz, and the heartbeat JSON. It remains a separate single-instance service without DB or Google credentials. Python worker code is retired.- 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-google -> reply-pilot-be.reply-pilot-googleowns technical 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 through the canonicalreply-pilot-googleDNS name and is not the durable cache owner ingmail_servicemode.
Accepted Google integration final state as of 2026-08-18:
- Use
reply-pilot-googleas the sole Google integration and credential-owning module. The source/runtime rename, repository consumer switch, and repository Calendar ownership move are complete. The production Gmail and Calendar cutovers and the legacy naming cleanup are also complete. - Preserve the existing Gmail HTTP contracts, mailbox cache, watch/Pub/Sub
behavior, technical Gmail credential, host port, and consumer-visible
behavior.
reply-pilot-googleis the only supported module and internal DNS name. - The stable browser/runtime call direction is
reply-pilot-app -> reply-pilot-be -> reply-pilot-google. Browser-facing modules and other internal consumers call narrow Gmail or Calendar HTTP APIs; they never receive Google access or refresh tokens. reply-pilot-googleowns Google OAuth authorization-code exchange, encrypted per-user Google grants, access-token refresh, revocation, Google API calls, Google-specific retry/quota handling, and the existing technical Gmail mailbox credential.- Keep the technical Gmail mailbox credential separate from per-user Google
grants. The technical credential remains a SOPS-managed runtime artifact;
the existing per-user Calendar grant remains encrypted in
app_user_google_calendar_profileunless an implementation slice proves a schema change is necessary. - Only
reply-pilot-googlemay decrypt or refresh per-user Google grants.reply-pilot-becontinues to own Reply Pilot authentication, authorization, UI-facing orchestration, stable app-facing contracts, and persistence of the opaque encrypted Calendar credential envelope, but consumes Google capabilities over authenticated internal HTTP. - The Google Cloud project, OAuth branding/consent configuration, runtime module, OAuth client, technical Gmail credential, and per-user grant are distinct concepts. The migration must not merge credentials merely because they share one Google Cloud project.
- Production runs only the canonical Google module and path. Rollback uses the current canonical deployment and SOPS-managed credentials rather than a parallel legacy-named container or stale mailbox-cache copy.
Accepted production IČO target state as of 2026-08-20:
reply-pilot-behas one validation and ownership path for manual company create/edit. It removes whitespace, validates new or changed Czech IČO as eight ASCII digits, and writes only theCOMPANY_REGISTRATION_NUMBERidentifier. The public JSON/form property remainscompany_registration_number.- Authoritative IČO claims use the existing global identifier uniqueness and
fail the whole mutation with a field-level conflict when another party wins
ownership, including concurrent create/update races. They do not use
ON CONFLICT DO NOTHING. - Lead import and CME synchronization use the same optional-value policy and target ownership operation. Malformed or incompatible source values remain in their audit record with an explicit error; they do not become canonical identifiers. CME isolates such errors with a row savepoint so other rows in the same synchronization can continue.
- Lead and CME IČO matching use only the target identifier. A valid imported value may fill an empty target; an existing different authoritative value is not overwritten.
- Company merge checks both locked parties before moving related data. Zero or one distinct authoritative IČO is deterministic; two different authoritative IČOs reject and roll back the merge.
- Expand changeset
0058safely backfills valid unambiguous legacy Czech IČO, moves every unresolved legacy value into audit-only identifier typeLEGACY_UNRESOLVED_COMPANY_REGISTRATION_NUMBER, and enforces canonical format plus at most one canonical IČO per party. Its scoped normalized value prevents quarantine rows from becoming matching or ownership keys. - Backend, search, reports, import, merge, and CME synchronization read and
write only
party_identifier. - The canonical Czech scheme remains
COMPANY_REGISTRATION_NUMBER, with the normalized value restricted to eight ASCII digits. Verified foreign commercial-register entries useCOMMERCIAL_REGISTER_NUMBER; their normalized value includes country, issuing register, section, and number so the existing global uniqueness has a complete issuer scope. DIČ and other identifier families remain separate. - Expand changeset
0058and targeted contract changeset0059are applied in production. Before the drop, drift was zero. After it, the legacy column, trigger, and function are absent; 2,964 canonical rows and 573 raw-preserved quarantine rows remain, with zero invalid canonical values, duplicate canonical values, or parties with multiple canonical values. - A verified pre-contract logical backup is retained. Company list, detail, and search smoke checks returned HTTP 200, services remained healthy, and three post-contract CME runs linked 2,669 source rows without creating a company.
Accepted VAT expand target for RP-4122:
vat_jurisdictionis the versioned jurisdiction/country/local-name catalog; its explicit exceptional mappings areEL -> GR,XI -> GB, and Czechlocal_name = DIČ.party_vat_registrationkeeps raw and normalized values, allows at most one row per party and jurisdiction, and deliberately has no global uniqueness on normalized VAT value.- the shared backend syntax policy uppercases first, removes only whitespace,
-,., and/, then rejects any remaining unsupported characters, unknown prefixes, or jurisdiction-invalid formats. Syntax validation does not call live VIES. - expand item 34.1 is additive: company payloads retain
tax_identifierand addvat_registrations[]; the authenticated catalog API is read-only. - compatibility item 34.2 routes valid manual create/edit, lead promotion, and CME promotion through one policy and transactionally mirrors raw scalar plus normalized jurisdiction row. VAT equality is not ownership, a save conflict, or an automatic lead/CME match. Same-jurisdiction merge disagreement stops before related data moves.
- inventory/backfill item 34.3 is a one-off backend command that dry-runs by
default, requires
--apply, classifies every non-empty organization scalar, applies the refined merged-party rule, and refuses unexplained target drift; after item 34.5 it no longer queries legacyTAX_IDENTIFIERrows. - legacy identifier cutoff item 34.5 filters those rows from backend/app/search,
removes the manual supplier-identifier option, moves wholesale VAT lookup to
party_vat_registration, and stages reversible contract changeset0068. Immutable supplier evidence and AI extraction classifications remain explicitly non-authoritative. - production item 34.6 deployed those consumers, recovery-tested backup
reply-pilot-20260821T105254Z.sql.gz, and applied only0068. Its audit snapshots all 318 deleted legacy rows and records318 -> 0; the zero count held after a company write and scheduled CME jobs. The 1,138 non-empty organization scalars, 1,067 VAT registrations, and scalar column were preserved. - backend cutover item 34.7 makes the collection authoritative for company
create/edit/list/detail/search, lead and CME promotion, merge, and AI draft
context. The temporary scalar JSON adapter is derived from zero or one
collection row, rejects scalar mutation when multiple rows exist, and never
reads or writes
party_organization.tax_identifier. Per-entry mutation errors retain their collection index. Equal normalized values on separate effective parties remain legal and never select an owner. - browser/search/batch cutover item 34.8 edits and renders the complete collection from the authenticated jurisdiction catalog, preserves submitted row order and indexed errors, indexes every registration, and treats VAT in wholesale matching only as corroboration for an existing non-VAT match.
- production cutover item 34.9 deployed backend, search, and app in dependency order, completed a full company reindex, accepted the authenticated form and a data-preserving company save, and verified an existing equal VAT value on two active parties remains non-unique. No production party was given a second registration and no lead, AI, or merge business workflow was manufactured without an approved target; those paths retain deterministic test evidence.
- scalar-removal item 34.10 deletes the temporary JSON/form/DTO adapter and the
completed one-off backfill command, leaving no organization-scalar runtime
consumer. Contract changeset
0069is staged behindcontextFilter="contract"with reconciliation preconditions and a scalar-restoring rollback. Production item 34.11 recovery-tested a fresh backup and applied only0069; the party, organization, and VAT counts remained4929 / 3895 / 1068, the scalar column is gone, and unrelated contracts0053and0054remain pending. Source snapshots and immutable evidence remain non-authoritative.
Accepted email-thread/company resolution target for RP-4121:
reply-pilot-beowns one canonical dynamic resolver over normalized activity data.reply-pilot-appconsumes its HTTP contracts and does not independently merge company-name mentions, activity-party links, or contact matches.- The resolver collects deduplicated external addresses from
from,to,cc, andbccacross every message in a thread. A visible company matches only by an exact active company email contact, an exact active email contact of a person currently linked throughCONTACT_FOR, or an exact company-ownedDOMAIN. It supports zero, one, or multiple companies per thread. - A
DOMAINis an explicit manual assertion of company ownership. Email ingest and task creation never infer or create it, technical ownership verification is not part of this target, and no runtime domain/address blacklist is added. Existing invalid shared-providerDOMAINrows identified by the production analysis are deleted before domain matching becomes canonical. - Unknown addresses do not automatically create a person or company. They are
presented on an inbox-adjacent page grouped by normalized address, with thread
count, last occurrence, and links to the affected threads. Resolution reuses
the existing person, company, contact, and
CONTACT_FORmutations. - Marking a thread reviewed without assignment is durable per thread, not per address. A later unresolved thread from the same address must appear again.
- Company-name text matching and primary/participant activity links alone are not canonical company signals. Task-to-thread linkage remains independent of the derived company set.
- All 25 legacy explicit links were resolved on 2026-08-21: three were discarded
by owner decision and 22 were independently reproduced by canonical rules
before their rows were removed.
email_thread_company_linkis empty and no replacement override model is planned. - Reconciliation verifies dynamic resolver results and cross-screen parity; it
must not recreate a materialized thread/company mapping table. Contract
changeset
0054remains a later production step after the retained 25 audit events have an explicit delete-or-archive decision.
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 apply a task-derived read-only
visibility exception: the local assignee or mapped Jira assignee of any active
Jira task can read only that linked company and active task. Company
collaboration remains limited to active assigned
reply_pilot_taskitems; unrelated companies, unassigned tasks, andDonetasks stay inaccessible. - 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-08-18: Gmail mailbox data is owned by
reply-pilot-google. The Google 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, and bothreply-pilot-beandreply-pilot-jira-reportsuse the canonicalhttp://reply-pilot-google:5000endpoint. Production now runs the canonical Google container, path, image and DNS alias with no legacy-named runtime compatibility. 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-08-18: Calendar OAuth exchange, refresh,
revocation, credential encryption/decryption, and public Google Calendar API
calls live under
reply-pilot-google.reply-pilot-bekeeps its existing app-facing Calendar routes and database table, but treats the stored grant as an opaque encrypted envelope and sends it to the Google module over internal HTTP. BE uses sharedGOOGLE_API_BASE_URLandGOOGLE_API_TIMEOUT_SECONDSsettings for Gmail and Calendar module calls, and authenticates Calendar-only internal endpoints withGOOGLE_API_TOKEN; Google-specific mailbox configuration such asGMAIL_TOKEN_FILEremains Gmail-prefixed. Local and production Calendar OAuth values are configured in the Google SOPS env while BE has none of the Calendar client or encryption secrets. Production has activev1credential envelopes updated through the canonical Google module. - 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.mdreply-pilot-google/as the current Google/Gmail implementationreply-pilot-be/src/main/java/cz/replypilot/be/controller/GoogleCalendarController.javareply-pilot-be/src/main/java/cz/replypilot/be/service/GoogleCalendarService.javareply-pilot-google/src/main/java/cz/replypilot/google/calendar/GoogleCalendarController.javareply-pilot-google/src/main/java/cz/replypilot/google/calendar/GoogleCalendarService.javareply-pilot-google/src/main/java/cz/replypilot/google/calendar/GoogleCalendarSettings.javareply-pilot-db/migrations/0049_create_app_user_google_calendar_profile.sql- 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.
- Preserve the existing Gmail API, cache layout, watch/Pub/Sub behavior, account identifier, and technical token bytes during the module rename.
- Do not let
reply-pilot-be,reply-pilot-worker, reporting modules, or the browser app read or decrypt Google credential rows. They consume explicit internal capability APIs fromreply-pilot-google. - A runtime contract rename must update
AGENTS.md,docs/remote-server.md, anddocs/container-runtime-contract.mdtogether, plus module README, root orchestration, SOPS documentation, and architecture diagrams.
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
mvn -f reply-pilot-google/pom.xml verifyfor Google module changes- focused and full backend validation for every Google HTTP client or Calendar ownership change
python3 -m pytest reply-pilot-jira-reports/testsandmvn -f reply-pilot-worker/pom.xml testwhen their Google/Gmail consumer contracts change- Docker build, non-root identity, healthcheck, internal DNS alias, Gmail cache status, watch status, and read-only weekly-report smoke validation for the renamed module
- SOPS decrypt/extract verification for every renamed or changed local/prod Google module secret; never print credential values as validation evidence
- production validation of container health, mailbox/cache status, Gmail read operation, Calendar status and the report consumer path after Google runtime changes