# RedmineUP Local Fork Changelog The installed RedmineUP `redmine_contacts` and `redmine_contacts_helpdesk` plugins are treated as locally maintained legacy code for this Redmine 3.4.4 environment. Before risky edits, archive the current plugin directories in `dist/` and record the purpose, touched behavior, and LAN test result here. ## Current Checkpoint - Baseline: - Redmine `3.4.4` - `redmine_contacts` `4.1.2 PRO` - `redmine_contacts_helpdesk` `3.0.9 PRO` - Strategic direction: - Treat helpdesk/customer data as first-class. - Prefer local-fork plugin edits when they unlock safer search/indexing. - Keep Redmine request paths independent from external worker/index failures. - Implemented locally: - `redmine_event_outbox` plugin with issue/journal/contact events. - Optional helpdesk outbox hooks for `HelpdeskTicket` and `JournalMessage`. - Read-only `helpdesk_search/*` JSON endpoints in the local helpdesk fork. - Standalone contact CLI and read-only helpdesk export/search CLI. - LAN deployment status: - Helpdesk search routes were deployed and route-loaded successfully on the LAN Redmine copy. - Short alias/usage routes were added to avoid noisy routing errors during manual browser testing. - Helpdesk mail import, Helpdesk metadata lookup, default non-email updates, and explicit outbound Helpdesk replies are live-smoke-tested through redMCP. - Helpdesk outbox/worker validation now has a repeatable live script. - Next meaningful milestone: - Run `validate_helpdesk_outbox_worker.py` after outbox or worker changes, then choose the external index target. ## 2026-05-06 - Production Plugin Rollout Via Unified Script - Touched areas: - `plugins/redmine_contacts` - `plugins/redmine_contacts_helpdesk` - `plugins/redmine_event_outbox` - `deploy_redmine_prod_patches.sh` - Purpose: - Apply the same plugin payload shape used by test-host post-import automation to production in a controlled, repeatable sequence. - Keep rollback simple by backing up plugin directories before each apply run. - Behavior/deployment tooling: - Added and used `deploy_redmine_prod_patches.sh` with one-command dry-run, apply, and rollback modes. - Deployment package assembled as `dist/redmine-prod-plugin-rollout-20260506T210606Z.tar.gz` for upload and unpack on production. - Script-level checks include Ruby syntax checks per plugin, plugin migration, Passenger restart, and optional API verification for `include=journals,helpdesk` behavior. - Production result: - Production rollout completed and reported working after deploy. - This confirms the production host now has the local plugin fork updates in place through the scripted deployment path. ## 2026-04-25 - redMCP Native Search, Filtering, And MCP Operations - Touched areas: - `redMCP` - Purpose: - Make Redmine's existing issue filtering and built-in text search explicit before adding external search infrastructure. - Make redMCP runnable as an MCP server for live client testing. - Make the network MCP server easier to debug and restart during local tests. - Behavior changed: - Added `filterIssues()` as a named alias for Redmine's `/issues.json` filtering. - Added `search()` for Redmine's built-in `/search.json` endpoint. - Added `searchIssues()` for issue-only Redmine text search. - Added `projects()`, `listProjects()`, and `project()` for Redmine's `/projects.json` APIs. - Added `users()`, `listUsers()`, `user()`, and `projectMemberships()` for Redmine's user and membership APIs. - Added `ListQueryNormalizer` so MCP list tools accept friendly paging, sorting, status, and date options while preserving raw Redmine `filters`/`params` overrides. - Added `redMCP/bin/test-query-normalizer.php` for no-network checks of Redmine query parameter normalization. - Added a shared MCP dispatcher and transport-specific server wrappers. - Added `redMCP/bin/redmcp-server.php` for stdio MCP clients. - Added `redMCP/bin/redmcp-http-server.php` for bearer-token-protected Streamable HTTP network clients on `/mcp`. - Added PID/status/stop handling to the HTTP server. - Added optional full-argument JSONL debug logging via `--debug-log` or `MCP_DEBUG_LOG`. - Added recursive credential redaction for MCP tool output and debug logs. - Added `redMCP/bin/generate-bearer-token.php`. - Both transports expose Redmine project reads, users, project memberships, filtering/search, issue CRUD, Helpdesk-aware reads, and explicit Helpdesk response tools. - Registered all MCP helper commands as Composer `bin` entries. - LAN test result: - `php -l redMCP/app/RedmineClient.php` passed. - `php -l redMCP/bin/redmcp-server.php` passed. - `php -l redMCP/bin/redmcp-http-server.php` passed. - `php -l redMCP/bin/generate-bearer-token.php` passed. - `composer validate --working-dir=redMCP` passed; Composer emitted PHP 8.5 deprecation notices from system Composer dependencies. - Live stdio MCP framing test passed for `initialize`, `tools/list`, and `tools/call` using `redmine_search_issues` against `fud-helpdesk`. - Live Streamable HTTP test passed for authenticated `initialize`, `tools/list`, and `tools/call` using `redmine_search_issues`. - `redmcp-http-server.php` refused to start without `MCP_SERVER_TOKEN`. - Unauthenticated `/mcp` returned `401`; wrong path returned `404`. - HTTP PID helpers reported stopped/running states, rejected a duplicate start, stopped the live process, detected a stale PID file, and started with `--force`. - Live Streamable HTTP tests passed for `redmine_list_users`, `redmine_get_user`, and `redmine_list_project_memberships`. - `redmine_get_user` redacted the returned Redmine `api_key` field. - `redmine_list_project_memberships` returned direct and inherited memberships for `customer-service`; `fud-helpdesk` returned a valid empty membership list. - `php redMCP/bin/test-query-normalizer.php` passed with coverage for paging, sort shortcuts, status aliases, date presets/ranges, free-text dates, and raw override precedence. - Live Streamable HTTP tests passed for friendly `redmine_list_issues`, `redmine_search_issues`, `redmine_list_users`, `redmine_list_projects`, and `redmine_list_project_memberships` arguments. - Debug logging wrote JSONL records with full project-tool arguments and did not include the bearer token, `Authorization`, or Redmine API key. - Token generation passed default, `--bytes 48`, and `--env-line` modes. - `redmine_list_projects` returned three projects from 117 total. - `redmine_get_project` returned `fud-helpdesk` by identifier and by id 117. - The live MCP tool calls returned issue search results from seven total for `redMCP-smoke`. ## 2026-04-25 - Test Helpdesk Credential Sanitization - Touched areas: - Test instance post-import tooling - Post-import validation - Purpose: - Ensure production database imports cannot leave real Helpdesk POP3/SMTP credentials on projects where Helpdesk is disabled but settings rows still exist. - Behavior changed: - `reset_helpdesk_mail_settings.py` now rewrites Helpdesk mail settings for every active project, not only projects with `contacts_helpdesk` enabled. - `validate_test_instance.py` checks every active project for Mailpit Helpdesk settings. - LAN test result: - `./reset_helpdesk_mail_settings.py --dry-run` matched 52 active projects. - `./reset_helpdesk_mail_settings.py` updated 1,092 setting rows across 52 active projects. - `./validate_test_instance.py` passed and reported 52 active projects with matching Mailpit Helpdesk settings. ## 2026-04-25 - Outbox Worker Processing Policy - Touched areas: - External outbox worker - Worker operations documentation - Purpose: - Make the worker usable for bounded JSONL publishing before a message bus is chosen. - Add queue visibility and explicit processed-row cleanup for test-instance maintenance. - Behavior changed: - Default worker output is `/tmp/redmine-outbox/derived_documents.jsonl`. - `--status` reports pending, ready, locked, failed, and processed counts. - `--purge-processed-days N` previews processed-row cleanup; `--apply-purge` is required to delete rows. - Normal processing still marks rows processed only after successful output. - LAN test result: - `./redmine_outbox_worker.py --status` reported 100 total rows, 100 ready, 0 locked, 0 failed, and 0 processed before the processing run. - `./redmine_outbox_worker.py --dry-run --batch-size 5` previewed the first five rows without marking them processed. - `./redmine_outbox_worker.py --batch-size 5` processed five rows and wrote six JSONL documents to `/tmp/redmine-outbox/derived_documents.jsonl`. - Follow-up status reported 95 pending, 95 ready, 0 locked, 0 failed, and 5 processed. - `./redmine_outbox_worker.py --purge-processed-days 30` previewed zero rows. - Follow-up `./validate_helpdesk_outbox_worker.py` passed with controlled Helpdesk issue `#39875`; final status reported 118 total rows, 113 ready, 0 locked, 0 failed, and 5 processed. ## 2026-04-25 - Helpdesk Outbox Worker Validation - Touched areas: - Helpdesk test tooling - External outbox worker validation docs - Purpose: - Make Helpdesk outbox coverage repeatable after database refreshes and worker changes. - Validate worker enrichment against a real Mailpit-imported Helpdesk ticket without claiming rows or marking them processed. - Behavior checked: - Controlled Helpdesk import produces `helpdesk_ticket.created`, `helpdesk_ticket.updated`, `journal_message.created`, `journal.created`, and `issue.updated` rows. - Worker dry-run enrichment emits `event`, `ticket`, and `message` documents. - Derived message documents expose `has_bcc_address` but do not expose raw `bcc_address`. - LAN test result: - `./validate_helpdesk_outbox_worker.py` passed against `fud-helpdesk` and `fud-nohelpdesk`. - Latest documented passing run created and closed controlled Helpdesk issue `#39873`. - Observed outbox rows: 1 `helpdesk_ticket.created`, 3 `helpdesk_ticket.updated`, 1 `journal_message.created`, 3 `journal.created`, and 2 `issue.updated`. - Worker dry-run enrichment produced 10 `event`, 6 `ticket`, and 2 `message` documents without claiming or marking rows processed. ## 2026-04-24 - Helpdesk/redMCP Smoke Validation - Touched areas: - `redMCP` - Helpdesk test tooling - Purpose: - Prove the LAN test instance can import a Helpdesk email, expose its Helpdesk metadata through redMCP, update a non-Helpdesk control issue, send an explicit Helpdesk reply, and verify outbound delivery through Mailpit. - Make customer-visible Helpdesk email opt-in in redMCP. - Behavior clarified: - `RedMCP\RedmineClient::updateIssue()` uses the normal Redmine REST API and does not send a Helpdesk email by default. - `updateIssue(..., ['send_helpdesk_email' => true])` and `sendHelpdeskIssueResponse()` deliberately use the Helpdesk email path. - LAN test result: - `./helpdesk_smoke_test.py` passed against `fud-helpdesk` and `fud-nohelpdesk`. - The smoke test verifies that a default issue update does not send Mailpit mail, while an explicit Helpdesk response does. - Latest documented passing run created and closed controlled Helpdesk issue `#39871`. ## 2026-04-24 - POP3 Get Mail Compatibility Fix - Touched plugin: - `redmine_contacts` - `redmine_contacts_helpdesk` - Purpose: - Fix Helpdesk POP3 retrieval on the LAN test host when Ruby 2.5 raises `FrozenError: can't modify frozen String` inside `Net::POP3`. - Allow Helpdesk outbound mail to use Mailpit's unauthenticated SMTP listener. - Behavior changed: - Changed POP3 message retrieval from `msg.pop` to `msg.pop(String.new)` so Ruby's POP3 code appends chunks into an explicit mutable destination string. - This does not change message handling semantics; it only avoids relying on Ruby's default empty string argument being mutable. - Changed Helpdesk SMTP delivery option construction to omit `authentication`, `user_name`, and `password` when the project SMTP authentication setting is blank. - LAN test result: - Deployed to `/usr/share/redmine/plugins/redmine_contacts`. - `HelpdeskMailer.check_project(Project.find("fud-helpdesk").id)` completed successfully and processed 1 message. - Deployed to `/usr/share/redmine/plugins/redmine_contacts_helpdesk`. - Mailpit rejected `AUTH PLAIN` with `502 5.5.1 Command not implemented`. After blanking SMTP auth settings and omitting auth options, a Helpdesk test mail for issue `#39863` was delivered to Mailpit. ## 2026-04-21 - Helpdesk Search Foundation - Archives created before plugin edits: - `dist/redmine_contacts-4.1.2-local-before-helpdesk-search-20260421T215548Z.tar.gz` - `dist/redmine_contacts_helpdesk-3.0.9-local-before-helpdesk-search-20260421T215548Z.tar.gz` - Touched plugins: - `redmine_contacts_helpdesk` - `redmine_event_outbox` - Purpose: - Make helpdesk ticket and message identity available to external search and indexing workers. - Avoid relying on Redmine issue author when helpdesk-created tickets use `Anonymous`. - Behavior changed: - Added read-only `helpdesk_search/*` JSON endpoints guarded by the existing `view_helpdesk_tickets` permission. - Added optional outbox hooks for `HelpdeskTicket` and `JournalMessage`. - Payload/content policy: - Include ids, source, direction, message id, and non-body address metadata. - Do not copy email bodies, private note text, attachments, or BCC addresses into event rows or the read API. - LAN test result: - Pending. Validate on the LAN Redmine copy by creating/updating a controlled helpdesk ticket and journal message, checking `event_outbox_events`, and calling the new `helpdesk_search/*` endpoints with a user/API key that has `view_helpdesk_tickets`.