Files
gochat/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md
T

178 KiB

Chatwoot Parity Development Plan

Updated: 2026-06-05

Goal

Build GoChat as a Go backend that can directly reuse the frontend from reference/chatwoot. The backend API, data contracts, side effects, permissions, and runtime behavior must match the local reference/chatwoot repository first. Existing docs are secondary when they conflict with the reference implementation.

Confirmed Decisions

  • Frontend: reuse Chatwoot frontend directly. Backend compatibility is mandatory.
  • Baseline: reference/chatwoot is the source of truth for routes, controllers, models, serializers, jobs, and service behavior.
  • Immediate order: keep go test ./... green first, then deepen Chatwoot behavior parity.
  • Search: final implementation must use Meilisearch. DB/LIKE search is not acceptable as the final engine.
  • Enterprise scope: exclude SSO/SAML/LDAP/OIDC. Include the remaining paid features already present in planning and code: SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, assignment policies, and related limits/workflows.

Current Baseline

  • Latest implementation checkpoint: ef3a909 feat(csat): send surveys on resolved conversations.
  • Latest documentation checkpoint before this update: bda5ad5 docs: expand parity execution plan.
  • Worktree status at this planning checkpoint: clean after ef3a909; next active slice is B8 CSAT download/export parity.
  • go test ./... passes.
  • Route dump succeeds with TOTAL: 830 after adding the Chatwoot-compatible applied-SLA index route.
  • Route parity artifacts now exist under docs/parity/ and are generated by cmd/route_parity.
  • Tracked frontend-critical route audit covers 277 Chatwoot routes: 270 exact, 0 method-compatible, 7 parameter-compatible, 0 missing. The 7 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits; the external URL shape is equivalent.
  • /api/v1/widget stubs are burned down and public inbox/contact/conversation/message core flows are backed by real handlers.
  • Handler test stability fixes are committed into the baseline before feature parity work continues.
  • .codegraph/ is generated indexing output and is not part of tracked product code.

Planning Control Board

This document is the active tracker. The older Hermes plans remain source notes only; each future slice must be represented here before or inside the matching implementation commit.

Current operating rules:

  • Implement from the local reference/chatwoot source first, then update this tracker with the exact files or behavior inspected.
  • Keep each checkpoint small enough to verify with focused tests plus go test ./....
  • Commit implementation and documentation together unless the checkpoint is explicitly documentation-only.
  • Do not mark a slice Done while side effects, async jobs, permission gates, payload shape, or frontend smoke coverage are still untracked.
  • Search-related work must target Meilisearch; DB fallback is only a local development escape hatch.
  • SSO, SAML, LDAP, and OIDC are excluded; all other enterprise features remain in scope.

Next ordered checkpoints:

Order Slice Required outcome Primary verification
1 B7.6 SLA business-hours timer math and breach notification fan-out match Chatwoot or have named job-boundary gaps. Focused applied-SLA lifecycle/notification tests, service package tests, go test ./....
2 B8 CSAT account/public/report/send behavior matches Chatwoot enough for the reused frontend. CSAT handler/service/listener tests, public update tests, CSV/report tests.
3 B9 Automation rules and macros mutate conversations through real side effects with execution logs. Rule listener tests, macro execute tests, action-service retry/log tests.
4 B10 Audit, CustomRole, and remaining InboxLimit surfaces enforce Chatwoot admin behavior. Permission matrix tests, audit writer/list tests, inbox/account limit tests.
5 B11 Captain/Copilot enterprise screens have real persistence and safe LLM feature gates. Captain/Copilot handler/service fixtures and disabled-state tests.
6 B12 Reused Chatwoot frontend smoke runs repeatably against GoChat. Checked smoke command plus gap report under docs/parity/.

Execution Snapshot

Phase Name Status Blocking gaps
Phase 0 Test and route baseline Done none
Phase 1 Meilisearch search engine Review B6 payload parity, optional live gate, and DB-fallback hardening are implemented; an actual live Meilisearch run is optional and environment-dependent
Phase 2 Route and controller parity audit Doing Ruby/Bundler unavailable, so Chatwoot route extraction currently uses static routes.rb fallback
Phase 3 Data and serializer parity Doing JSON fixture coverage is partial and still endpoint-family based
Phase 4 Enterprise feature completion Doing B7 is in Review; B8 account-side CSAT report payloads, public submit/update idempotency, and resolve-triggered survey send are implemented; CSAT download depth remains active
Phase 5 Background jobs and integrations Planned durable worker choice and job parity are open
Phase 6 Core placeholder burn-down Doing account/contact/conversation/message/inbox placeholder groups remain broad
Phase 7 Verification harness Planned search live gate and reused-frontend smoke harness are not complete

Tracking Artifacts

Artifact Purpose Update rule
docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md Master execution plan and status ledger. Update in every parity commit.
docs/parity/gochat_routes.txt Generated Go route inventory. Regenerate after every route change.
docs/parity/route_parity.md Generated tracked route comparison against reference/chatwoot/config/routes.rb. Regenerate after every route-tracking or route-registration change.
cmd/route_parity Static route parity generator. Extend whenever a new Chatwoot route group enters the tracked critical set.
.hermes/plans/2025-05-24-global-search-meilisearch.md Original Meilisearch implementation plan. Mine for context only; this document is now the active tracker.
.hermes/plans/2026-05-24-automation-macro-csat.md Original automation, macro, and CSAT implementation plan. Mine for context only; this document is now the active tracker.
docs/requirements/*.md Reference notes extracted from Chatwoot modules. Use as helper material after checking reference/chatwoot directly.

Commit Ledger

This ledger records the committed parity checkpoints that future slices should build on. Each implementation commit must update this document again before it is considered closed.

Commit Scope Verification summary Follow-up state
6aa62c6 docs: consolidate chatwoot parity roadmap Promoted Hermes-era plans into this master tracker; locked user decisions; added milestone, slice, enterprise, and webhook provider tracking. Documentation-only checkpoint. B1/P6.7 selected as next implementation slice.
9e3f561 feat(webhook): align chatwoot ingress routes Added Chatwoot public webhook paths for Twitter, Telegram, LINE, SMS/Twilio, WhatsApp, Instagram, TikTok, and Shopify; removed generic fallback success masking. go test ./...; route dump regenerated with TOTAL: 801; route parity stayed 251 exact, 0 missing. Provider-specific lookup and verification moved to review.
bc7da9e feat(webhook): implement instagram and shopify ingress Implemented Instagram verify/event handling, Shopify HMAC/redact/event forwarding, and WhatsApp verify-token/app-secret signature corrections. Focused webhook tests, go test ./..., route dump TOTAL: 801, route parity 251 exact, 0 missing, git diff --check. Remaining P6.7 work is durable incoming-message persistence and provider dispatch parity.
5eb726b feat(webhook): persist incoming provider messages Added provider incoming-message persistence boundary and wired Telegram, LINE, SMS/Twilio, WhatsApp, Facebook/Instagram, and TikTok parsed incoming messages into ContactInbox, Conversation, and Message storage. Focused webhook/channel tests passed; full go test ./... passed. Continue P6.7 review with delivery/read receipt status updates and async dispatch/events.
66ecabb feat(webhook): persist provider receipt statuses Added delivery/read/failed status persistence for Twilio, WhatsApp, Facebook/Instagram, and TikTok receipt events. Focused webhook/channel tests passed; full go test ./... passed. Continue P6.7 review with async dispatch/events and broader provider fixture assertions.
06b999b feat(webhook): dispatch persisted provider events Wired webhook incoming persistence and status updates into the existing channel.Dispatcher fan-out boundary. Focused webhook tests passed; full go test ./... passed. Continue P6.7 review with broader provider fixture assertions.
55295dd test(webhook): cover provider ingress persistence fixtures Added provider-specific webhook persistence fixture assertions for LINE, Twilio SMS, WhatsApp, Instagram, and TikTok, extending the existing Telegram fixture. Focused webhook tests passed; full go test ./... passed. Continue P6.7 review with signature edge fixtures and final provider Done/Review classification.
0ea7a08 feat(webhook): verify tiktok ingress signatures Added Chatwoot-style TikTok Tiktok-Signature HMAC verification using TIKTOK_APP_SECRET, timestamp freshness, and invalid-signature rejection coverage. Focused webhook tests passed; full go test ./... passed. Continue P6.7 review with LINE/WhatsApp missing-signature edge fixtures and final provider classification.
0439f3b feat(webhook): require line ingress signatures Tightened LINE webhook signature parity so configured channel_secret requires a present and valid X-Line-Signature, with missing-signature rejection coverage. Focused webhook tests passed; full go test ./... passed. Continue P6.7 review with WhatsApp missing-signature edge fixtures and Shopify/Twitter final classification.
1c30933 test(webhook): cover whatsapp ingress verification Added WhatsApp route-level verification coverage for verify-token challenge echo, signed Cloud API POST persistence, and missing Meta signature rejection without persistence. Focused webhook tests passed; full go test ./... passed. Continue P6.7 review with Shopify/Twitter final classification.
ebd8f08 feat(webhook): finalize provider ingress parity Finalized P6.7 provider classification with Twitter CRC prefix parity, Twitter route-level webhook tests, Instagram missing-signature rejection coverage, and Shopify real-handler classification. Focused webhook/API tests passed; full go test ./... passed. Move next to serializer/API parity queue.
56a8c5b feat(profile): align chatwoot user serializer Aligned /api/v1/profile, profile update, avatar delete/update, availability, auto-offline, and reset-access-token responses to Chatwoot's raw api/v1/models/_user.json.jbuilder shape. Added user/account/account_user fields and personal access-token persistence needed by the serializer. Focused profile/API tests passed; sandboxed go test ./... hit socket restrictions; escalated full go test ./... passed; git diff --check passed. Continue B2 with /auth/sign_in, /auth/validate_token, auth headers/cookie contract, and current-user payload fixtures.
e59f9f3 feat(auth): add chatwoot session routes Added root /auth/sign_in, /auth/validate_token, /auth/sign_out, and /auth/password routes consumed by the reused Chatwoot frontend. Sign-in now emits DeviseTokenAuth-compatible access-token, token-type, client, expiry, and uid headers and returns raw { data: user }; validate-token returns { payload: { success, data } }. Focused auth/profile tests passed; route dump regenerated with TOTAL: 805; route parity remained 251 exact, 0 missing; escalated full go test ./... passed; git diff --check passed. Continue B2 with profile settings update parity: display_name, message_signature, ui_settings, password update, and multipart avatar handling.
7aa3362 feat(profile): support chatwoot settings updates Completed profile settings update parity for display_name, message_signature, ui_settings, phone_number custom attributes, password changes with current-password verification, and multipart profile[...] FormData including avatar file input. Focused profile tests passed; escalated full go test ./... passed; git diff --check passed. B2 core auth/profile fixtures are complete enough to move next to conversations/messages serializer parity.
9f89cbf feat(conversations): align chatwoot message serializers Added Chatwoot conversation/message serializer boundary for dashboard list/show/filter, message index, message create/update/retry, status toggle payloads, display-id route resolution, outgoing/private message defaults, echo_id, content_attributes, and conversation/message parity migration fields. Focused conversation/message handler tests passed; full go test ./... passed; git diff --check passed. Continue B3 with delete/update status parity, multipart attachment create, team assignment response parity, and deeper message finder before moving to contacts/companies.
a465bbe feat(conversations): finish message mutation parity Finished the B3 mutation gap set: message delete now returns the Chatwoot deleted-message serializer and clears attachments; message status update supports status/external_error with API-inbox-only enforcement; MessageFinder now supports latest, before, after, and between windows; multipart attachments[] create attachment rows and serialize them in message payloads; team assignment returns the raw team payload. Focused handler/service/repository tests passed; full go test ./... passed; git diff --check passed. B3 core dashboard message flows move to review; continue with B4 contacts/companies while tracking deeper delivery/storage side effects.
3481597 feat(crm): align contact company payloads Started B4 contacts/companies parity: added Chatwoot-shaped { meta, payload } and { payload } CRM serializers, strict empty-query 422 handling, contact create { contact, contact_inbox } envelope, selected custom-attribute deletion, companies nested company params, company contacts on contacts.company_id, relation search/add routes, and Rails-compatible PATCH routes. Focused contacts/companies tests passed; handler/service/repository tests passed; route dump regenerated with TOTAL: 809; route parity remained 251 exact, 0 missing; sandboxed full go test ./... failed on socket restrictions, escalated full go test ./... passed; git diff --check passed. Continue B4 with merge/import/export/labels/avatar/company destroy-custom-attributes, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures.
7a033e2 feat(crm): complete contact label avatar gaps Completed the next B4 CRM route/behavior slice: added contact_labels persistence, Chatwoot { payload: labels } contact label list/update endpoints, contact list/search label filtering via contact labels, contact/company avatar delete responses, company destroy_custom_attributes, and tracked trailing-slash nested route aliases. Focused CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with TOTAL: 817; route parity is 261 exact, 0 missing; sandboxed full go test ./... failed on local socket restrictions, escalated full go test ./... passed; git diff --check passed. Continue B4 with contact merge, import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures.
1e3bf47 feat(crm): align contact merge action Completed B4 contact merge parity for the reused dashboard action: added the exact POST /api/v1/accounts/:account_id/actions/contact_merge route, changed merge responses to the raw Chatwoot contact model serializer, moved conversations/contact inboxes/notes/messages from mergee to base, preserved base attributes while filling blanks from mergee, and covered legacy lowercase contact message senders. Focused merge/CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with TOTAL: 818; route parity is 262 exact, 0 missing; full go test ./... passed; git diff --check passed. Continue B4 with import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures.
8eed2f1 feat(crm): persist contact data imports Advanced B4 import/data-import parity: added Chatwoot-shaped data_imports fields and migration, changed contacts import missing-file errors to 422, successful imports to empty 200 OK, persisted import lifecycle states/counts/errors, and made CSV import merge/update existing contacts by identifier/email/phone with labels/custom attributes. Focused import tests passed; handler/service/repository/router package tests passed; full go test ./... passed; git diff --check passed. Continue B4.6 with contacts export async artifact/download/notification parity, then notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures.
47c2b80 feat(crm): persist contact export artifacts Advanced B4 export parity: POST /contacts/export now creates a persisted contact_exports artifact, stores Chatwoot default/requested CSV columns with UTF-8 BOM, supports label/filter export inputs, exposes a local download URL, and creates a user notification with export metadata. Focused export tests passed; handler/service/repository/router package tests passed; regenerated route dump TOTAL: 819; route parity remains 262 exact, 0 missing; full go test ./... passed; git diff --check passed. Continue B4 with real email delivery for export completion if required by mailer infrastructure, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures.
af57482 feat(crm): align contact note payloads Advanced B4 notes parity: contact notes now return raw Chatwoot note arrays/objects with nested agent payloads, flat and nested note.content requests are accepted, create/update/show avoid the Go API envelope, delete returns empty 200 OK, note deletion hard-deletes like Chatwoot, company note payloads include user metadata, and the PUT note update route is registered. Focused notes/API tests passed; full go test ./... passed; route dump regenerated with TOTAL: 820; tracked route parity remains 262 exact, 0 missing; git diff --check passed. Continue B4 with nested contact/company conversations payload depth, CRM Meilisearch search shape, frontend smoke fixtures, and deferred export email delivery.
fb775b7 feat(crm): align nested conversation payloads Advanced B4 nested CRM conversation parity: contact and company conversations now render Chatwoot's { payload: [conversation partial] } shape with nested meta.sender, messages, last_non_activity_message, account/inbox IDs, timestamps, priority, labels, and unread fields from the shared conversation serializer. Company conversations now follow the enterprise controller's latest-20, last_activity_at DESC behavior instead of paginated raw models. Focused nested conversation handler tests passed; focused handler/service/repository/router tests passed; escalated full go test ./... passed after sandbox socket limits blocked local-only auth/middleware tests; git diff --check passed. No route changes; route dump remains TOTAL: 820. Continue B4 with CRM Meilisearch search shape, stricter import label validation, frontend smoke fixtures, and deferred export email delivery.
202da19 feat(crm): route crm search through meilisearch Advanced B4 CRM search parity: contact and company service search now use the configured search service for non-empty queries, preserving Chatwoot CRM { meta, payload } serializers by resolving Meilisearch result IDs back through account-scoped repositories. Bootstrap wires the Meilisearch-backed search service into contact/company services, and DB search remains only the no-reader/test fallback. Global search DB fallback now includes companies so the company document shape has a repository equivalent. Focused search/service/repository/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. Continue B4 with stricter import label validation, frontend smoke fixtures, and deferred export email delivery.
653db81 feat(crm): validate imported contact labels Completed the remaining B4 import validation gap: contact CSV import now validates labels against existing account labels before creating/updating contacts, rejects rows with unknown labels, records failed row counts, preserves canonical existing label names, and no longer creates arbitrary account tags from import input. Focused import/service/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. Continue B4 with frontend CRM smoke fixtures and deferred export email delivery.
c2766fe feat(crm): email contact export completions Completed B4 contact export email delivery: the completed export artifact now triggers a Chatwoot-style contact_export_complete mailer boundary for the requesting user's email, using the same subject and download URL semantics while preserving the existing persisted notification. The default mailer reads Chatwoot-compatible SMTP env vars and no-ops when SMTP is not configured. Focused export/service/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. Continue B4 with frontend CRM smoke fixtures.
ad29dc3 test(crm): cover chatwoot frontend crm smoke Closed the B4 CRM API smoke gap with a repeatable handler-level flow that mirrors the reused Chatwoot dashboard contact/company clients: contact create/list/search/show/update/custom-attribute deletion/labels/contactable-inboxes/notes/conversations plus company create/list/search/show/update/custom-attribute deletion/contact attach/list/search/notes/conversations. go test ./internal/handler/api/v1 -run TestChatwootFrontendCRMSmoke -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. Move B4 to review. True browser Playwright validation with the reused frontend remains tracked by B12/M7.
0e83e8d feat(inboxes): align chatwoot inbox serializers Started B5 inbox/channel parity by rendering Chatwoot api/v1/models/_inbox.json.jbuilder-style payloads for inbox list/show/create/update/reset-secret flows, mapping local channel slugs to Channel::* class names, exposing channel-specific settings from channel_config, returning Chatwoot's async delete message, making avatar delete empty 200 OK, and adding the frontend-used PATCH /inboxes/:id route. `go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity TestInbox' -count=1; go test ./internal/handler/api/v1 -count=1; regenerated docs/parity/gochat_routes.txt (TOTAL: 821); go run ./cmd/route_parity; go test ./...; git diff --check`.
ee93546 feat(inboxes): bind chatwoot channel settings Completed B5.2 request binding for reused dashboard inbox create/update flows: JSON and multipart/urlencoded bodies now accept top-level inbox settings, nested channel[...], nested csat_config[...], selected feature flags, API webhook config, generated web widget/API secrets, and Telegram create without explicit name. go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInboxHandler_ChatwootCreateUpdateRequestBinding|TestInbox' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; escalated go test ./... after sandbox socket denial; git diff --check. Continue B5.3 with durable working-hours update behavior and out-of-office parity, then B5.4 inbox members and B5.5 channel-specific config depth.
945782e feat(inboxes): persist chatwoot working hours Completed B5.3 working-hours parity for the generic inbox update flow: inbox create initializes Chatwoot default weekly schedule where the table exists, inbox update accepts frontend working_hours JSON with string/empty time fields from timeSlotTransform, persists schedules in working_hours, serializes working_hours from the relation, and preserves working_hours_enabled, out_of_office_message, and timezone behavior. go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInboxHandler_ChatwootCreateUpdateRequestBinding|TestInbox' -count=1; go test ./internal/service -run 'Inbox|WorkingHour' -count=1; go test ./internal/handler/api/v1 -count=1; escalated go test ./...; git diff --check. Continue B5.4 with inbox member payload and round-robin membership side effects, then B5.5 channel-specific config depth.
82167f5 feat(inboxes): align chatwoot inbox members Completed B5.4 inbox member parity for reused dashboard agent assignment flows: account-scoped and nested member endpoints now return Chatwoot { payload: [agent] } responses, agent payloads expose frontend-required user fields, duplicate adds are idempotent, create adds only missing users, and update performs a diff so preserved rows are not recreated unnecessarily. go test ./internal/handler/api/v1 -run InboxMember -count=1; go test ./internal/service -run InboxMember -count=1; go test ./internal/repository -run InboxMember -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. Continue B5.5 with deeper channel-specific config parity and InboxLimit enforcement on channel creation.
f0aae79 feat(inboxes): deepen channel config parity Advanced B5.5 channel config parity: generic inbox JSON updates now hoist flat frontend SMTP/IMAP/channel setting keys into channel_config, the serializer exposes deeper Email, WhatsApp, LINE, SMS, and Twilio voice/config fields, channel defaults match Chatwoot closer for WebWidget/API/Email/WhatsApp, and the Twilio frontend create route accepts nested twilio_channel payloads while returning the raw Chatwoot inbox serializer. go test ./internal/handler/api/v1 -run 'TestInboxHandler_Chatwoot(ChannelSpecificConfigDepth|CreateUpdateRequestBinding|SerializerParity)|TestTwilioChannel_Create' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. Continue B5.5 with InboxLimit enforcement and any remaining channel-specific route response cleanup.
8fd40ec docs: record channel config checkpoint Recorded the B5.5 channel config checkpoint and kept the route count at TOTAL: 821 with tracked route parity still at 262 exact and 0 missing. Documentation-only checkpoint after the B5.5 channel config implementation. Next active slice is B5.5c AgentCapacityPolicy/InboxCapacityLimit route, serializer, and service parity.
b197e54 feat(capacity): align chatwoot inbox capacity limits Completed B5.5c AgentCapacityPolicy/InboxCapacityLimit API/data parity: policy CRUD now returns raw Chatwoot payloads with Unix timestamps, assigned_agent_count, and inbox_capacity_limits; assignment_logic is optional/defaulted; nested policy users and inbox limits are registered; InboxCapacityLimit validates account scope, duplicate inbox assignment, and non-negative limits; account users can be assigned/unassigned to capacity policies. go test ./internal/service -run AgentCapacity -count=1; go test ./internal/handler/api/v1 -run AgentCapacity -count=1; go test ./internal/router -count=1; go test ./internal/handler/api/v1 -count=1; sandboxed go test ./... failed on local socket restrictions; escalated go test ./... passed; route dump regenerated with TOTAL: 829; route parity is 267 exact, 7 parameter-compatible, 0 missing; git diff --check. Continue final B5.5 review for remaining channel-specific route response cleanup, then move to B6 Meilisearch live-shape review.
f04a03b feat(channels): align channel route inbox payloads Completed the final B5.5 channel route-response cleanup: Email, Twilio SMS, and LINE dedicated channel create/get/update/list routes now return Chatwoot frontend-compatible raw inbox payloads or { payload: [...] } lists instead of { channel }, { channels }, { channel, inbox }, or success-message envelopes. Delete routes now return empty 200 OK, and channel creation binds the dedicated channel ID/config back onto the inbox before serialization. go test ./internal/handler/api/v1 -run 'Test(Email|Twilio|LINE)Channel' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. B5 inbox/channel API parity moves to Done for current frontend-critical scope; next active slice is B6 Meilisearch live-shape review.
a16c23c feat(search): align chatwoot search payloads Advanced B6 search live-shape parity: global and entity search endpoints now return Chatwoot frontend payload envelopes, search filters accept Chatwoot since, until, and from=contact:id/agent:id params, message sender IDs are indexed/filterable in Meilisearch and DB fallback, SearchController default page size is aligned to 15, and Meilisearch hit maps are serialized into frontend message/contact/conversation/article shapes. go test ./internal/handler/api/v1 -run SearchHandler -count=1; go test ./internal/search -run 'SearchFilter|Meili|Engine' -count=1; go test ./internal/repository -run Search -count=1; go test ./internal/handler/api/v1 -count=1; go test ./internal/search -count=1; go test ./internal/repository -count=1; sandboxed go test ./... failed on local socket restrictions; escalated go test ./... passed; git diff --check. No route changes; route dump remains TOTAL: 829. B6.1 and B6.3 are Done; B6.2 is in Review with mocked Meilisearch coverage. Continue B6.4 live integration gate and B6.5 DB-fallback hardening before moving B6 to Review/Done.
f08c743 test(search): add meilisearch live gate Completed B6 gate/hardening work: added an env-gated live Meilisearch test that bootstraps isolated indexes, indexes message/contact documents, verifies account-scoped searches and sender filters, rejects search.engine=db in release mode, and prevents cmd/reindex_search from silently running against the DB fallback. go test ./internal/config -count=1; go test ./cmd/reindex_search -count=1; go test ./internal/search -count=1; go test ./...; git diff --check. Live gate is skipped unless GOCHAT_LIVE_MEILI_HOST is set. No route changes; route dump remains TOTAL: 829. B6 moves to Review. Optional next verification is running GOCHAT_LIVE_MEILI_HOST=http://localhost:7700 GOCHAT_LIVE_MEILI_API_KEY=... go test ./internal/search -run TestLiveMeiliSearchEngineIndexesAndSearchesChatwootShapes -count=1; otherwise continue B7 SLA/assignment capacity.
a98dc2c feat(capacity): enforce inbox assignment limits Started B7 assignment/capacity enforcement: manual assignment, team assignment with explicit agent, team overflow fallback, and auto-assignment now respect account_users.agent_capacity_policy_id plus matching inbox_capacity_limits.conversation_limit; only open conversations in the same inbox count toward capacity and resolved conversations do not block new assignments. go test ./internal/service -run 'AssignAgent|AssignTeam' -count=1; go test ./internal/autoassignment -run Capacity -count=1 escalated after sandbox socket denial; go test ./internal/service -count=1; go test ./internal/autoassignment -count=1 escalated after sandbox socket denial; go test ./internal/handler/api/v1 -run 'Conversation|AgentCapacity' -count=1; escalated go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. Continue B7 with SLA policy CRUD payload review, applied-SLA persistence for new conversations, and timer/breach lifecycle tests.
95224fa feat(sla): align sla policy payloads Completed B7.2 SLA policy CRUD payload parity for the reused Chatwoot frontend: create/show/update now return { payload: sla_policy }, index returns { payload: [...] }, destroy returns empty 200 OK, and the SLA policy serializer emits only the fields from Chatwoot's enterprise SLA policy partial. go test ./internal/handler/api/v1 -run SlaPolicy -count=1; go test ./internal/service -run Sla -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. Continue B7.3 with applied-SLA persistence for new/open conversations, then B7.4 timer/breach lifecycle.
a11bb96 feat(sla): persist applied sla on conversations Completed the first B7.3 applied-SLA persistence slice: conversation create/update now accept Chatwoot enterprise sla_policy_id, validate account ownership and replacement/removal rules, create one applied_slas row when a policy is attached, keep status-only updates idempotent for existing SLA conversations, and serialize applied_sla in conversation payloads using Chatwoot's applied-SLA partial fields. go test ./internal/service -run 'ConversationService_.*Sla|ConversationService_Update|ConversationService_Create' -count=1; go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite/Test(Update_WithSlaPolicy|Update_Success|Create_Success)' -count=1; go test ./internal/service -run 'Conversation|AppliedSla|Sla' -count=1; go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite|SlaPolicy' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. Continue B7.4 with idempotent SLA event/timer lifecycle and then B7.5 applied-SLA report/list payload parity.
213bf2b feat(sla): make sla breach events idempotent Completed B7.4 timer/breach lifecycle core parity: SLA thresholds now use Chatwoot seconds units, FRT/NRT/RT checks treat the exact threshold as breached, missed events are idempotent by applied_sla_id, event_type, and meta, NRT events store the last incoming message ID in meta, and resolved active/active-with-misses applied SLAs transition to hit or missed. go test ./internal/service -run 'AppliedSlaService|ConversationService_Create_AppliesSlaPolicy' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicy|ConversationCrudTestSuite/TestUpdate_WithSlaPolicy' -count=1; go test ./internal/service -run 'AppliedSla|Sla|Conversation' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicy|ConversationCrudTestSuite' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. Continue B7.5 with applied-SLA report/list payload parity, then review notifications/business-hours depth.
4f85ef1 feat(sla): align applied sla reports Completed B7.5 applied-SLA report/list payload parity for the reused Chatwoot SLA reports screen: added GET /api/v1/accounts/:account_id/applied_slas, aligned the index { payload, meta } shape, filtered missed reports by date/inbox/team/SLA policy/label/assignee, returned Chatwoot applied-SLA/conversation/SLA-event fields, changed metrics to raw total_applied_slas, number_of_sla_misses, and hit_rate, and changed download to the breached-conversation CSV. go test ./internal/service -run 'SlaPolicyService_.*AppliedSla|AppliedSlaReport' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicyHandler_.*AppliedSla' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; go test ./cmd/route_parity -count=1; git diff --check. Route dump is now TOTAL: 830; route parity tracks 277 routes with 270 exact, 7 parameter-compatible, and 0 missing. Continue B7 with business-hours timer math and SLA notification delivery review before closing the SLA package.
330f875 docs: record applied sla reports checkpoint Recorded the B7.5 applied-SLA reports checkpoint in this tracker and made B7.6 the active follow-up. Documentation-only checkpoint after the applied-SLA report/list implementation verification. Continue B7.6 with business-hours timer math and SLA notification delivery.
64926db docs: land chatwoot parity tracking plan Normalized this tracker after B7.5: added the planning control board, mapped Hermes plans into active B8/B9/B6 slices, and made B7.6's investigation/landing contract explicit. Documentation-only checkpoint; git diff --check passed before commit. Continue B7.6 implementation.
d23f3f0 feat(sla): notify users on sla misses Completed B7.6 notification parity and business-hours timing review: new SLA miss events now create Chatwoot notification types for conversation participants, account administrators, and assignee with user de-duplication; repeated SLA evaluation remains event/notification idempotent; the current reference/chatwoot service stores only_during_business_hours but still calculates thresholds as elapsed seconds, so Go has a regression test for that reference timing. go test ./internal/service -run AppliedSlaService -count=1; go test ./internal/service -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. B7 moves to Review. Next active enterprise slice is B8 CSAT account/public/report/send parity.
9e0826b docs: record sla notification checkpoint Recorded the B7.6 SLA notification checkpoint, moved B7 to Review, and set B8 as the next active enterprise slice. Documentation-only checkpoint after d23f3f0; git diff --check passed before commit. Continue B8 CSAT account/public/report/send parity.
f441680 feat(csat): align account report payloads Started B8 with account-side CSAT report parity: GET /csat_survey_responses now returns the raw Chatwoot array of CSAT response serializers; list and metrics filters accept frontend since/until, user_ids, inbox_id, team_id, and rating; metrics now returns total_count, ratings_count, and total_sent_messages_count; CSAT update/review-note responses return the Chatwoot CSAT response serializer with nested contact, assigned agent, reviewer, display conversation ID, and Unix timestamps. go test ./internal/handler/api/v1 -run CsatSurvey -count=1; go test ./internal/automation -run CsatSurveyService -count=1; go test ./internal/handler/api/v1 -count=1; go test ./internal/automation -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. Continue B8 with public CSAT submit/update lock regression, resolve-triggered survey send idempotency, and download CSV parity.
fca6838 docs: record csat report checkpoint Recorded the B8.1 CSAT report checkpoint in this tracker, moved B8 to Doing, and made B8.2/B8.3 the next active implementation slices. Documentation-only checkpoint after f441680; git diff --check passed before commit. Continue B8 public CSAT submit/update, resolve-triggered survey send idempotency, and CSAT download parity.
bda5ad5 docs: expand parity execution plan Expanded this tracker with executable B8-B12 breakdowns for CSAT, automation/macros, audit/custom roles/limits, Captain/Copilot, and reused frontend smoke validation. Documentation-only checkpoint; git diff --check HEAD~1 HEAD passed. Continue B8 public CSAT submit/update and resolve-triggered send parity.
ef3a909 feat(csat): send surveys on resolved conversations Advanced B8 public/send parity: public CSAT update now accepts the reused survey frontend's object-shaped message.submitted_values, keeps one message-linked CSAT response per input_csat message across repeated submissions, and still enforces the 14-day message-date lock. The resolve listener now loads the resolved conversation, checks inbox CSAT enablement and survey label rules, creates one Chatwoot-style input_csat template message with inbox CSAT content/display type, and no longer creates pending rating-0 responses before customer submission. Message-updated events also build CSAT responses from submitted_values when present. go test ./internal/automation -run 'CsatSurveyListener|CsatSurveyService' -count=1; go test ./internal/handler/api/v1 -run CsatSurvey -count=1; go test ./internal/handler/widget -run 'CSAT|Csat|PublicInbox' -count=1; go test ./internal/service -run Widget -count=1; go test ./internal/automation -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. Continue B8.4 CSAT download CSV parity and then review channel-specific WhatsApp/Twilio template send hooks as broader integration work.

Next Slice Contract

Completed implementation slice: B5.1-B5.5 now cover inbox serializer shape, Chatwoot frontend create/update binding, working-hours persistence, out-of-office behavior, inbox member assignment payload/mutation semantics, channel-specific config depth, AgentCapacityPolicy/InboxCapacityLimit API data contracts, and dedicated Email/Twilio/LINE channel route response shapes. B6 is now in Review after a16c23c and f08c743: Chatwoot search response envelopes, frontend query params, Meilisearch sender filters, mocked hit serialization, env-gated live Meilisearch validation, release-mode DB fallback rejection, and reindex Meilisearch-only guard are covered. B7 is now in Review after a98dc2c, 95224fa, a11bb96, 213bf2b, 4f85ef1, and d23f3f0, enforcing per-inbox capacity limits, aligning SLA policy CRUD payloads, persisting applied SLA records, making core FRT/NRT/RT breach events idempotent, aligning the applied-SLA reports API, and creating SLA miss notifications. B3 and B4 remain in review for deeper side effects and browser validation.

Next implementation slice: continue B8 with CSAT download/export parity.

Step Required result Reference source Verification
N1 Keep B6 search payload/gate checkpoint as the current baseline. SearchController, search.js, conversationSearch.js, search Jbuilder views, cmd/reindex_search. Done by a16c23c and f08c743; response envelopes, Chatwoot params, sender filters, mocked hit shape, live gate, and fallback guards are covered.
N2 Optionally run the live Meilisearch gate when a local Meilisearch instance is available. internal/search/engine_meili_live_test.go. GOCHAT_LIVE_MEILI_HOST=http://localhost:7700 GOCHAT_LIVE_MEILI_API_KEY=... go test ./internal/search -run TestLiveMeiliSearchEngineIndexesAndSearchesChatwootShapes -count=1.
N3 Keep B7 assignment capacity enforcement as current capacity baseline. AssignmentHandler, AutoAssignmentHandler, AgentCapacityPolicy, InboxCapacityLimit. Done by a98dc2c; manual/team/auto assignment now skip or reject agents at per-inbox capacity.
N4 Keep SLA policy CRUD payload review as current SLA API baseline. reference/chatwoot/enterprise/app/controllers/api/v1/accounts/sla_policies_controller.rb, SLA policy Jbuilder views, dashboard SLA store/API. Done by 95224fa; create/show/update/index/delete payloads match the frontend-consumed Chatwoot shape.
N5 Keep applied-SLA persistence as current conversation SLA baseline. reference/chatwoot/enterprise/app/models/enterprise/concerns/conversation.rb, AppliedSla#push_event_data, applied-SLA model partial. Done by a11bb96; sla_policy_id attach creates exactly one applied SLA and conversation payloads include Chatwoot-style applied_sla.
N6 Keep SLA timer and breach lifecycle as current SLA lifecycle baseline. reference/chatwoot/enterprise/app/services/sla/evaluate_applied_sla_service.rb, SlaEvent, SLA processing jobs. Done by 213bf2b; active applied SLAs create idempotent FRT/NRT/RT miss events, transition to active_with_misses, hit, or missed, and preserve status on repeated evaluation.
N7 Keep applied-SLA report/list payload parity as the current SLA reports baseline. reference/chatwoot/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb, applied-SLA index/metrics/download views, dashboard SLA reports client. Done by 4f85ef1; account-side applied-SLA list/report filters return frontend-compatible payloads and metrics/download shapes.
N8 Keep B7.6 SLA notification and timing review as current SLA closeout baseline. Sla::EvaluateAppliedSlaService, SlaEvent#create_notifications, SLA processing jobs. Done by d23f3f0; SLA miss notifications fan out to participants/admins/assignee, repeated evaluation is idempotent, and current reference timing for only_during_business_hours is covered.
N9 Keep B8.1-B8.3 CSAT account/public/send parity as the current CSAT baseline. CsatSurveyResponsesController, public CSAT controller/views, CsatSurveyService, CsatSurveyListener, dashboard reports and survey frontend clients. Done by f441680 and ef3a909; list/metrics/review-note payloads, public object-shaped submission, lock window, response idempotency, and resolve-triggered message send are covered.
N10 Update this tracker after every implementation checkpoint. This document. git diff --check; go test ./... for Go changes.

Current B2 profile checkpoint:

  • Added a ProfileUserResponse matching Chatwoot's api/v1/models/_user.json.jbuilder, including access_token, available_name, message_signature, pubsub_token, ui_settings, and nested account membership fields.
  • Profile handlers now return raw Chatwoot user JSON instead of the Go API {success,data} envelope for frontend-consumed profile endpoints.
  • Availability and auto-offline endpoints now return the full current-user serializer, matching frontend store expectations.
  • User creation now assigns a pubsub_token like Chatwoot's Pubsubable concern, avoiding duplicate empty token rows and enabling ActionCable-compatible profile payloads.
  • Added migration 000017_add_profile_serializer_fields for profile serializer fields on users, accounts, and account_users.
  • Added Chatwoot root auth/session routes. /auth/sign_in now accepts normalized email/password login, emits the auth headers stored by frontend cw_d_session_info, and returns the profile serializer under data. /auth/validate_token validates the same access-token header and returns the frontend-expected payload.data shape. /auth/sign_out revokes the refresh session.
  • Added profile settings update support for JSON and multipart form payloads produced by dashboard/api/auth.js: profile[display_name], profile[message_signature], profile[avatar], JSON profile.ui_settings, profile.phone_number, and password update fields now persist and return the raw Chatwoot user serializer.

Current B3 conversation/message checkpoint:

  • Conversation list now returns Chatwoot's index.json.jbuilder shape: { data: { meta, payload } }, with payload entries built from api/v1/conversations/partials/_conversation.json.jbuilder fields such as meta.sender, messages, labels, timestamps, status, priority, and unread count.
  • Conversation show/create/update/priority/labels responses now return raw conversation payloads instead of the Go {success,data} envelope for frontend-consumed dashboard endpoints.
  • Conversation route IDs now resolve by account-scoped display_id first, with legacy primary-key fallback, and new conversations receive account-scoped display IDs.
  • Message list now returns Chatwoot's { meta, payload } shape with contact/assignee metadata and serialized messages matching api/v1/models/_message.json.jbuilder field names.
  • Message create accepts the reused frontend payload from dashboard/api/inbox/message.js, including omitted message_type, private, echo_id, JSON or multipart content_attributes, and defaults to outgoing text messages like Chatwoot's dashboard composer.
  • Message create/update/retry return raw Chatwoot message payloads with numeric message_type values expected by shared/constants/messages.js.
  • Message delete now follows MessagesController#destroy: it keeps the message row, changes content to This message was deleted, sets content_attributes.deleted, deletes message attachments, and returns the raw message serializer instead of 204.
  • Message status updates now follow Messages::StatusUpdateService for sent/delivered/read/failed, preserve the read-to-delivered guard, record external_error only on failed messages, and reject status updates for non-API inboxes with 403.
  • Message list now follows MessageFinder windows for latest, before, after, and between ranges, including filter_internal_messages handling.
  • Multipart attachments[] from Chatwoot's dashboard composer now create attachment rows and serialize Chatwoot-style attachment event data in message payloads.
  • Team assignment now follows AssignmentsController#set_team and renders the raw team payload when assigning by team_id.
  • Added migration 000018_add_conversation_message_parity_fields for display-id routing and message serializer fields such as echo_id, status, and JSON attributes.
  • Remaining B3 review gaps: attachment persistence currently records metadata/URLs but does not yet provide ActiveStorage-equivalent durable binary storage; outbound delivery jobs and provider-specific attachment send behavior still need deeper Chatwoot side-effect parity; message realtime event payloads need frontend smoke validation.

Current B4 contacts/companies checkpoint:

  • Contact list, active/filter/search, show, update, selected custom-attribute deletion, and create now return Chatwoot-style meta/payload envelopes instead of legacy Go contacts or raw model JSON.
  • Contact create returns { payload: { contact, contact_inbox } } and accepts existing frontend fields while preserving contact inbox shell output.
  • Empty contact/company search queries now return 422 with Specify search string with parameter q, matching Chatwoot controllers.
  • Company list/search/show/create/update now return enterprise Chatwoot payload and meta.total_count/page shapes; create/update accept nested { company: ... } params while retaining flat compatibility inside the service boundary.
  • Company contact relations now use contacts.company_id instead of the older local many-to-many join table, matching Chatwoot enterprise company membership semantics.
  • Added Chatwoot-compatible PATCH /contacts/:contact_id, PATCH /companies/:company_id, GET /companies/:company_id/contacts/search, and POST /companies/:company_id/contacts body-based contact assignment routes.
  • Added contact_labels persistence and Chatwoot-compatible GET/POST /contacts/:contact_id/labels responses as { payload: [...] }; contact list/search/filter label params now filter contact label lists instead of conversation labels.
  • Added DELETE /contacts/:contact_id/avatar, DELETE /companies/:company_id/avatar, and POST /companies/:company_id/destroy_custom_attributes with Chatwoot-shaped { payload } responses and 422 { error: "custom_attributes must be an array" } validation.
  • Added trailing-slash aliases for Chatwoot nested label/contact-inbox collection routes.
  • Added exact POST /api/v1/accounts/:account_id/actions/contact_merge and raw contact-model response shape for dashboard merge actions; merge moves conversations, contact inboxes, notes, and contact-sender messages to the base contact while preserving base attributes over mergee attributes.
  • Added Chatwoot-shaped data_imports persistence for contact CSV imports. Missing import_file now returns 422 { error: "failed to import contacts" }, successful imports return empty 200 OK, import state/count/error fields are recorded, and CSV rows merge/update existing contacts by identifier/email/phone while preserving labels and unknown/custom columns in contact attributes.
  • Added persisted contact export artifacts for POST /contacts/export: default/requested column handling, label/filter export inputs, UTF-8 BOM CSV content, local download URL, row counts, and a user notification carrying file metadata.
  • Added Chatwoot note serializer parity for contact notes: raw array/object responses, nested agent payloads, flat and nested note request bodies, empty 200 OK destroy, hard-delete semantics, and PUT/PATCH update routes. Company note payloads now include nested user metadata.
  • Added nested contact/company conversation serializer parity: both endpoints now return { payload: [...] } arrays built from the shared Chatwoot conversation partial, including nested sender metadata, latest message payloads, last_non_activity_message, labels, timestamps, priority, and unread fields. Company conversations are limited to the latest 20 by last_activity_at DESC like the enterprise controller.
  • Routed CRM contact/company non-empty search through the configured Meilisearch-backed search service while preserving existing Chatwoot CRM list serializers by resolving hit IDs through account-scoped repositories. Service tests now prove contact/company search reader usage, and global search has a company repository fallback for document-shape parity in local DB mode.
  • Added Chatwoot-style contact import label validation: CSV rows with labels not already present on the account are rejected, failed row counts are recorded on data_imports, valid labels use existing canonical tag names, and imports no longer create unknown labels from user-provided CSV values.
  • Added contact export completion email delivery: after the persisted CSV artifact is generated, a Chatwoot-style mailer boundary sends Your contact's export file is available to download. to the requesting user when SMTP is configured, while keeping the in-app notification metadata.
  • Added a repeatable Chatwoot frontend CRM API smoke covering the contacts and companies dashboard client paths without frontend adapters: list/search/show/edit, labels, contactable inboxes, custom-attribute deletion, notes, nested conversations, company attach/list/search contact flows, and company detail updates.
  • At B4 close, route dump was TOTAL: 820 and tracked route parity was 262 exact, 0 missing; the current project-wide route baseline is recorded above.
  • Remaining B4 review gap: real browser validation is not yet run and remains in B12/M7; API-level CRM frontend contract coverage is present.

Current B5 inbox/channel checkpoint:

  • Inbox list now follows Chatwoot index.json.jbuilder and returns { payload: [inbox] } instead of the older { inboxes, meta } envelope expected by earlier local tests.
  • Inbox show/create/update/reset-secret style responses now render raw Chatwoot inbox payloads rather than raw Go models; delete returns Your inbox deletion request will be processed in some time. and avatar delete returns an empty 200 OK.
  • Local channel slugs are serialized to frontend-required Rails channel class names such as Channel::WebWidget, Channel::Api, Channel::Telegram, and Channel::TwilioSms.
  • Channel-specific fields stored in channel_config are surfaced in the inbox payload for web widget, API, Telegram, Facebook/Instagram/TikTok, Twilio, Email, and WhatsApp-style keys.
  • Added the frontend-used PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id route alias; the current route dump is now TOTAL: 830 after later capacity and applied-SLA report routes, and tracked route parity remains regenerated with zero missing tracked routes.
  • Chatwoot dashboard create/update request bodies now bind through the generic inbox route for JSON, urlencoded forms, and multipart FormData. The binder accepts top-level inbox settings, nested channel[...], channel[selected_feature_flags][], and nested csat_config[...] values generated by buildInboxData.
  • Create/update now persists greeting/email collection/CSAT/auto-assignment/timezone/post-resolve/single-conversation/sender/business settings, stores channel-specific fields in channel_config, generates web widget website_token/hmac_token, generates API secret/hmac_token/identifier, and derives a Telegram inbox name when the frontend omits name.
  • Inbox create/update now follows Chatwoot's OutOfOffisable schedule lifecycle: default seven-day working hours are created for new inboxes, working_hours updates persist the weekly relation, empty string time fields from the frontend are normalized, open-all-day rows are expanded to 00:00-23:59, and the serializer returns weekly_schedule shape from the relation.
  • Inbox member routes now follow Chatwoot's account-scoped InboxMembersController contract: GET /inbox_members/:inbox_id, POST /inbox_members, and PATCH /inbox_members return { payload: [agent] }; DELETE /inbox_members returns empty 200 OK; nested local member routes use the same agent payload boundary for frontend compatibility.
  • Inbox member create is add-only and idempotent for duplicate users, while update computes add/remove diffs instead of clearing and recreating rows. Returned agent payloads include id, account_id, availability_status, auto_offline, confirmed, email, provider, available_name, name, role, thumbnail, and enterprise custom_role_id.
  • Generic inbox JSON create/update now persists flat channel-specific frontend keys used by Email IMAP/SMTP settings and settings-page toggles, not only nested channel payloads.
  • Inbox serializer depth now covers Email IMAP/SMTP/forwarding fields, WhatsApp provider_config and generated webhook verify token, LINE channel credentials, SMS provider config, and Twilio SMS/WhatsApp/voice fields. Twilio channel creation accepts Chatwoot's nested twilio_channel payload and returns the raw inbox serializer instead of { channel, inbox }.
  • AgentCapacityPolicy and InboxCapacityLimit API/data parity now covers raw policy serializers, optional/defaulted assignment_logic, nested users and inbox-limit routes, account-scoped inbox validation, duplicate prevention, non-negative limits, and assigned_agent_count through account_users.agent_capacity_policy_id.
  • Dedicated Email, Twilio SMS, and LINE channel create/get/update/list/delete routes now use frontend-compatible inbox payload contracts: raw inbox objects for single-resource mutations, { payload: [...] } for lists, and empty 200 OK for deletes. The handlers bind the dedicated channel record ID/config back onto inboxes.channel_id/channel_config before serialization.
  • Remaining B5 gap: none for the current frontend-critical API scope. Browser validation remains deferred to B12/M7.

Active B5 task board:

ID Task Reference source Status Exit gate
B5.1 Inbox CRUD serializer and route shape. InboxesController, _inbox.json.jbuilder, dashboard inbox store/API. Done 0e83e8d; serializer parity test, route dump TOTAL: 821, full tests.
B5.2 Inbox create/update request binding and settings persistence. buildInboxData, permitted_params, settings pages. Done ee93546; JSON and multipart channel[...] requests persist core inbox/channel settings.
B5.3 Working hours and out-of-office parity. OutOfOffisable, working hour controller/model. Done 945782e; weekly schedule create/update/show and out_of_office service tests match Chatwoot semantics.
B5.4 Inbox member payload and round-robin side effects. InboxMembersController, Inbox#add_members/remove_members. Done 82167f5; account-scoped and nested member routes return { payload } agents, create is add-only/idempotent, update is diff-based, and delete is empty 200 OK.
B5.5 Channel-specific config depth and inbox limits. WebWidget/API/Telegram/Email/WhatsApp/SMS/LINE channel models/controllers, enterprise inbox limits. Done f04a03b; Email/WhatsApp/LINE/SMS/Twilio config payloads, Twilio create route, AgentCapacityPolicy/InboxCapacityLimit API/data parity, and dedicated Email/Twilio/LINE response cleanup are covered.

B5.2 execution breakdown:

Step Implementation target Reference source Required tests Status
B5.2a Add a request binder that accepts JSON, urlencoded form, and multipart FormData from the reused dashboard inbox screens. It must parse top-level settings plus nested keys such as channel[type], channel[website_url], channel[selected_feature_flags][], and csat_config[...]. reference/chatwoot/app/javascript/dashboard/store/modules/inboxes/channelActions.js, reference/chatwoot/app/javascript/dashboard/store/modules/inboxes.js Handler tests for website multipart create, API JSON create, Telegram JSON create without explicit name, and settings update. Done
B5.2b Normalize channel type aliases from frontend slugs and Rails class names into the local storage format while keeping serializers Rails-compatible. reference/chatwoot/app/models/channel/*, _inbox.json.jbuilder Serializer tests prove stored slug returns Channel::* and unknown channel types are rejected with frontend-readable validation errors. Done
B5.2c Persist core inbox settings sent by permitted_params: greeting, email collection, CSAT flag/config, auto-assignment, working-hours flag, out-of-office message, timezone, post-resolve messages, single-conversation lock, sender name type, business name, and portal ID. reference/chatwoot/app/controllers/api/v1/accounts/inboxes_controller.rb, inbox settings Vue screens Create/update tests reload the inbox and assert fields survive round trip in Chatwoot payload names. Done
B5.2d Persist channel-specific settings into dedicated fields where they exist and channel_config where GoChat has no first-class column yet. WebWidget/API/Telegram/LINE/Twilio/Email channel models and settings screens Tests cover web widget website_url/widget_color/welcome_*, API webhook_url, and Telegram bot_token shapes. LINE deep settings remain in B5.5. Review
B5.2e Preserve backward compatibility for internal tests and existing local callers that still construct service.CreateInboxRequest with channel_type and simple fields. Existing Go service callers and handler tests Existing TestInbox* suite stays green. Done

B5.2 exit command set:

env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInbox' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run Inbox -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check

B5.4 execution breakdown:

Step Implementation target Reference source Required tests Status
B5.4a Return Chatwoot account-scoped inbox member envelopes as { payload: [agent] } instead of local { members, meta } model envelopes. reference/chatwoot/app/controllers/api/v1/accounts/inbox_members_controller.rb, app/views/api/v1/accounts/inbox_members/*.json.jbuilder TestAccountScopedInboxMembers_ChatwootPayloadAndDiffUpdate validates payload shape and agent fields. Done
B5.4b Serialize assigned users through the Chatwoot agent partial field set, including enterprise custom_role_id. reference/chatwoot/app/views/api/v1/models/_agent.json.jbuilder Handler payload assertions cover available_name, availability_status, and absence of local inbox_id. Done
B5.4c Match create/update/delete mutation semantics: create adds missing users only, update diffs desired membership, duplicate user IDs are idempotent, and delete returns empty 200 OK. Inbox#add_members, Inbox#remove_members, dashboard inboxMembers.js store actions. Account-scoped handler regression creates duplicates, patches to a replacement set, and deletes back to zero. Done
B5.4d Preserve loaded User and Inbox relations for member serialization after create/list/update. Go repository preload boundary; Chatwoot views expect account/user fields. Repository/handler focused tests and full go test ./.... Done

B5.5 planned breakdown:

Step Implementation target Reference source Required tests Status
B5.5a Compare current Go channel config fields against Chatwoot channel models/controllers for WebWidget, API, Telegram, Email, WhatsApp, Twilio SMS, and LINE. reference/chatwoot/app/models/channel/*, channel controllers, dashboard inbox setting screens. Done by f04a03b; config matrix is covered and dedicated Email/Twilio/LINE route responses are frontend-compatible. Done
B5.5b Persist and serialize deeper channel-specific settings used by reused settings screens, preferring first-class columns only where GoChat already has them and channel_config otherwise. Chatwoot channel model validations and InboxesController#permitted_params. Done by f0aae79; handler tests cover Email IMAP/SMTP flat JSON update, WhatsApp provider config, LINE credentials, and Twilio nested create raw inbox payload. Done
B5.5c Enforce enterprise InboxLimit during inbox/channel creation and return frontend-compatible over-limit errors. Chatwoot enterprise inbox limit policy/model behavior. Done by b197e54; nested policy inbox limits validate duplicate/wrong-account/non-negative inputs and expose Chatwoot payloads. Assignment-path enforcement remains in B7/P4.4. Done
B5.5d Keep route artifacts unchanged unless new Chatwoot-compatible routes are registered; regenerate route dump only on route changes. cmd/dump_routes, cmd/route_parity. Done by b197e54; route dump regenerated to TOTAL: 829, route parity expanded to 274 tracked routes with 0 missing. Done
B5.5e Clean up dedicated channel controller envelopes outside generic inbox CRUD. Twilio channel controller/view, dashboard channel clients, api/v1/models/_inbox.json.jbuilder. Done by f04a03b; Email/Twilio/LINE create/get/update return raw inbox payloads, list returns { payload: [...] }, delete returns empty 200 OK, and no route artifacts changed. Done

B5.5c AgentCapacityPolicy / InboxCapacityLimit parity contract:

Area Chatwoot reference contract Current Go gap Required landing work
Policy routes reference/chatwoot/config/routes.rb:123-127 exposes /api/v1/accounts/:account_id/agent_capacity_policies, nested /:policy_id/users, and nested /:policy_id/inbox_limits. Go registers only policy CRUD through RegisterAgentCapacityRoutes; the old local /inboxes/:inbox_id/inbox_limits handler is not the Chatwoot enterprise route. Register nested Chatwoot-compatible users and inbox_limits routes under agent_capacity_policies; keep old routes only as compatibility surfaces.
Policy request/response Enterprise controller accepts { agent_capacity_policy: { name, description, exclusion_rules } }; index/show/create/update return raw policy JSON, destroy is head :ok; assignment_logic is not a permitted Chatwoot field. Go response helpers wrap policy payloads in { success, data } or pagination meta, and service creation currently requires assignment_logic. Add raw Chatwoot serializers, accept wrapped and flat bodies where useful, make assignment_logic optional/defaulted for storage compatibility, and keep update wrapper-compatible.
Policy serializer _agent_capacity_policy.json.jbuilder emits id, name, description, exclusion_rules, Unix created_at, Unix updated_at, account_id, assigned_agent_count, and inbox_capacity_limits. Repository does not preload InboxCapacityLimits; response exposes model timestamps and local fields directly; assigned count is not serialized in Chatwoot shape. Add a serializer boundary that preloads capacity limits, counts assigned users through account users, and emits Unix timestamps plus inbox_capacity_limits.
Inbox capacity limits Nested inbox_limits#create/update/destroy lives under a current account policy, validates current-account inbox, prevents duplicate inbox assignment, requires conversation_limit >= 0, create returns raw limit JSON, update also emits inbox_name, destroy is 204 No Content. Existing InboxCapacityLimit model exists, but no nested handler/service/repo flow is wired; old InboxLimit model/handler has a different path and shape. Add repo/service methods for create/update/delete under policy/account scope, duplicate and negative-limit errors, raw create/update serializers, and 204 destroy.
Enforcement Capacity policies and inbox limits must feed assignment and auto-assignment limits before the feature can leave Review. B5.5c API/data parity is done; assignment enforcement is still in P4.4/P4.9. Wire manual and automatic assignment enforcement in the B7/P4.4 capacity checkpoint.
Route artifacts Adding nested Chatwoot routes changes Go route inventory. Done by b197e54; route dump is TOTAL: 829 and route parity tracks 274 routes with 0 missing. Keep regenerated after any later route change.

B5.5c implementation checkpoints:

Step Implementation target Verification gate Status
B5.5c.1 Policy serializer and CRUD response shape: raw index/object payloads, Unix timestamps, optional assignment_logic, wrapped create/update body support. Done by b197e54; handler tests assert no { success, data } envelope and no required assignment_logic. Done
B5.5c.2 Repository/service preload and helpers for InboxCapacityLimit, current-account policy/inbox validation, duplicate guard, and non-negative conversation_limit. Done by b197e54; service tests cover success, duplicate, wrong-account inbox, deletion, and negative limit. Done
B5.5c.3 Nested POST/PUT/DELETE /agent_capacity_policies/:policy_id/inbox_limits endpoints with Chatwoot create/update/delete payload/status behavior. Done by b197e54; handler flow creates a policy, creates a limit, rejects duplicate, updates limit with inbox_name, deletes with 204, and show includes inbox_capacity_limits. Done
B5.5c.4 Nested policy users endpoints for assignment-policy membership parity. Done by b197e54; handler/service tests cover add/remove/list users and assigned_agent_count. Done
B5.5c.5 Route artifacts and full regression pass. Done by b197e54; route dump/parity regenerated, focused service/handler/router tests passed, escalated full go test ./... passed, and git diff --check passed. Done

B5.5 channel config matrix after f0aae79:

Channel Create/update support now covered Serializer fields now covered Remaining gap
WebWidget Generic FormData/JSON channel[...], selected feature flags, HMAC, domains, pre-chat defaults. Website token, HMAC token, widget color, welcome copy, pre-chat options, continuity, reply time. Review final settings-page edge fields only.
API Generic JSON/FormData webhook_url, HMAC, additional attributes, generated identifier/secret. Secret, webhook URL, inbox identifier, HMAC token, additional attributes. Review validation parity for agent_reply_time_window.
Email Flat JSON IMAP/SMTP updates and nested create fields are hoisted into channel_config; dedicated email-channel create/update bind channel IDs/config back to inboxes. Email, forwarding, IMAP, SMTP, auth, SSL/TLS, domain fields. Done for current frontend-critical API scope.
WhatsApp Manual/cloud create stores phone_number, provider, nested provider_config, and generates cloud webhook verify token. Phone number, provider, provider config, message templates, reauthorization, voice flag. Provider credential validation and embedded signup deep behavior remain later integration work.
Twilio SMS/WhatsApp Nested twilio_channel frontend create payload accepted; SMS/WhatsApp medium and phone normalization stored in inbox config; dedicated Twilio create/get/update/list/delete responses now use raw inbox/list/empty contracts. Phone, messaging service SID, medium, content templates, auth/account/API key fields, voice flags. Done for current frontend-critical API scope.
LINE/SMS Generic create stores LINE credentials and Bandwidth SMS provider config; dedicated LINE create/get/update/list/delete responses now use raw inbox/list/empty contracts. LINE credentials; SMS phone/provider config. LINE behavior is frontend-compatible and preserves both Chatwoot-style line_channel_* keys and local webhook channel_* config aliases.

Active B6 task board:

ID Task Reference source Status Exit gate
B6.1 Inventory Chatwoot search frontend request/response consumers and current Go search routes. reference/chatwoot/app/javascript/dashboard/api/search.js, conversationSearch.js, SearchController, internal/handler/api/v1/search_handler.go. Done Matrix below records global, contacts, conversations, messages, and articles params/payload fields.
B6.2 Compare Meilisearch document fields and filters against frontend payload needs. internal/search/engine.go, engine_meili.go, document builders, Chatwoot search views/entities. Review a16c23c adds mocked Meilisearch sender filter and hit-shape tests; live Meilisearch gate remains B6.4.
B6.3 Tighten endpoint serializers for /search, /search/contacts, /search/conversations, /search/messages, and /search/articles. reference/chatwoot/app/controllers/api/v1/accounts/search_controller.rb, dashboard search API specs. Done SearchHandler tests assert Chatwoot payload envelopes and Meilisearch hit serialization.
B6.4 Add optional live Meilisearch integration gate. Local Meilisearch flow in Phase 1 and cmd/reindex_search. Done f08c743 adds TestLiveMeiliSearchEngineIndexesAndSearchesChatwootShapes, skipped unless GOCHAT_LIVE_MEILI_HOST is set.
B6.5 Document DB fallback as development-only and verify production config remains Meilisearch-first. User decision ledger and search config. Done f08c743 rejects DB fallback in release mode and prevents reindex_search from using DB fallback.

B6 request and payload matrix after a16c23c:

Endpoint Frontend params now accepted Chatwoot payload shape now returned Covered by
GET /api/v1/accounts/:account_id/search q, page, default 15 items, existing local filters, plus since, until, from, inbox_id. { payload: { conversations, contacts, messages, articles } }. TestSearchHandler_GlobalSearch_Success, TestParseSearchFilter_ChatwootSearchParams.
GET /api/v1/accounts/:account_id/search/conversations q, page, status/priority/assignee/team/inbox/labels/date filters. { payload: { conversations: [...] } }. TestSearchHandler_SearchConversations_Success.
GET /api/v1/accounts/:account_id/search/messages q, page, message_type, sender_type, from, sender_id, content_type, private, inbox_id, since, until. { payload: { messages: [...] } } with numeric Chatwoot message_type for model and Meilisearch hit data. TestSearchHandler_SearchMessages_Success, TestSearchHandler_SearchMessages_MeiliHitPayloadShape, TestMeiliSearchEngine_SearchSendsMessageSenderIDFilter.
GET /api/v1/accounts/:account_id/search/contacts q, page, contact_source. { payload: { contacts: [...] } }. TestSearchHandler_SearchContacts_Success.
GET /api/v1/accounts/:account_id/search/articles q, page, portal_id, article_status, locale. { payload: { articles: [...] } }. TestSearchHandler_SearchArticles_Success.

B6 current checkpoint:

  • Search endpoints no longer use the local { success, data, meta } response envelope for frontend paths.
  • search.ParseSearchFilter now understands Chatwoot dashboard since/until Unix seconds and from=contact:id/from=agent:id sender filters while preserving legacy date_from/date_to and sender_id inputs.
  • Meilisearch documents now store sender_id, index settings mark it filterable, and DB fallback applies the same sender ID filter for local tests/dev mode.
  • Search handler pagination defaults to Chatwoot's 15 items when per_page is not supplied.
  • The optional live gate is implemented but not run by default. It requires GOCHAT_LIVE_MEILI_HOST and validates isolated live indexes, account-scoped contact/message search, and message sender filters.
  • DB fallback remains available for explicit local development, but release config validation and reindex_search now refuse it so production parity stays Meilisearch-first.

Active B7 task board:

ID Task Reference source Status Exit gate
B7.1 Enforce AgentCapacityPolicy/InboxCapacityLimit during manual and automatic assignment. AssignmentHandler, AutoAssignmentHandler, AgentAssignmentService, AgentCapacityPolicy, InboxCapacityLimit. Done a98dc2c; manual assignment, team assignment, overflow fallback, and auto-assignment respect per-inbox open-conversation limits.
B7.2 Align SLA policy CRUD payloads and validation. sla_policies_controller.rb, SLA frontend store/API, SLA policy views/entities. Done 95224fa; handler tests prove Chatwoot request wrapper, raw { payload }/list shapes, validation, and empty 200 OK delete status.
B7.3 Persist applied SLA on new/open conversations. Chatwoot applied SLA model/services and conversation create/status callbacks. Done a11bb96; sla_policy_id create/update creates one applied SLA, validates policy/account/replacement rules, and serializes conversation applied_sla.
B7.4 Implement SLA timer and breach lifecycle. First response, next response, resolution targets, business-hours behavior, SLA events/notifications. Done 213bf2b; lifecycle tests cover FRT/NRT miss idempotency, NRT message meta, hit, missed, and active-with-misses transitions.
B7.5 Add SLA report/list payload parity. applied_slas routes, SLA report frontend. Done 4f85ef1; account-side applied-SLA index/metrics/download filters return frontend-compatible payloads and breached-conversation CSV.
B7.6 Review business-hours timer math and SLA notifications. Business-hours helpers, SLA event notification creation, processing jobs. Done d23f3f0; SLA miss notifications match Chatwoot fan-out/type mapping, repeated evaluation is idempotent, and current reference timing stores only_during_business_hours without changing elapsed-second thresholds.

B7 execution breakdown:

Step Implementation target Reference source Required tests Status
B7.2a Return SLA policy create/show/update as { payload: sla_policy } and list as { payload: [sla_policy] }; omit local model metadata from the serializer. reference/chatwoot/enterprise/app/views/api/v1/accounts/sla_policies/*.json.jbuilder, _sla_policy.json.jbuilder. Handler tests assert payload envelope, field set, and absence of success, created_at, and account_id. Done by 95224fa
B7.2b Match Chatwoot destroy behavior with empty 200 OK instead of a local success envelope. sla_policies_controller.rb#destroy, controller spec. Delete handler test asserts 200 OK and empty body. Done by 95224fa
B7.3a Determine applied-SLA policy attachment behavior from Chatwoot callbacks/services and map it to Go conversation create/update/open flows. Enterprise::Concerns::Conversation, Enterprise::Api::V1::Accounts::ConversationsController#permitted_update_params, AppliedSLA. Service tests prove explicit sla_policy_id attachment, account validation, replacement rejection, and no duplicate applied SLA rows. Done by a11bb96
B7.3b Persist applied_slas and link conversations.sla_policy_id when a conversation enters an SLA-covered state. Applied SLA model and serializer views. Conversation create/update tests assert applied_slas, conversations.sla_policy_id, initial active status, and frontend applied_sla payload fields. Done by a11bb96
B7.4a Implement first-response, next-response, and resolution target state transitions with idempotent SLA events. Sla::EvaluateAppliedSlaService, SlaEvent, SLA processing jobs and event model. Lifecycle tests cover active, hit, missed, active-with-misses, NRT message_id meta, exact-threshold breach behavior, and duplicate-event prevention. Done by 213bf2b
B7.5a Add the Chatwoot account applied-SLA index route and handler if the Go route surface is missing it. reference/chatwoot/enterprise/app/controllers/api/v1/accounts/applied_slas_controller.rb#index, dashboard SLA reports client. Route artifacts are regenerated only if a route is added; handler tests prove GET /api/v1/accounts/:account_id/applied_slas returns the expected envelope. Done by 4f85ef1
B7.5b Align applied-SLA index filters and pagination. AppliedSlasController#index, applied_slas/index.json.jbuilder, frontend SLA reports filters. Tests cover sla_policy_id, inbox_id, team_id, assigned_agent_id, label_list, since, until, and Chatwoot's missed scope semantics (missed plus active_with_misses). Done by 4f85ef1
B7.5c Align applied-SLA list item serializers. api/v1/models/_applied_sla.json.jbuilder, _sla_event.json.jbuilder, conversation/report Jbuilder views. Tests assert applied_sla, conversation, sla_events, SLA policy names/descriptions/threshold fields, Unix timestamps, event meta, and no local response envelope. Done by 4f85ef1
B7.5d Align applied-SLA metrics and download payloads used by the frontend reports. applied_slas_controller.rb#metrics/#download, metrics.json.jbuilder, dashboard reports API. Handler/service tests assert metric keys, hit/miss math, shared filters, CSV/download shape, and frontend field names. Done by 4f85ef1
B7.5e Update this tracker and commit immediately after the applied-SLA report/list implementation checkpoint. This document. Commit ledger row, B7 status changes, verification commands, and route counts if changed. Done by this documentation checkpoint
B7.6a Audit and implement remaining business-hours timer math and SLA notifications. Sla::EvaluateAppliedSlaService, SlaEvent#create_notifications, SLA processing jobs. Focused lifecycle/notification tests cover current reference timing and notification fan-out. Done by d23f3f0

B7.5 implementation notes:

  • Inspect the Rails controller, model scopes, Jbuilder views, and reference/chatwoot/app/javascript/dashboard/api/slaReports.js before changing Go code.
  • Keep SLA threshold units in seconds, matching 213bf2b and the Chatwoot enterprise SLA policy serializer.
  • Reuse the existing applied-SLA lifecycle data model where possible; add repository query helpers only for report filters and preloads.
  • If the index route is added, run go run ./cmd/dump_routes > docs/parity/gochat_routes.txt and go run ./cmd/route_parity before committing.
  • Exit commands for the implementation checkpoint should include focused applied-SLA handler/service tests, go test ./..., and git diff --check.

Upcoming enterprise task boards:

Slice Step Implementation target Reference source Required tests Status
B8 B8.1 Align account-side CSAT survey response index, metrics, filters, pagination, and review-note payloads. reference/chatwoot/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb, CSAT Jbuilder/views, dashboard reports clients. Handler/service tests for list, metrics, review note update, date/agent/inbox/team/rating filters. Done by f441680
B8 B8.2 Align public CSAT submit/update flow, lock window, message linkage, and one-response-per-message behavior. reference/chatwoot/app/controllers/public/api/v1/csat_survey_controller.rb, CSAT response model/services. Public handler tests for submit, update within window, update after lock, duplicate response rejection. Done by ef3a909
B8 B8.3 Wire resolve-triggered CSAT survey send with idempotency and channel-aware message creation. Chatwoot CSAT listener/jobs and inbox CSAT settings. Listener tests prove one CSAT message per resolved conversation and no send when disabled. Done by ef3a909; WhatsApp/Twilio external template dispatch remains integration follow-up
B8 B8.4 Align CSAT downloads/export payloads used by reports screens. CSAT report/download controller paths and frontend report API. CSV/download tests cover filters and frontend column names. Todo
B9 B9.1 Align automation rule CRUD payloads, validation, condition groups, and event names. Chatwoot automation rule controllers/models and dashboard automation builder. Handler/service tests for create/update/list/show/delete and validation errors. Todo
B9 B9.2 Implement automation action side effects for labels, status, priority, assignee/team, custom attributes, private notes, and snooze. Chatwoot automation action services/listeners. Rule listener tests assert real conversation mutations and stop-on-match behavior. Todo
B9 B9.3 Add durable execution logs, delayed actions, webhook delivery, and email transcript delivery boundaries. Chatwoot automation jobs and webhook/transcript actions. Worker/action tests cover retryable external actions and observable failures. Todo
B9 B9.4 Align macro CRUD/availability and macro execution side effects. Chatwoot macros controller/model/action execution. Macro handler/service tests cover frontend payloads and conversation mutations. Todo
B10 B10.1 Align audit log serializer, filters, pagination, actor/request metadata, and admin route behavior. Chatwoot enterprise audit controllers/models. Audit list tests plus representative mutation writer tests. Todo
B10 B10.2 Align CustomRole permission keys, account-user role resolution, deletion nullification, and authorization failure payloads. Chatwoot custom role controllers/policies and permission constants. Permission matrix tests for admin/non-admin/custom-role access. Todo
B10 B10.3 Review remaining InboxLimit/account-limit create-path enforcement outside capacity policies. Chatwoot enterprise account/inbox limit policies. Inbox/channel create tests for over-limit behavior and frontend-readable errors. Todo
B11 B11.1 Align Captain assistant CRUD, inbox bindings, responses, documents, scenarios, and custom tools payloads. Captain controllers/services/frontend clients under reference/chatwoot. Handler/service fixtures for every Captain dashboard client path. Todo
B11 B11.2 Align Copilot threads, messages, tasks, preferences, playground/tool-call behavior, and disabled-state feature gates. Copilot controllers/services/frontend clients under reference/chatwoot. Copilot handler/service tests for persistence, disabled LLM state, and frontend payloads. Todo
B11 B11.3 Add document sync/embedding/LLM job boundaries where external dependencies are required. Captain/Copilot jobs and document services. Worker tests or explicit feature-gated fallback tests. Todo
B12 B12.1 Add a repeatable command to run the reused Chatwoot frontend against GoChat. reference/chatwoot frontend boot/auth/API clients. Smoke command documented and runnable locally. Todo
B12 B12.2 Cover login, inbox list/settings, conversation list/detail/message send, contact/company views, widget init/message, public CSAT, SLA/CSAT reports, and enterprise admin screens. Dashboard route usage and frontend stores/API modules. Smoke report checked into docs/parity/ with pass/fail gaps. Todo

B8 CSAT execution breakdown:

Step Implementation target Reference source Required tests Status
B8.2a Re-audit public CSAT payload shape against Rails controller and Jbuilder views, including display_type, content, inbox identity, locale, message ID, conversation ID, and nested csat_survey_response. reference/chatwoot/app/controllers/public/api/v1/csat_survey_controller.rb, show.json.jbuilder, update.json.jbuilder, _csat_survey.json.jbuilder. Public show/update handler fixture tests assert exact Chatwoot keys and no local response envelope. Done by ef3a909
B8.2b Enforce Chatwoot's 14-day date lock from the CSAT message creation date, and keep public inbox message update on the same lock path. CsatSurveyController#update, public inbox messages controller, input_csat message behavior. Public CSAT and public inbox update tests cover within-window success and after-window 422. Done by ef3a909
B8.2c Make response creation/update message-linked and idempotent: one CSAT response per input_csat message, updates mutate the existing response, and repeated public submissions do not create extra rows. CsatSurveys::ResponseBuilder, Message#csat_survey_response, CSAT response model uniqueness. Service tests count rows after repeated public updates and assert message/contact/conversation/assignee linkage. Done by ef3a909
B8.3a Replace the current resolve listener placeholder with Chatwoot-style survey message creation when an inbox has CSAT enabled. reference/chatwoot/app/listeners/csat_survey_listener.rb, CsatSurveyService, inbox CSAT settings. Listener tests prove disabled inboxes do not send, enabled inboxes create exactly one input_csat outgoing message, and repeated resolve events are idempotent. Done by ef3a909
B8.3b Keep CSAT survey sending inside a clear job boundary even if execution remains synchronous for now, so durable worker migration can happen under Phase 5 without changing behavior. Chatwoot listener/job boundary and existing Go channel dispatcher. Unit tests cover enqueue/perform boundary or documented synchronous fallback with idempotency. Review; ef3a909 isolates behavior in SendSurveyForConversation, durable queue remains Phase 5
B8.4a Align CSAT download CSV with Chatwoot report filters and columns, including review notes where the frontend exposes them. CSAT report/download controller, dashboard reports API. CSV tests cover date, agent, inbox, team, rating filters and expected column names/order. Todo

B9 automation and macro execution breakdown:

Step Implementation target Reference source Required tests Status
B9.1a Align automation-rule CRUD request/response envelopes, condition/action validation, and rule status toggles. Chatwoot automation rules controller/model and dashboard automation client. Handler tests for index/show/create/update/delete/toggle payloads and validation failures. Todo
B9.1b Implement real rule trigger coverage for conversation created/updated/resolved, message created, contact updated, and webhook/provider events. Chatwoot automation listener and existing channel.Dispatcher events. Listener tests prove matching rules execute once per event with account/inbox filters. Todo
B9.1c Persist execution logs and stop-on-match behavior, including failed action records. Chatwoot automation execution/log models and services. Service tests cover success, skipped, failed, retryable, and stop-on-match flows. Todo
B9.1d Make webhook and email transcript actions timeout-bound, retryable, and observable. Chatwoot action services/jobs and Phase 5 worker plan. Tests use fake HTTP/mailer boundaries and assert retry/log metadata. Todo
B9.2a Align macro CRUD payloads, visibility rules, and validation. Chatwoot macros controller/model and dashboard macro client. Handler tests for raw macro payloads, ownership, account scoping, and delete status. Todo
B9.2b Execute macro actions against real conversations: labels, status, priority, assignee, team, custom attributes, private notes, and attachments where supported. Chatwoot macro execution service and conversation/message controllers. Macro execute tests reload conversation/message rows and assert Chatwoot-shaped response payloads. Todo

B10 admin and limits breakdown:

Step Implementation target Reference source Required tests Status
B10.1 Align audit list filters, serializer fields, and pagination before broadening writers. Chatwoot enterprise audit controller/model. Audit handler tests for account scope, action/auditable filters, actor metadata, and pagination. Todo
B10.2 Add audit writer coverage for representative core and enterprise mutations: inbox, conversation assignment/status, SLA policy, capacity policy, custom role, automation, macro, CSAT review notes. Chatwoot audit hooks and current Go service mutation points. Mutation tests assert audit rows with actor, auditable type/id, account, IP/request metadata where available. Todo
B10.3 Align custom role permission keys, serializers, delete-nullification behavior, and AccountUser permission resolution. Chatwoot enterprise custom roles policy/model/controller. Permission matrix tests and handler tests for admin/non-admin access. Todo
B10.4 Review legacy InboxLimit routes versus Chatwoot account-level limit behavior and enforce create limits in inbox/channel creation paths. Chatwoot enterprise inbox limit behavior, existing capacity-limit implementation. Inbox/channel creation tests cover under-limit and over-limit frontend-readable errors. Todo

B11 Captain/Copilot breakdown:

Step Implementation target Reference source Required tests Status
B11.1 Align Captain assistant CRUD, inbox bindings, scenarios, responses, and document/custom-tool payloads. reference/chatwoot Captain controllers/services/frontend clients. Handler/service tests for assistant lifecycle, nested resources, account scoping, and feature-disabled state. Todo
B11.2 Implement document sync/indexing gates for Meilisearch or the chosen embedding/search backend without blocking the frontend when LLM config is absent. Captain document and embedding services. Tests cover disabled state, failed sync observability, and successful fake backend indexing. Todo
B11.3 Align Copilot thread/message/task APIs, tool calls, preferences, and streaming fallback. Copilot controllers/services/frontend clients. Handler tests cover thread/message/task lifecycle, tool-call persistence, disabled-state payloads, and non-streaming fallback. Todo

B12 reused frontend verification breakdown:

Step Implementation target Reference source Required tests Status
B12.1 Add a repeatable smoke command that boots GoChat plus the reused reference/chatwoot frontend without adapters. Local app boot scripts, Chatwoot dashboard frontend. Checked command and gap report under docs/parity/. Todo
B12.2 Cover login, current user, inbox list, conversation list/detail, message send, contact/company view, and widget init/message. Dashboard/widget frontend routes and API clients. Smoke output records pass/fail and links failed API calls to route/serializer tasks. Todo
B12.3 Add enterprise smoke coverage as B8-B11 land: SLA reports, CSAT public/account reports, automation/macros, audit/custom roles, Captain/Copilot. Enterprise frontend screens and clients. Smoke output keeps enterprise failures as named follow-up tasks, not hidden browser-only debt. Todo

Hermes plan material now mapped:

  • .hermes/plans/2025-05-24-global-search-meilisearch.md maps to Phase 1/B6. The Meilisearch interface, config, documents, indexing hooks, reindex command, payload shape, and live gate are already tracked here. Remaining search work is only future payload gaps discovered by frontend smoke or route expansion.
  • .hermes/plans/2026-05-24-automation-macro-csat.md maps to B8 and B9. CSAT model/service/listener/handlers, automation rule handlers, macro handlers, route wiring, action side effects, and durable jobs are tracked above instead of relying on the Hermes-era checklist.

Next checkpoint contract for B7.6:

Step Required investigation Landing rule Status
B7.6a Compare Go AppliedSlaService target-time calculation with Chatwoot Sla::EvaluateAppliedSlaService and business-hours helpers. If only_during_business_hours changes timer math in Rails, add Go tests proving closed-hour/weekend skipping or record the exact missing job/helper gap. Done by d23f3f0; current reference stores the flag but calculates thresholds as elapsed seconds, covered by regression test.
B7.6b Compare Go SlaEvent creation with Chatwoot SlaEvent#create_notifications. New SLA miss events create notifications for participants, account administrators, and assignee with Chatwoot notification types, deduped by user/event. Done by d23f3f0.
B7.6c Compare SLA processing jobs with current Go worker boundaries. Any missing async schedule/retry behavior is named in Phase 5, not hidden under B7 Done. Review; durable account-wide SLA processing remains Phase 5 worker scope.
B7.6d Update this tracker and commit after implementation. B7.6 row moves to Done or Review with named deferred job gaps, and verification commands are recorded. Done by this documentation checkpoint.

Active B4 task board:

ID Task Reference source Status Exit gate
B4.1 Contact/company CRUD/search serializers and empty-search errors. Contacts and enterprise companies controllers/views. Done 3481597; CRM handler tests.
B4.2 Company-contact relation semantics on contacts.company_id. Enterprise company contacts controller. Done 3481597; relation add/list/search tests.
B4.3 Contact label list/update and label-filtered contact search. contacts/labels_controller.rb, LabelConcern, contact frontend API. Done 7a033e2; label update/list/filter tests.
B4.4 Contact/company avatar delete and company destroy-custom-attributes. Contacts controller avatar; enterprise companies controller avatar and destroy_custom_attributes. Done 7a033e2; avatar/custom-attribute tests.
B4.5 Contact merge behavior and response shape. actions/contact_merges_controller.rb, ContactMergeAction, contact merge Jbuilder view. Done 1e3bf47; merge route/handler/service tests.
B4.6a Contact import/data-import persistence. Contacts import controller, data import model/job, DataImport::ContactManager. Done 8eed2f1; import handler/service tests cover status codes, lifecycle persistence, merge/update matching, labels, and custom attributes.
B4.6b Contacts export artifact/download behavior. Contacts export controller and Account::ContactsExportJob. Done 47c2b80; export request persists CSV artifact, download URL, label/filter data, row count, and notification metadata.
B4.6c Contacts export email delivery. AdministratorNotifications::AccountNotificationMailer#contact_export_complete. Done c2766fe; export completion calls a mailer boundary with the Chatwoot subject, requesting user's email, and export download URL. SMTP env mailer no-ops when not configured.
B4.6d Contact import label validation. DataImportJob#build_contact_from_row approved-label validation. Done 653db81; import rejects unknown labels, keeps valid rows, records failed counts, and avoids creating arbitrary tags.
B4.7a Contact/company notes payload depth. Contact notes controller/Jbuilder, company notes frontend consumer. Done af57482; raw contact note array/object fixtures, nested user payloads, empty destroy response, and company note user metadata tests.
B4.7b Contact/company conversations payload depth. Nested conversations controllers and Jbuilder views. Done fb775b7; nested CRM conversation payload fixtures cover shared conversation partial fields, latest messages, sender metadata, and company latest-20 ordering.
B4.8 CRM search through Meilisearch document shape. Chatwoot frontend search usage and local Meilisearch engine. Done 202da19; contact/company CRM search services use the configured search reader for non-empty queries, keep CRM serializers by resolving result IDs, and tests cover mocked search-reader usage plus company search fallback shape.
B4.9 Reused frontend CRM smoke. reference/chatwoot dashboard contacts/companies screens and API clients. Done ad29dc3; contact list/search/show/edit/labels/contactable inboxes/notes/conversations and company relation/detail flows run through Chatwoot-shaped API calls without adapters. Browser Playwright remains B12.

Immediate Execution Queue

This is the ordered queue for the next implementation slices. Do not skip the route and test gates even when working on deeper business behavior.

Order Work item Primary phase Done when Status
Q1 Expand tracked route parity to Captain/Copilot, assignment policies, widget, public inbox/contact/conversation, public CSAT, v2 reports, summary reports, and live reports. Phase 2 cmd/route_parity tracks these groups and docs/parity/route_parity.md lists every missing/mismatched route. Done
Q2 Patch route aliases discovered by Q1, especially Chatwoot widget/public paths such as /api/v1/widget/... versus existing /widget/.... Phase 2 Missing tracked routes return to zero or are explicitly documented with implementation tasks. Done
Q3 Add route boot regression coverage for wildcard conflict groups before expanding more Rails-style resources. Phase 2 Router tests cover nested dynamic resources that previously risked Gin conflicts. Done
Q4 Start serializer parity fixtures for auth/session, conversations/messages, contacts/companies, inboxes, notifications, and search. Phase 3 Each area has at least one reference fixture and Go response test. Doing
Q5 Review Meilisearch document shape and endpoint payloads against Chatwoot frontend consumers. Phase 1 and Phase 3 Search remains Meilisearch-first and payload mismatches are fixed or tracked. Review
Q6 Implement provider-specific webhook ingress for Chatwoot public webhook paths. Phase 6 Generic webhook placeholder no longer masks provider gaps; Telegram, LINE, SMS/Twilio, WhatsApp, Instagram/Twitter/TikTok routes resolve and verify like Chatwoot where supported. Done
Q7 Burn down enterprise gaps in this order: SLA, assignment policy and capacity, CSAT, automation/macros, Audit, CustomRole, InboxLimit, Captain/Copilot. Phase 4 and Phase 5 Each feature passes route, persistence, auth, side-effect, response, and test checks. Doing
Q8 Add frontend smoke harness using the reused Chatwoot frontend once core API flows boot end-to-end. Phase 7 Login, inbox list, conversation list/detail, message send, contact view, and widget init run without frontend adapters. Todo

Current Decision Ledger

No new user confirmation is required before continuing the next implementation slice.

Topic Locked decision Consequence
Frontend Reuse reference/chatwoot frontend directly. Backend URLs, request payloads, response serializers, auth behavior, async side effects, and error envelopes must match Chatwoot.
Reference Local reference/chatwoot wins over older docs. Every task must cite or inspect matching Rails route/controller/model/job behavior before being marked done.
Test order Keep go test ./... green before deepening behavior. Route/handler fixes and regression tests come before broad enterprise implementation.
Search Meilisearch is mandatory. DB search can stay only as explicit development fallback; all final search payload and indexing work targets Meilisearch.
Enterprise scope Exclude SSO/SAML/LDAP/OIDC; include all other paid features. Do not spend roadmap capacity on SSO family except safe disablement. SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, and assignment policies remain in scope.

End-to-End Milestone Map

These milestones are the tracking spine for the remaining Chatwoot frontend reuse work. A milestone is complete only after its verification gates pass and this document records the commit/result.

Milestone Scope Exit gate Status
M0 Test, route, and documentation baseline. Clean worktree, go test ./..., route dump/parity artifacts current. Done
M1 Meilisearch-first search foundation. Config, engine, indexing hooks, reindex command, mocked tests, and live-shape review tracked. Review
M2 Route parity expansion for frontend-critical routes. Tracked route set has zero missing routes and every new route group has router boot coverage. Doing
M3 Serializer parity for frontend API families. Fixture tests cover auth/profile, accounts/users, inboxes, conversations/messages, contacts/companies, notifications, reports, widget/public, and search. Doing
M4 Core handler placeholder burn-down. Account/contact/conversation/message/inbox/webhook handlers are repository-backed and no frontend-critical route returns placeholder JSON. Doing
M5 Paid feature parity excluding SSO family. SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation/macros, and assignment policies pass route, persistence, auth, side-effect, serializer, and tests. Doing
M6 Durable jobs and external integrations. Search indexing, CSAT send, automation actions, notifications, webhooks, and external deliveries are queued, retryable, logged, and idempotent. Todo
M7 Reused Chatwoot frontend smoke validation. Chatwoot frontend boots against GoChat for login, inbox list, conversation detail, message send, contact view, widget init/message, public CSAT, and key enterprise screens without adapters. Todo

Slice Backlog

Work proceeds top-down unless a failing test or frontend blocker forces a narrower fix.

Slice Work Reference source Verification Status
B1 Webhook ingress route and handler parity. reference/chatwoot/config/routes.rb:614-624, reference/chatwoot/app/controllers/webhooks/*, reference/chatwoot/app/controllers/api/v1/webhooks_controller.rb Provider lookup tests, router route dump, go test ./.... Done
B2 Auth/profile serializer fixtures. reference/chatwoot/app/controllers/api/v1/profile*, frontend auth client. Fixture tests for login/current user/profile/availability/settings. Done
B3 Conversation/message serializer and behavior fixtures. Chatwoot conversation/message controllers, entities, jobs. Fixture tests for list/show/create/update/private notes/attachments/status/assignment. Review
B4 Contact/company behavior fixtures. Chatwoot contact/company controllers, merge/import/export/notes/labels. Fixture tests for CRUD/search/merge/relation/import-export shells and frontend CRM API smoke. Review
B5 Inbox/channel behavior fixtures. Chatwoot inbox/channel controllers and channel models. Fixture tests for inbox CRUD, settings, business hours, members, avatar, channel config. Done
B6 Meilisearch live-shape review. Chatwoot frontend search usage and search controllers. Meilisearch-backed response fixtures plus optional live integration gate. Review
B7 SLA and assignment capacity. Chatwoot enterprise SLA and assignment policy behavior. Unit/integration tests for SLA state, breach, assignment capacity, policy selection. Review
B8 CSAT account/public/send completion. Chatwoot CSAT survey responses, reports, downloads, listeners. Metrics/list/review/download/send idempotency tests. Doing
B9 Automation/macros durable side effects. Chatwoot automation/macro services and jobs. Action execution, logs, webhook/email transcript retry tests. Todo
B10 Audit, CustomRole, InboxLimit. Chatwoot enterprise admin behavior and policies. Authorization, audit emission, limits enforcement, admin payload fixtures. Todo
B11 Captain/Copilot deep behavior. Chatwoot Captain/Copilot controllers, services, frontend clients. Assistant/tool/document/scenario/copilot thread/task tests and feature gates. Todo
B12 Frontend smoke harness. reference/chatwoot frontend. Repeatable smoke command and checked gap report. Todo

Remaining slice landing plan:

Slice First implementation checkpoint Follow-up checkpoints Done classification rule
B6 Compare current Meilisearch document fields and global/entity search payloads against Chatwoot frontend consumers. Add optional live Meilisearch integration gate, tighten account-scoped filters, and document DB fallback as development-only. Done only after mocked tests and live-shape review prove search payloads work without DB-only assumptions.
B7 Done: assignment capacity enforcement, SLA policy CRUD payload parity, applied-SLA conversation persistence, idempotent SLA breach lifecycle, applied-SLA report/list payload parity, and SLA miss notification fan-out. Capacity-aware assignment policy binding review and durable SLA job scheduling remain broader Phase 5/P4.9 follow-ups, not hidden SLA gaps. Review after d23f3f0; move to Done only after assignment policy binding review is either implemented or split out of B7.
B8 Done: account-side CSAT list/metrics/review-note payload parity, public submit/update idempotency, and resolve-triggered generic survey message send. CSAT download filters/CSV and channel-specific WhatsApp/Twilio template send hooks. Done only after account APIs, public APIs, resolve listener, and CSV/download behavior agree on one message-linked response model; external template dispatch may remain a named Phase 5 integration gap.
B9 Macro execution side effects for labels, status, assignee/team, priority, private notes, and attachments. Automation rule condition/action parity, execution logs, webhook delivery retry, email transcript delivery, stop-on-match semantics. Done only after actions mutate real conversations and async external actions are retryable or explicitly feature-gated.
B10 Audit list payload and audit writer boundary for representative mutating core resources. CustomRole permission-key parity, AccountUser permission resolution, InboxLimit enforcement in inbox/channel creation paths. Done only after authorization tests prove admin-only surfaces and non-admin denial shapes match Chatwoot.
B11 Captain Assistant CRUD, inbox binding, responses, documents, and custom tools payload fixtures. Copilot threads/messages/tasks, playground/tool-call behavior, document sync/embedding feature gates, streaming fallback. Done only after LLM-dependent behavior is either implemented behind config or safely stubbed with frontend-compatible disabled states.
B12 Boot reused Chatwoot frontend against GoChat auth/profile/inbox/conversation/contact flows. Add smoke paths for widget init/message, public CSAT, reports, and enterprise screens as B7-B11 land. Done only after the smoke command is repeatable and writes a checked gap report.

Per-slice documentation rule:

Checkpoint type Required doc update
Implementation commit Add a commit ledger row, update the active task board status, add or refresh the current checkpoint bullets, and record verification commands.
Route change Regenerate docs/parity/gochat_routes.txt; regenerate docs/parity/route_parity.md when tracked route sets change; update route counts in Current Baseline.
Serializer or request contract change Link the reference/chatwoot controller/view/frontend client and add the fixture or handler test name that proves the contract.
Enterprise feature checkpoint Update both the enterprise tracking table and the relevant B7-B11 remaining-slice row so the paid-feature scope stays visible.
Deferred gap Keep the parent slice in Review or Doing; add a named follow-up instead of silently marking Done.

Phase 0: Test And Route Baseline

Status: done.

Checklist:

  • Fix internal/handler/api/v1 route param mismatches around :account_id, :id, and resource IDs.
  • Fix malformed test routes that conflict with Gin wildcard rules.
  • Add defensive handling for zero-value services in handler edge tests.
  • Align handler error responses where tests encode the expected Chatwoot-compatible status category.
  • Keep platform routes bootable and dumpable.
  • Verify go test ./....
  • Verify go run ./cmd/dump_routes.

Verification commands:

env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes

Phase 1: Meilisearch Search Engine

Status: review.

Source material:

  • .hermes/plans/2025-05-24-global-search-meilisearch.md
  • reference/chatwoot search controllers, models, and indexing behavior
  • Current local packages under internal/search, internal/handler/api/v1/search_handler.go, and entity repositories

Checklist:

  • Define a stable SearchEngine interface for Meilisearch-backed search and indexing.
  • Add search config for engine, host, API key, and index prefix.
  • Implement Meilisearch client wrapper with index bootstrapping and settings.
  • Define per-entity documents for conversations, messages, contacts, companies, articles, and help-center content as required by Chatwoot frontend behavior.
  • Wire create/update/delete hooks from services into indexing.
  • Add batch reindex command for existing data.
  • Keep DB search only as explicit development fallback, not as final production mode.
  • Add tests with a mocked search engine and integration hooks that can run without a live Meilisearch instance.
  • Document required Meilisearch environment variables and local startup flow.

Acceptance:

  • Global search endpoint and entity search endpoints return Chatwoot-compatible payloads.
  • Search results are account-scoped.
  • Search indexing survives entity updates and deletes.
  • go test ./... stays green.

Tracking table:

ID Task Target files Reference source Status
P1.1 Add SearchConfig with engine, host, API key, index prefix, env binding, and defaults. internal/config/config.go, configs/config.yaml, config tests .hermes/plans/2025-05-24-global-search-meilisearch.md Done
P1.2 Add stable engine contract for search, indexing, deletes, batch indexing, and close. internal/search/engine.go Chatwoot global/entity search behavior Done
P1.3 Implement Meilisearch engine wrapper, index naming, bootstrap, sortable/filterable/searchable settings. internal/search/engine_meili.go Chatwoot search models/services Review
P1.4 Keep existing DB search as explicit dev fallback only. Production config must prefer Meilisearch. internal/search/engine_db.go, internal/search/search_service.go, internal/config/validator.go, cmd/reindex_search User decision on Meilisearch Done
P1.5 Define documents and serializers for conversations, messages, contacts, companies, articles, and help-center content. internal/search/engine.go reference/chatwoot models/serializers Review
P1.6 Wire create/update/delete hooks from entity services into async or synchronous indexing boundary. internal/service/*, internal/search/search_service.go, internal/app/bootstrap.go Chatwoot callbacks/jobs Done
P1.7 Add batch reindex command and account/entity filters. cmd/reindex_search Chatwoot reindex/search tasks Done
P1.8 Add mocked engine tests and service integration tests without requiring live Meilisearch. internal/search/engine_test.go, internal/search/engine_meili_live_test.go, internal/config/config_test.go Existing test style Done
P1.9 Document Meilisearch env vars and local startup flow. this doc, ops docs if needed Hermes plan Done

Meilisearch local flow:

docker run --rm -p 7700:7700 -e MEILI_MASTER_KEY=gochat_dev getmeili/meilisearch:latest
GOCHAT_SEARCH_ENGINE=meilisearch GOCHAT_SEARCH_HOST=http://localhost:7700 GOCHAT_SEARCH_API_KEY=gochat_dev go run ./cmd/reindex_search -types all

Search environment variables:

Variable Default Notes
GOCHAT_SEARCH_ENGINE meilisearch Use db only for explicit local fallback.
GOCHAT_SEARCH_HOST http://localhost:7700 Meilisearch endpoint.
GOCHAT_SEARCH_API_KEY empty Set to Meilisearch master/search key when enabled.
GOCHAT_SEARCH_INDEX_PREFIX gochat_ Prefixes indexes such as gochat_conversations.
GOCHAT_SEARCH_TIMEOUT_SECONDS 5 HTTP timeout for search/index requests.

Phase 2: Route And Controller Parity Audit

Status: doing.

Checklist:

  • Dump Chatwoot route source declarations from reference/chatwoot/config/routes.rb.
  • Dump GoChat routes with cmd/dump_routes.
  • Build a first tracked route parity table covering method, path, controller, source, and route status.
  • Prioritize first frontend-critical API v1 account routes used by the Chatwoot web app.
  • Extend parity table with auth scope, request params, response serializer, and handler implementation status.
  • Patch first-batch route names and wildcard params where Gin constraints require different internal names, while preserving external URLs.
  • Add regression tests for the contact conversations relation route added in this slice.
  • Expand route boot regression coverage for tracked Gin wildcard/param conflict groups.

Acceptance:

  • Route gap report is generated and checked in.
  • All currently implemented routes boot without panic.
  • Missing frontend-critical routes have tickets or implementation tasks.

Tracking table:

ID Task Artifact Status
P2.1 Generate Chatwoot route dump from reference/chatwoot. docs/parity/chatwoot_routes_static.md Review
P2.2 Generate GoChat route dump with cmd/dump_routes. docs/parity/gochat_routes.txt Done
P2.3 Produce route parity table: method, path, controller/handler, auth, request params, serializer, status. docs/parity/route_parity.md Review
P2.4 Mark frontend-critical gaps from Chatwoot web app route usage. docs/parity/route_parity.md Done
P2.5 Convert existing placeholders/stubs into tracked feature tasks instead of hidden debt. this doc and gap report Doing
P2.6 Add route boot regression tests for Gin wildcard/param conflicts. internal/router/router_test.go Done
P2.7 Track Captain/Copilot route group from routes.rb:62-89. cmd/route_parity, docs/parity/route_parity.md Done
P2.8 Track assignment policies and inbox assignment policy routes from routes.rb:306-313. cmd/route_parity, router Done
P2.9 Track widget API routes from routes.rb:442-472; keep /api/v1/widget as the Chatwoot-compatible surface. cmd/route_parity, router widget mount Done
P2.10 Track public API routes from routes.rb:569-585, including public inbox contacts, conversations, messages, and CSAT survey. cmd/route_parity, public handlers Done
P2.11 Track v2 reports, summary reports, and live reports from routes.rb:479-513. cmd/route_parity, reports handlers Done

Current Phase 2 route findings:

Type Count Required action
Exact tracked critical routes 270 Keep covered while expanding audit scope.
Method-compatible update routes 0 First tracked batch now has exact Rails-compatible method coverage.
Parameter-compatible routes 7 Nested AgentCapacityPolicy user/inbox-limit routes use Gin-internal parameter names while preserving external path shape.
Missing tracked critical routes 0 Current tracked frontend-critical route set has no route-level gaps.

Expanded tracked groups now covered by route parity:

Area Coverage
Agents and assignable agents CRUD/bulk-create route coverage for agents, plus assignable_agents#index.
Canned responses index/create/update/destroy account routes.
Custom attributes and custom filters index/show/create/update/destroy account routes.
Labels and team membership labels CRUD plus teams and team_members collection actions.
Notifications account-scoped notifications, notification_settings, unread_count, read_all, destroy_all, snooze, unread.
Inbox members Chatwoot account-scoped inbox_members create/show/update/destroy routes.
Inbox member actions agent_bot, set_agent_bot, sync_templates, health, register_webhook, reset_secret, avatar.
Captain/Copilot Assistants, assistant inboxes/scenarios, assistant responses, bulk actions, copilot threads/messages, custom tools, documents, preferences, and tasks.
Assignment policies Account assignment policies, nested inbox bindings, and inbox assignment policy routes.
Agent capacity policies Account policy CRUD plus nested users and inbox capacity limits from routes.rb:123-127.
Applied SLA reports Account applied-SLA index, metrics, and download routes from routes.rb:228-231.
Widget API Chatwoot /api/v1/widget/* route surface plus legacy /widget/* compatibility.
Public API Public inbox contact/conversation/message routes and public CSAT survey route.
Reports v2 /api/v2/accounts/:account_id summary reports, reports, and live reports.
CRM nested routes Contact active/search/filter/export/import, contactable inboxes, contact labels/contact inboxes, company avatar/custom-attribute/contact search routes.

New route groups tracked in this slice:

Area Chatwoot source Current Go surface observed Expected action
Captain/Copilot reference/chatwoot/config/routes.rb:62-89 Many /api/v1/accounts/:account_id/captain/... routes already existed. Added tracked critical set; route parity is exact.
Assignment policies reference/chatwoot/config/routes.rb:306-313 /assignment_policies and local /assignment_policies_v2 routes exist. Added tracked route set and aligned singular inbox assignment delete route.
Widget API reference/chatwoot/config/routes.rb:442-472 GoChat exposed many routes only under /widget. Added /api/v1/widget aliases; behavior parity remains tracked under Phase 3/6.
Public API reference/chatwoot/config/routes.rb:569-585 GoChat had public CSAT conversation routes. Added public inbox/contact/conversation/message route surface and Chatwoot public CSAT path.
Reports v2 reference/chatwoot/config/routes.rb:479-513 GoChat had report-style routes under /api/v1/accounts. Added exact v2 /api/v2/accounts/:account_id/... report paths.

Closed tracked critical route gaps in this slice:

Method Path Chatwoot controller Implementation note
GET /api/v1/accounts/:account_id/contacts/:contact_id/conversations api/v1/accounts/contacts/conversations#index Added contact conversation relation endpoint returning Chatwoot payload shape, optional inbox_id, and latest-20 ordering.
POST /api/v1/accounts/:account_id/contacts/export api/v1/accounts/contacts#export Added Chatwoot async request path returning 200 OK; existing CSV download route remains for local compatibility.
POST /api/v1/accounts/:account_id/conversations/:conversation_id/toggle_priority api/v1/accounts/conversations#toggle_priority Added Chatwoot action path returning 200 OK.

Closed Rails update method gaps:

Expected Existing GoChat route Implementation note
PUT /api/v1/accounts/:account_id/conversations/:conversation_id PATCH /api/v1/accounts/:account_id/conversations/:conversation_id Added PUT alias to the same update handler.
PUT /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id PATCH /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id Added PUT alias to the same update handler.

Phase 2 commands:

env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity

Phase 3: Data And Serializer Parity

Status: doing.

Checklist:

  • Compare key Chatwoot serializers/entities with Go response payloads.
  • Align account, user, inbox, conversation, message, contact, company, team, label, canned response, campaign, help center, and notification payload shapes.
  • Verify timestamps, IDs, enum strings, nested objects, pagination metadata, and error envelopes.
  • Add fixture-driven tests for payload compatibility.

Acceptance:

  • Chatwoot frontend can consume the payloads without adapter code.
  • Serializer deviations are documented only where GoChat intentionally differs.

Frontend-critical API groups to audit first:

ID Area Scope Status
P3.1 Auth/session/profile Login, logout, current user, profile, availability, notification settings. Done
P3.2 Accounts/users/teams Account settings, users, agents, teams, invitations, roles, permissions. Todo
P3.3 Inboxes/channels Inbox CRUD, assignable agents, avatars, channel config, business hours, widget config. Done
P3.4 Conversations/messages List filters, status changes, assignment, labels, private notes, attachments, drafts, typing/read events. Review
P3.5 Contacts/companies CRUD, merge, labels, notes, custom attributes, import/export, conversations relation. Doing
P3.6 Labels/custom attributes/custom filters Create/update/list behavior and exact response shapes. Todo
P3.7 Notifications/reports/help center/campaigns Frontend-visible payloads and pagination/error envelopes. Todo
P3.8 Widget/public APIs Widget init, campaigns, config, contact, conversations, messages, direct uploads, public inbox flow, public CSAT. Doing
P3.9 Search payloads Global search and entity search documents backed by Meilisearch. Review

Serializer parity work plan:

Order Endpoint family Reference sources Verification artifact Status
S1 Auth and profile reference/chatwoot/app/controllers/api/v1/profile*, frontend auth API usage fixture tests for current user/profile payloads Done
S2 Conversations and messages reference/chatwoot/app/controllers/api/v1/accounts/conversations*, serializers/entities fixture tests for index/show/message create/update Review
S3 Contacts and companies reference/chatwoot/app/controllers/api/v1/accounts/contacts*, companies* fixture tests for list/show/search/merge/relation payloads Doing
S4 Inboxes and channels reference/chatwoot/app/controllers/api/v1/accounts/inboxes*, channel controllers fixture tests for inbox CRUD, channel settings, widget config Done
S5 Notifications and settings reference/chatwoot/app/controllers/api/v1/accounts/notifications* fixture tests for notification list/actions/settings Todo
S6 Reports and CSAT reference/chatwoot/app/controllers/api/v1/accounts/reports*, csat_survey_responses* fixture tests for report filters and CSAT metrics/list Doing
S7 Widget/public reference/chatwoot/app/controllers/api/v1/widget*, public/api/v1* widget smoke fixtures and public flow tests Doing
S8 Search reference/chatwoot search controllers plus frontend search client Meilisearch-backed search response fixtures Todo

Serializer comparison rules:

  • Compare against reference/chatwoot serializers/entities before changing Go responses.
  • Prefer fixture-driven tests for exact JSON shape, enum strings, pagination metadata, and error envelopes.
  • Preserve Chatwoot field names even if Go internal naming differs.

Phase 4: Enterprise Feature Completion

Status: doing.

Excluded:

  • SSO
  • SAML
  • LDAP
  • OIDC

Included checklist:

  • SLA policies and SLA event tracking.
  • Audit logs and admin-readable audit endpoints.
  • Custom roles and permission checks.
  • Agent capacity and assignment limits.
  • Assignment policies and auto-assignment compatibility.
  • Captain/Copilot assistant, custom tools, scenarios, documents, responses, and inbox bindings.
  • CSAT survey response flow, metrics, filters, and review notes.
  • Inbox limits and account/inbox usage enforcement.
  • Automation rules, macros, execution logs, and action side effects.

Acceptance:

  • Each included feature has routes, persistence, authorization, tests, and Chatwoot-compatible response behavior.
  • Unsupported SSO family features are explicitly disabled or omitted without breaking frontend navigation for enabled features.

Enterprise tracking table:

ID Feature Existing Go surface Required next work Status
P4.1 SLA policies/events internal/model/sla_policy.go, internal/model/sla_event.go, internal/service/sla_policy_service.go, internal/service/applied_sla_service.go, internal/service/sla_event_service.go, internal/handler/api/v1/sla_policy_handler.go SLA policy CRUD, applied-SLA conversation attach, idempotent core breach lifecycle, applied-SLA reports, current reference timing review, and SLA miss notifications are implemented; durable job scheduling remains Phase 5. Review
P4.2 Audit logs internal/model/audit.go, internal/service/audit_service.go, internal/repository/audit_repo.go, internal/handler/api/v1/audit_handler.go Ensure every mutating enterprise/core action emits audit events and filters match Chatwoot. Todo
P4.3 Custom roles/permissions internal/model/custom_role.go, internal/service/custom_role_service.go, internal/middleware/role_check.go, internal/handler/api/v1/custom_role_handler.go Align permission keys, inherited roles, authorization failures, and admin UX payloads. Todo
P4.4 Agent capacity internal/model/agent_capacity_policy.go, internal/service/agent_capacity_policy_service.go, internal/handler/api/v1/agent_capacity_handler.go, internal/autoassignment/* First align AgentCapacityPolicy API, serializers, nested users, and InboxCapacityLimit data contracts; then enforce capacity in manual/auto assignment. Review
P4.5 Inbox limits internal/model/agent_capacity_policy.go InboxCapacityLimit, plus legacy internal/model/inbox_limit.go, internal/service/inbox_limit_service.go, internal/repository/inbox_limit_repo.go, internal/handler/api/v1/inbox_limit_handler.go Chatwoot enterprise nested agent_capacity_policies/:policy_id/inbox_limits and assignment-path enforcement are covered; next review account-level create-limit behavior and legacy route compatibility. Review
P4.6 Captain/Copilot internal/model/captain_models.go, internal/model/copilot_models.go, internal/service/captain_*, internal/service/copilot_*, internal/handler/api/v1/captain_*, internal/handler/api/v1/copilot_handler.go Complete assistant, tools, scenarios, documents, responses, inbox bindings, suggestions, and streaming compatibility. Todo
P4.7 CSAT internal/csat/*, internal/automation/csat_survey_*, internal/handler/api/v1/csat_*, internal/service/csat_metrics_service.go Account-side list/metrics/review-note payloads, public update depth, and resolve-triggered generic survey send are aligned; next wire download CSV parity and channel-specific template send hooks. Doing
P4.8 Automation and macros internal/automation/*, internal/handler/api/v1/automation_rule_handler.go, internal/handler/api/v1/macro_handler.go Finish action side effects, execution logs, webhook/email transcript delivery, and rule trigger coverage. Todo
P4.9 Assignment policies internal/autoassignment/*, internal/automation/agent_bot_rule_listener.go Match Chatwoot assignment policy behavior and availability/capacity rules. Doing

Enterprise work package breakdown:

Package Subtasks Must verify Status
SLA Policy CRUD parity, conversation SLA assignment, first-response/next-response/resolution timers, business-hours handling, breach events, notifications. Policy payloads, applied-SLA attach, seconds-based thresholds, core state transitions, breach idempotency, report payloads, reference only_during_business_hours timing, and notification fan-out are covered. Durable scheduling remains Phase 5. Review
Assignment and capacity Assignment policy CRUD, inbox policy binding, round-robin/availability/capacity selection, manual assignment limits, fallback behavior. Manual and automatic assignment respect policy, availability, team/inbox membership, and limits. Review
CSAT account/public side Survey send on resolve, response list, metrics, filters, downloads, review notes, resend/idempotency, public lock. Account report list/metrics/review-note payload fixtures are covered by f441680; public object-shaped submit/update, one-response-per-message idempotency, and resolve-triggered generic survey message send are covered by ef3a909; download CSV remains. Doing
Automation rules Condition/action parity, event listener coverage, delayed actions, execution logs, stop-on-match behavior, webhook and transcript actions. Rule trigger tests for conversation/contact/message events and durable retry for external actions. Todo
Macros Macro CRUD, availability by account/user, execute side effects, validation, audit/log output. Execute changes conversation labels/status/assignee/team/notes/custom attributes exactly as frontend expects. Todo
Audit Audit model parity, mutating action coverage, request metadata, filters/pagination, admin endpoint payloads. Representative mutations across core and enterprise features emit audit records. Todo
Custom roles Permission-key parity, account-user role resolution, policy middleware, create/update/delete behavior. Permission matrix tests and frontend admin payload fixtures. Todo
Inbox limits Account/inbox limit models, create/update enforcement, UI-readable limit responses, admin overrides. Chatwoot nested capacity-limit API and assignment-path enforcement are covered; account-level create limits remain for later review. Review
Captain/Copilot Assistants, inbox bindings, scenarios, responses, documents, tools, copilot threads/messages, tasks, streaming/tool-call behavior. Route fixtures, persistence tests, feature gates for external LLM dependencies, frontend smoke screens. Todo

Enterprise acceptance gates:

Feature Required gates before Done Reference notes
SLA Field names and units match Chatwoot; only_during_business_hours exists and current reference timing is covered; policy CRUD payloads match the enterprise Jbuilder views; applied SLA is created when sla_policy_id is attached; FRT/NRT/RT breach events are idempotent; report index/metrics/download payloads match Rails; SLA miss notifications use Chatwoot recipients and types. docs/verification/SLA_ASSIGNMENT_POLICY_V2_COMPATIBILITY_REPORT.md, docs/requirements/M11-enterprise-features.md
Audit Mutating account resources emit audit records with actor, IP, request UUID, auditable type/id, associated account, and changes; admin list pagination matches Chatwoot. docs/requirements/M11-enterprise-features.md
CustomRole Permission keys match Chatwoot; AccountUser permission resolution honors custom roles; deleting a role nullifies users; admin-only policy is enforced. docs/requirements/M1-accounts-and-users.md, docs/requirements/M11-enterprise-features.md
AgentCapacity and InboxLimit Assignment and auto-assignment respect per-inbox conversation limits; account/inbox limits are enforced in create paths and surfaced to frontend. docs/requirements/M1-accounts-and-users.md, docs/requirements/M5-team-and-assignment.md
Assignment policies V2 route and payload shape match Chatwoot; policy selection is applied during manual and automatic assignment; availability and capacity interact correctly. reference/chatwoot/config/routes.rb:306-313
CSAT Resolve event sends survey once; widget/WhatsApp/Twilio paths are modeled where supported; public submit/update honors lock window; metrics and download filters match Chatwoot. .hermes/plans/2026-05-24-automation-macro-csat.md, docs/requirements/M7-reporting-and-csat.md
Automation and macros Conditions/actions match Chatwoot; macro execute side effects are real; execution logs and async webhook/email transcript actions are durable and retryable. .hermes/plans/2026-05-24-automation-macro-csat.md, docs/requirements/M6-automation-and-templates.md
Captain/Copilot Assistant, tools, documents, responses, scenarios, inbox bindings, copilot threads/messages, tasks, streaming, and LLM/tool-call behavior are implemented or explicitly feature-gated. docs/requirements/M10-captain-and-copilot.md

Excluded tracking table:

Feature Decision Required handling
SSO Excluded Do not prioritize implementation. Existing surfaces should not block core frontend use.
SAML Excluded Do not expand beyond already present code unless needed to disable safely.
LDAP Excluded Omit from parity scope.
OIDC Excluded Omit from parity scope.

Phase 5: Background Jobs And Integrations

Status: planned.

Known hotspots:

  • internal/worker/worker.go is still mostly placeholder.
  • internal/automation/action_service.go has pending webhook/email transcript work.
  • internal/automation/csat_survey_listener.go now handles CSAT enablement, survey rules, generic send idempotency, and message-updated response building; durable queueing and external WhatsApp/Twilio template delivery remain follow-ups.
  • internal/auth/webhook_registry.go has pending signature verification for Facebook/WhatsApp.
  • internal/service/analytics_service.go has placeholder analytics paths.

Checklist:

  • Map Chatwoot jobs/listeners to Go worker responsibilities.
  • Implement durable job dispatch for automation, CSAT, notifications, webhooks, and search indexing.
  • Add retry and failure logging for external calls.
  • Add tests for job enqueueing and idempotency.

Acceptance:

  • User-visible side effects do not depend on synchronous handler-only execution.
  • Failed background work is observable and retryable.

Tracking table:

ID Task Current hotspot Status
P5.1 Choose and wire durable job runner compatible with current Go stack. internal/worker/worker.go, internal/channel/dispatcher.go Todo
P5.2 Move search indexing into retryable jobs where Chatwoot uses callbacks/jobs. search services and worker Todo
P5.3 Implement automation webhook delivery with retry, timeout, and logs. internal/automation/action_service.go Todo
P5.4 Implement email transcript delivery once email infrastructure is available. internal/automation/action_service.go Todo
P5.5 Move CSAT survey send and channel-specific external template delivery into durable jobs. internal/automation/csat_survey_listener.go, internal/csat/listener.go, channel send services Todo
P5.6 Complete webhook signature verification for Facebook/WhatsApp/Twilio/provider paths. internal/auth/webhook_registry.go, channel providers Todo
P5.7 Replace placeholder analytics with real report builders/queries. internal/service/analytics_service.go, reporting services Todo

Phase 6: Core Product Placeholder Burn-down

Status: doing.

Purpose: route parity is not enough; existing handlers that return placeholder JSON must be converted into real Chatwoot-compatible behavior before the frontend can be reused directly.

Tracking table:

ID Area Known references Required next work Status
P6.1 Account APIs docs/ROUTE_GAP_ANALYSIS.md, account handlers Replace placeholder responses with repository-backed behavior and serializer tests. Todo
P6.2 Contact APIs docs/ROUTE_GAP_ANALYSIS.md, contact handlers/services Finish merge, import/export/data-import, notes serializer depth, and Meilisearch-backed CRM search. Doing
P6.3 Conversation APIs docs/ROUTE_GAP_ANALYSIS.md, conversation handlers/services Implement frontend-critical filters, assignment, status, snooze, merge, bulk actions. Todo
P6.4 Message APIs docs/ROUTE_GAP_ANALYSIS.md, message handlers/services Implement create/list/delete, private notes, attachments, source attribution, events. Todo
P6.5 Inbox APIs docs/ROUTE_GAP_ANALYSIS.md, inbox handlers/services Implement CRUD, assignable agents, avatar, campaigns, channel settings, reset secret. Doing
P6.6 Widget/public APIs docs/ROUTE_GAP_ANALYSIS.md, widget/channel provider code, chatwootParityStub routes Widget/public frontend-critical route behavior is handler-backed, including public inbox flow, direct uploads/attachments, and public CSAT survey submission. Done
P6.7 Webhook ingress internal/router/router.go, internal/handler/webhook/*, channel providers Replace generic placeholder with provider-specific verified ingestion and dispatch. Done

Webhook ingress subtracking:

ID Provider/path Chatwoot reference Current Go gap Done when Status
P6.7a Twitter GET/POST /webhooks/twitter api/v1/webhooks#twitter_crc, #twitter_events Go route exposed only /webhooks/twitter/webhook. CRC and event routes exist at Chatwoot paths and use the existing Twitter handlers/tests. Done
P6.7b LINE POST /webhooks/line/:line_channel_id webhooks/line#process_payload Router param and handler lookup were mismatched; handler read an inbox-style param instead of line channel ID. Handler resolves ChannelLINE by channel_id, verifies X-Line-Signature, and persists/dispatches like Chatwoot. Done
P6.7c Telegram POST /webhooks/telegram/:bot_token webhooks/telegram#process_payload Handler lookup was a placeholder and did not resolve the real inbox by bot token. Handler resolves ChannelTelegram by bot_token, loads inbox, processes update, persists incoming messages, and returns provider-safe 200 OK. Done
P6.7d SMS/Twilio POST /webhooks/sms/:phone_number webhooks/sms#process_payload Go path was /webhooks/twilio/sms/:phone_number; handler read an inbox-style param. Chatwoot path is registered, phone number resolves ChannelTwilioSMS, message/status events persist and dispatch, and Twilio-safe XML ack behavior is covered. Done
P6.7e WhatsApp GET/POST /webhooks/whatsapp/:phone_number webhooks/whatsapp#verify, #process_payload Verify-token lookup scanned only account 0, and Cloud signature verification used access token as a placeholder secret. Verify challenge and POST event ingestion match Chatwoot path, token, response, and inbox resolution behavior. Done
P6.7f Instagram GET/POST /webhooks/instagram webhooks/instagram#verify, #events Chatwoot no-param route was registered but still returned parity stub responses. Verify/event routes exist at Chatwoot paths, validate Meta signatures, resolve account/inbox from payload/subscription data, and persist/dispatch messages. Done
P6.7g TikTok POST /webhooks/tiktok webhooks/tiktok#events Go route expected :business_id; Chatwoot route has no path param and should derive identity from payload. Handler accepts Chatwoot path, verifies Tiktok-Signature, resolves business/inbox from payload, and persists/dispatches provider events. Done
P6.7h Shopify POST /webhooks/shopify webhooks/shopify#events Chatwoot route existed but returned parity stub responses. Route has a real verified handler with Shopify HMAC validation, shop/redact cleanup, and supported topic forwarding. Done
P6.7i Generic fallback and auth middleware Go WebhookAuth, webhookStub Generic middleware reads :channel_type/:identifier, which breaks provider-specific routes; fallback returned placeholder success. Provider routes perform provider-specific verification; fallback no longer masks missing providers with success JSON. Done

P6.7 implementation notes:

  • Public webhook routes should not depend on generic WebhookAuth params that do not exist on Chatwoot provider paths.
  • Provider handlers may still return 200 OK on invalid external payloads when Chatwoot does so to avoid provider retries, but the reason must be covered by tests.
  • Route dump and route parity artifacts must be regenerated if public webhook paths are added to the tracked route set.
  • Mark P6.7 Done only after provider lookup, verification, dispatch boundary, route registration, and regression tests exist for the provider set above.

Widget/public subtracking:

ID Slice Reference behavior Status
P6.6a /api/v1/widget config, message send/list, contact, conversations, cable token reference/chatwoot/app/controllers/api/v1/widget/* and widget SDK clients Done
P6.6b /api/v1/widget campaigns, events, inbox members, labels Chatwoot widget campaigns/events/labels controllers and serializers Done
P6.6c /api/v1/widget message update, transcript, contact/set_user, Dyte participant Chatwoot widget message/contact/transcript/integration behavior Done
P6.6d /public/api/v1/inboxes contact/conversation/message core flow reference/chatwoot/app/controllers/public/api/v1/inboxes/* and matching jbuilder views Done
P6.6e Widget direct uploads and attachments Chatwoot active storage/direct upload and attachment payloads Done
P6.6f Public CSAT deep behavior Chatwoot CSAT survey controller/listener and message locking rules Done

Phase 7: Verification Harness

Status: planned.

Checklist:

  • Add repeatable command to compare Chatwoot and GoChat route dumps.
  • Add fixture-based JSON parity tests for major frontend endpoints.
  • Add Meilisearch mock tests and optional integration test mode gated by env vars.
  • Add frontend smoke path using reused Chatwoot frontend once backend boot flow is ready.
  • Keep a generated gap report in docs/parity/ with date, reference commit, Go commit, and pass/fail summary.

Required verification before each feature commit:

env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes

Verification milestone gates:

Gate Command or artifact Required before
V1 env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./... Every commit that changes Go code.
V2 env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt Every route/router/handler registration change.
V3 env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity Every tracked route or route generator change.
V4 JSON fixture parity tests for touched endpoint family. Every serializer or handler payload change.
V5 Optional live Meilisearch integration test gated by env var. Search behavior changes that claim Meilisearch compatibility.
V6 Reused Chatwoot frontend smoke flow. Before declaring frontend reuse viable.

Task Status Legend

  • Todo: not started or only skeleton exists.
  • Doing: implementation is underway in the current working tree.
  • Review: code is implemented and needs parity/test verification.
  • Done: committed, tested, and linked to the matching reference/chatwoot behavior.
  • Blocked: needs a decision, missing dependency, or reference behavior cannot yet be reproduced.

Ongoing Tracking Rules

  • Every parity task should name the matching file or behavior in reference/chatwoot.
  • Every route change should keep cmd/dump_routes passing.
  • Every phase must keep go test ./... green before moving on.
  • Test-only route fixes should be limited to malformed tests. Production route changes must preserve external Chatwoot-compatible URLs.
  • New search work must target Meilisearch first.
  • Update this document in the same commit as each completed parity slice: change task status, add verification command output summary, and link the touched reference/chatwoot behavior.
  • Do not count a feature as done because a model/handler exists; it is done only when route behavior, persistence, authorization, side effects, response shape, and tests are covered.
  • Keep .codegraph/ and other generated local analysis artifacts out of product commits unless explicitly requested.

Progress Log

  • 2026-06-04: Baseline stabilized and committed as 42cdab8 chore: stabilize chatwoot parity baseline; go test ./... passed and route dump reported TOTAL: 704.
  • 2026-06-04: Phase 1 search foundation added: Meilisearch config/env defaults, SearchEngine contract, Meilisearch HTTP wrapper with bootstrap/settings, DB fallback adapter, document builders, reindex command, and no-live-Meilisearch tests. Verified go test ./... in unsandboxed mode because miniredis/httptest need local sockets; route dump still reports TOTAL: 704.
  • 2026-06-04: Phase 1 indexing hooks wired for conversations, messages, contacts, companies, and articles. Create/update/delete paths now call the service-layer SearchIndexer boundary, bootstrap injects the Meilisearch-backed search service, and unit tests cover each entity hook path.
  • 2026-06-04: Phase 2 route parity tracking added. Ruby/Bundler are unavailable in this workspace, so cmd/route_parity records static Chatwoot route DSL declarations from reference/chatwoot/config/routes.rb, consumes cmd/dump_routes output, and writes docs/parity/route_parity.md. First tracked critical route summary: 76 exact, 2 method-compatible, 0 parameter-compatible, 3 missing out of 81; GoChat route dump remains TOTAL: 704.
  • 2026-06-04: First tracked Phase 2 route gaps closed. Added Chatwoot-compatible contact conversations, contact export POST, conversation toggle_priority, and PUT aliases for conversation/message updates; fixed contact handlers to accept :account_id as well as legacy :id. Regenerated parity report: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 81; route dump now reports TOTAL: 709.
  • 2026-06-04: Expanded Phase 2 route parity from 81 to 138 tracked frontend-critical account routes. Added account-scoped notification routes, notification_settings PUT alias, and Chatwoot account-level inbox_members create/show/update/destroy handlers. Regenerated parity report: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138; route dump now reports TOTAL: 723.
  • 2026-06-04: Expanded Phase 2 route parity from 138 to 251 tracked frontend-critical routes. Added tracking and route-level coverage for Captain/Copilot, assignment policies, /api/v1/widget, public inbox/contact/conversation/message APIs, public CSAT survey, and /api/v2 reports. Regenerated parity report: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 251; route dump now reports TOTAL: 791. Added router boot regression coverage for Captain static/dynamic routes, widget collection routes, public nested message routes, and v2 reports.
  • 2026-06-04: Started Phase 3/6 widget behavior parity for the reused Chatwoot frontend. /api/v1/widget/config now returns Chatwoot-style website_channel_config, contact pubsub token, and global config; /api/v1/widget/messages accepts X-Auth-Token and nested message.content, returns Chatwoot message shape, and exposes latest messages as {payload, meta}; /api/v1/widget/contact and core conversation actions now route to real handlers instead of parity stubs. Legacy /widget/* response compatibility is preserved. Focused widget/service/router tests pass.
  • 2026-06-04: Continued Phase 3/6 widget behavior parity. Replaced more /api/v1/widget stubs with handlers for campaigns, events, inbox_members, labels, and label removal. Inbox member payload now follows Chatwoot {payload: [...]} shape; campaigns return enabled inbox campaigns with trigger rules; events validate website/contact token context and return 204; labels mutate the latest widget conversation only when the label exists in the account. Added focused widget handler coverage for available agents, events, and label add/remove. Remaining widget stubs: message update, transcript, contact/set_user, and Dyte participant integration; public inbox/contact/conversation/message routes are still placeholder-backed.
  • 2026-06-04: Completed the remaining /api/v1/widget stub burn-down. Message update now persists submitted email/form values and identifies the contact; contact/set_user validates identifier HMAC, supports verified contact identification, and returns widget_auth_token when the contact context changes; conversation transcript returns Chatwoot-compatible status behavior around missing conversations; Dyte participant endpoint validates integration messages and returns a meeting token payload. Added model/repository support for contact inbox HMAC verification and identifier lookup. Public inbox/contact/conversation/message routes remain the next P6.6 placeholder group.
  • 2026-06-04: Replaced /public/api/v1/inboxes contact/conversation/message placeholders with real Chatwoot public API handlers. API inboxes now resolve through Channel::Api identifiers, public contacts create/update by source_id with optional identifier HMAC verification, public conversations enforce the same verified-contact visibility split, and public messages support create/list/update submitted values. Added focused public API handler flow coverage. Verified go test ./..., regenerated docs/parity/gochat_routes.txt (TOTAL: 791), and regenerated docs/parity/route_parity.md (251 exact, 0 missing). Remaining P6.6 work is direct uploads/attachments and deeper public CSAT behavior.
  • 2026-06-04: Completed P6.6e widget direct upload/attachment parity for the reused Chatwoot widget frontend. /api/v1/widget/direct_uploads now accepts ActiveStorage metadata with website_token + X-Auth-Token, returns the raw signed_id/direct_upload blob shape expected by DirectUpload, supports the follow-up PUT body upload, and attaches message[attachments][] signed IDs to incoming widget messages. Message create/list payloads now include Chatwoot-style attachment fields (data_url, thumb_url, file_type, extension, size). Added focused handler coverage for ActiveStorage create/PUT and multipart attachment-only message send/list. Verified focused package tests and regenerated route artifacts; route dump now reports TOTAL: 793, while tracked parity remains 251 exact, 0 missing. Remaining P6.6 work is deeper public CSAT behavior.
  • 2026-06-04: Completed P6.6f public CSAT deep behavior. /public/api/v1/csat_survey/:id now resolves the conversation UUID to the input_csat message and returns the Chatwoot public survey payload (csat_survey_response, display type, inbox avatar/name, locale, conversation/message IDs). Public CSAT submit now accepts nested message.submitted_values, updates the survey message content attributes, upserts a message-linked CSAT response, and enforces Chatwoot's 14-day lock with 422. Public inbox message update now applies the same lock/response-builder path for input_csat messages. Added handler coverage for public CSAT show/update/lock and public inbox CSAT message update/lock. Focused package tests passed.
  • 2026-06-04: Consolidated the Hermes-era planning into this master tracker. Added the locked decision ledger, end-to-end milestone map, ordered slice backlog, enterprise work package breakdown, and detailed P6.7 provider webhook ingress checklist. Current next implementation slice is B1/P6.7 webhook ingress.
  • 2026-06-04: Started P6.7 webhook ingress parity. Added Chatwoot public webhook paths for Twitter, Telegram, LINE, SMS/Twilio, WhatsApp, Instagram, TikTok, and Shopify; removed the generic success fallback so unsupported providers no longer return placeholder success. Telegram, LINE, Twilio SMS, and TikTok handlers now resolve inboxes through provider channel records instead of inbox-id placeholders; TikTok model column naming now matches existing repository queries. Added provider lookup tests and router boot coverage. Regenerated route dump: TOTAL: 801; tracked route parity remains 251 exact, 0 missing.
  • 2026-06-04: Continued P6.7 webhook ingress parity. Instagram /webhooks/instagram now performs Chatwoot-style global verify-token challenge handling, verifies Meta signatures against env/channel app secrets, resolves Instagram inboxes from webhook sender/recipient IDs, and dispatches parsed DM/comment events through the existing Meta pipeline boundary. Shopify /webhooks/shopify now verifies X-Shopify-Hmac-SHA256 with SHOPIFY_CLIENT_SECRET, handles shop/redact by deleting matching Shopify integration hooks, and forwards supported topics to the existing Shopify event processor. WhatsApp verification now queries channels by verify token directly, and Cloud API signature verification uses app secrets from provider config/env instead of access tokens. Added focused webhook tests; route dump remains TOTAL: 801 and tracked route parity remains 251 exact, 0 missing.
  • 2026-06-04: Added the P6.7 incoming persistence boundary. Provider webhook handlers now persist normalized incoming messages into contacts, contact_inboxes, open conversations, and incoming messages instead of only parsing/logging them. Telegram, LINE, Twilio SMS, WhatsApp, Facebook/Instagram, and TikTok are wired through the shared persister; duplicates are skipped by inbox_id + source_id. Added direct persister coverage and Telegram webhook persistence coverage. Focused webhook/channel tests and full go test ./... passed.
  • 2026-06-04: Continued P6.7 dispatch parity by persisting provider receipt/status events. Existing messages are updated from Twilio delivery callbacks, WhatsApp sent/delivered/read/failed statuses, Facebook/Instagram delivery/read receipts, and TikTok read receipts. Added tests for direct status update and Twilio delivery callback update. Focused webhook/channel tests and full go test ./... passed.
  • 2026-06-05: Wired P6.7 incoming persistence into the existing dispatcher fan-out boundary. Newly persisted webhook contacts, conversations, messages, and message status updates now emit ChannelEvents through DispatchAsync's current sync fallback, keeping automation, bot rule, CSAT, and notification listeners reachable from provider webhooks. Added listener-based regression coverage for incoming and status events. Focused webhook tests and full go test ./... passed.
  • 2026-06-05: Broadened P6.7 provider webhook persistence fixtures. LINE, Twilio SMS, WhatsApp, Instagram, and TikTok webhook tests now assert persisted incoming messages by provider source ID, matching the existing Telegram persistence fixture and reducing the remaining provider-review surface to signature edge cases and final unsupported-provider classification. Focused webhook tests and full go test ./... passed.
  • 2026-06-05: Added TikTok webhook signature parity. /webhooks/tiktok now rejects missing, invalid, or stale Tiktok-Signature values using the same timestamp-plus-body HMAC shape as the Chatwoot reference controller, while valid signed payloads still resolve the inbox from biz_id and persist incoming messages. Focused webhook tests and full go test ./... passed.
  • 2026-06-05: Tightened LINE webhook signature parity. /webhooks/line/:line_channel_id now rejects missing signatures when a channel_secret is configured and only persists signed payloads whose X-Line-Signature matches the raw request body. Focused webhook tests and full go test ./... passed.
  • 2026-06-05: Completed WhatsApp route-level webhook verification coverage. /webhooks/whatsapp/:phone_number now has tests for verify-token challenge echo, signed Cloud API POST persistence, and missing X-Hub-Signature-256 rejection without message persistence. Focused webhook tests and full go test ./... passed.
  • 2026-06-05: Finalized P6.7 provider classification. Twitter CRC now returns sha256=<base64-hmac> like Chatwoot, Twitter route-level CRC/event tests exist, Instagram rejects unsigned signed-required event payloads without persistence, and Telegram, SMS/Twilio, Instagram, Shopify, and the already completed providers are marked Done in the webhook ingress tracker. Focused webhook/API tests and full go test ./... passed.
  • 2026-06-05: Started B3 dashboard conversation/message serializer parity. Conversation list/filter/show/create/update and message list/create/update/retry now return Chatwoot frontend payload shapes instead of the Go response envelope; message creation accepts frontend composer JSON/multipart fields and preserves echo_id/content_attributes; account-scoped conversation display_id routing is now generated and resolved. Verified focused conversation/message handler tests, full go test ./..., and git diff --check.
  • 2026-06-05: B4 contacts/companies first checkpoint committed as 3481597 feat(crm): align contact company payloads; focused CRM tests, handler/service/repository tests, escalated full go test ./..., route dump TOTAL: 809, route parity 251 exact, 0 missing, and git diff --check passed.
  • 2026-06-05: B4 contacts/companies second checkpoint committed as 7a033e2 feat(crm): complete contact label avatar gaps; contact labels now persist through contact_labels, label list/update returns Chatwoot { payload }, CRM contact list/search/filter labels use contact labels, contact/company avatar delete and company destroy-custom-attributes match Chatwoot response envelopes, route dump is TOTAL: 817, route parity is 261 exact, 0 missing, escalated full go test ./... passed, and git diff --check passed.
  • 2026-06-05: B4 contact merge checkpoint committed as 1e3bf47 feat(crm): align contact merge action; /api/v1/accounts/:account_id/actions/contact_merge now matches Chatwoot route usage, merge returns the raw contact model serializer, moves mergee conversations/contact inboxes/notes/messages to the base contact, preserves base attributes while filling blanks from mergee, route dump is TOTAL: 818, route parity is 262 exact, 0 missing, full go test ./... passed, and git diff --check passed.
  • 2026-06-05: B4 contact import/data-import checkpoint committed as 8eed2f1 feat(crm): persist contact data imports; contact imports now persist Chatwoot-shaped data_imports, missing import_file returns 422, successful imports return empty 200 OK, lifecycle counts/errors are recorded, and CSV rows merge/update existing contacts by identifier/email/phone while applying labels and custom attributes. Focused import tests, handler/service/repository/router package tests, full go test ./..., and git diff --check passed.
  • 2026-06-05: B4 contact export artifact checkpoint committed as 47c2b80 feat(crm): persist contact export artifacts; POST /contacts/export now persists contact_exports, generates Chatwoot default/requested CSV columns with UTF-8 BOM, supports label/filter export inputs, stores a download URL and row count, and creates a user notification with file metadata. Focused export tests, handler/service/repository/router package tests, regenerated route dump TOTAL: 819, route parity 262 exact, 0 missing, full go test ./..., and git diff --check passed.
  • 2026-06-05: B4 contact/company notes checkpoint committed as af57482 feat(crm): align contact note payloads; contact note index/create/show/update now returns Chatwoot raw note JSON with nested agent payloads, accepts flat and nested note params, destroy returns empty 200 OK and hard-deletes notes, company notes include nested user metadata, and the Rails-compatible PUT note update route is registered. Focused notes/API tests, full go test ./..., regenerated route dump TOTAL: 820, route parity 262 exact, 0 missing, and git diff --check passed.
  • 2026-06-05: B4 nested contact/company conversations checkpoint committed as fb775b7 feat(crm): align nested conversation payloads; contact and company nested conversation endpoints now return Chatwoot { payload: [...] } conversation partials with nested sender metadata, latest message payloads, last_non_activity_message, labels, timestamps, priority, account/inbox fields, and company latest-20 ordering by last_activity_at DESC. Focused nested conversation tests, focused handler/service/repository/router tests, escalated full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 820.
  • 2026-06-05: B4 CRM Meilisearch checkpoint committed as 202da19 feat(crm): route crm search through meilisearch; contact and company CRM search now routes non-empty queries through the configured search service, resolves Meilisearch result IDs back through account-scoped repositories to preserve Chatwoot CRM list payloads, wires the search reader in app bootstrap, and adds company document fallback coverage to global DB search. Focused search/service/repository/API tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 820.
  • 2026-06-05: B4 contact import label validation checkpoint committed as 653db81 feat(crm): validate imported contact labels; CSV import now matches Chatwoot's approved-label guard by rejecting rows whose labels column contains labels absent from the account, preserving valid rows, recording failed row counts on data_imports, and preventing arbitrary tag creation from CSV input. Focused import/service/API tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 820.
  • 2026-06-05: B4 contact export email checkpoint committed as c2766fe feat(crm): email contact export completions; completed contact exports now invoke a Chatwoot-style contact_export_complete mailer boundary for the requesting user's email, using the export download URL and subject Your contact's export file is available to download., while retaining the persisted notification. Focused export/service/API tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 820.
  • 2026-06-05: B4 frontend CRM API smoke checkpoint committed as ad29dc3 test(crm): cover chatwoot frontend crm smoke; the handler-level flow mirrors the reused Chatwoot dashboard contacts/companies clients across CRUD, search, labels, contactable inboxes, notes, nested conversations, and company relation flows. Focused smoke tests, handler package tests, full go test ./..., and git diff --check passed.
  • 2026-06-05: B4 documentation checkpoint committed as c256e27 docs: record crm frontend smoke checkpoint; B4 moved to Review with browser validation deferred to B12/M7.
  • 2026-06-05: B5 inbox serializer checkpoint committed as 0e83e8d feat(inboxes): align chatwoot inbox serializers; inbox list now returns { payload: [...] }, show/create/update/reset-secret return raw Chatwoot-style inbox payloads, channel slugs serialize as Channel::*, avatar delete returns empty 200 OK, async delete message matches Chatwoot, and PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id was added. Focused inbox tests, handler package tests, route dump TOTAL: 821, route parity, full go test ./..., and git diff --check passed.
  • 2026-06-05: B5 documentation checkpoint committed as 1f6ec3e docs: record inbox serializer checkpoint; B5.1 is Done and B5.2 multipart/JSON channel[...] create/update binding is the next active implementation slice.
  • 2026-06-05: Planning tracker expanded for handoff continuity. B5.2 now has executable substeps, B6-B12 have explicit first checkpoints and Done rules, and every future checkpoint has a required documentation update rule.
  • 2026-06-05: B5.2 inbox request binding checkpoint committed as ee93546 feat(inboxes): bind chatwoot channel settings; generic inbox create/update now accepts Chatwoot frontend JSON and multipart/urlencoded FormData, persists top-level inbox settings and nested channel/CSAT config, generates web widget/API secrets, and supports Telegram creation without an explicit name. Focused inbox handler tests, service inbox tests, handler package tests, escalated full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 821.
  • 2026-06-05: B5.3 working-hours checkpoint committed as 945782e feat(inboxes): persist chatwoot working hours; generic inbox create/update now initializes default working hours, accepts the reused frontend's working_hours payload with string and empty time fields, persists the weekly schedule relation, serializes working hours from the relation, and covers out-of-office detection. Focused inbox/working-hour tests, handler package tests, escalated full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 821.
  • 2026-06-05: B5.4 inbox members checkpoint committed as 82167f5 feat(inboxes): align chatwoot inbox members; account-scoped and nested inbox member routes now return Chatwoot { payload: [agent] } responses, serialize assigned users with agent partial fields including custom_role_id, make create add-only/idempotent, make update diff-based, and keep delete as empty 200 OK. Focused inbox member handler/service/repository tests, handler package tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 821.
  • 2026-06-05: B5.5 channel config depth checkpoint committed as f0aae79 feat(inboxes): deepen channel config parity; generic inbox JSON updates now hoist flat SMTP/IMAP/channel settings into channel config, Email/WhatsApp/LINE/SMS/Twilio serializers expose deeper Chatwoot frontend fields, WebWidget/API/Email/WhatsApp defaults are closer to Chatwoot, and Twilio channel create accepts nested twilio_channel payloads while returning the raw inbox serializer. Focused channel-depth tests, service inbox tests, handler package tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 821.
  • 2026-06-05: B5.5c planning contract added for AgentCapacityPolicy/InboxCapacityLimit parity; the next slice now tracks exact Chatwoot route sources, raw policy and inbox-limit serializers, optional assignment_logic, nested users and inbox_limits routes, duplicate/wrong-account/non-negative validation gates, and route artifact regeneration rules before implementation starts.
  • 2026-06-05: B5.5c AgentCapacityPolicy/InboxCapacityLimit checkpoint committed as b197e54 feat(capacity): align chatwoot inbox capacity limits; policy CRUD now returns raw Chatwoot serializers, nested policy users and inbox limits are implemented, account_users.agent_capacity_policy_id is persisted, duplicate/wrong-account/non-negative limit checks are covered, route dump is TOTAL: 829, tracked route parity is 267 exact, 7 parameter-compatible, 0 missing, focused service/handler/router tests passed, sandboxed full tests failed only on socket restrictions, escalated full go test ./... passed, and git diff --check passed.
  • 2026-06-05: B5.5 final channel route-response cleanup committed as f04a03b feat(channels): align channel route inbox payloads; Email, Twilio SMS, and LINE dedicated channel routes now return raw Chatwoot-compatible inbox payloads for create/get/update, { payload: [...] } for list, and empty 200 OK for delete. InboxService.BindChannel keeps dedicated channel IDs/configs reflected in inbox serialization. Focused Email/Twilio/LINE channel tests, service inbox tests, handler package tests, full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 829; next active slice is B6 Meilisearch live-shape review.
  • 2026-06-05: B6 search payload checkpoint committed as a16c23c feat(search): align chatwoot search payloads; global/entity search endpoints now return Chatwoot payload envelopes, default to 15 results, accept since/until/from frontend params, serialize model and Meilisearch-hit data into frontend-ready conversation/contact/message/article shapes, and filter message sender IDs through both Meilisearch and DB fallback. Focused handler/search/repository tests, handler/search/repository package tests, escalated full go test ./..., and git diff --check passed. Route dump unchanged at TOTAL: 829; B6.4 live Meilisearch gate and B6.5 fallback hardening remain active.
  • 2026-06-05: B6 live-gate/fallback checkpoint committed as f08c743 test(search): add meilisearch live gate; added an env-gated live Meilisearch test for isolated bootstrap/index/search of contacts and messages, kept it skipped unless GOCHAT_LIVE_MEILI_HOST is set, rejected search.engine=db in release validation, and made cmd/reindex_search fail fast if configured with DB fallback. Focused config/reindex/search tests, full go test ./..., and git diff --check passed. B6 moves to Review; next implementation slice is B7 SLA/assignment capacity unless the optional live gate is explicitly run first.
  • 2026-06-05: B7 capacity enforcement checkpoint committed as a98dc2c feat(capacity): enforce inbox assignment limits; manual agent assignment, team assignment with explicit agent, team overflow fallback, and auto-assignment now apply AgentCapacityPolicy/InboxCapacityLimit per-inbox open-conversation limits. Resolved conversations do not count against capacity. Focused assignment/capacity tests, handler smoke for conversation/capacity routes, escalated full go test ./..., and git diff --check passed. Next B7 checkpoint is SLA policy CRUD payload review and applied-SLA persistence.
  • 2026-06-05: B7 SLA policy payload checkpoint committed as 95224fa feat(sla): align sla policy payloads; SLA policy create/show/update now return { payload: sla_policy }, index returns { payload: [...] }, destroy returns empty 200 OK, and the serializer is limited to Chatwoot's enterprise SLA policy fields. Focused SLA policy handler tests, SLA service tests, handler package tests, full go test ./..., and git diff --check passed. Next B7 checkpoint is applied-SLA persistence for new/open conversations.
  • 2026-06-05: B7 applied-SLA persistence checkpoint committed as a11bb96 feat(sla): persist applied sla on conversations; conversation create/update now accepts sla_policy_id, validates SLA policy account ownership plus Chatwoot replacement/removal rules, creates exactly one active applied_slas record, keeps status-only updates idempotent for existing SLA conversations, and includes Chatwoot-style applied_sla payload fields in conversation responses. Focused conversation/SLA service and handler tests, full go test ./..., and git diff --check passed. Next B7 checkpoint is SLA timer/breach lifecycle with idempotent FRT/NRT/RT events.
  • 2026-06-05: B7 SLA breach lifecycle checkpoint committed as 213bf2b feat(sla): make sla breach events idempotent; SLA thresholds now use Chatwoot seconds units, exact-threshold checks count as breached, FRT/NRT/RT miss events are idempotent by applied SLA, event type, and meta, NRT events store last incoming message_id, and resolved applied SLAs transition to hit or missed. Focused applied-SLA lifecycle tests, handler SLA/conversation tests, full go test ./..., and git diff --check passed. Next B7 checkpoint is applied-SLA report/list payload parity.
  • 2026-06-05: B7 applied-SLA reports checkpoint committed as 4f85ef1 feat(sla): align applied sla reports; account applied-SLA index/metrics/download now match Chatwoot's reports controller and frontend SLA reports API, including missed-only table/download semantics, shared filters, { payload, meta } index shape, raw metrics keys, and breached-conversation CSV. Focused applied-SLA service/handler tests, service and handler package tests, full go test ./..., route dump TOTAL: 830, route parity 270 exact, 7 parameter-compatible, 0 missing, go test ./cmd/route_parity -count=1, and git diff --check passed. Next B7 checkpoint is business-hours timer math and SLA notification delivery review.
  • 2026-06-05: Development tracker was normalized for handoff continuity after 330f875 docs: record applied sla reports checkpoint; the current baseline now points at the latest implementation and documentation checkpoints, the planning control board records the next ordered B7-B12 checkpoints, Hermes search/automation/CSAT notes are mapped into active slices, and B7.6 has an explicit investigation/landing contract.
  • 2026-06-05: B7 SLA notification checkpoint committed as d23f3f0 feat(sla): notify users on sla misses; new FRT/NRT/RT SLA miss events now create Chatwoot notification types for conversation participants, account administrators, and conversation assignee, with de-duplication and account membership filtering. Repeated evaluation stays idempotent. Current reference/chatwoot timing was verified to keep only_during_business_hours as a stored policy flag without changing elapsed-second thresholds, and a regression test records that behavior. Focused applied-SLA tests, service package tests, full go test ./..., and git diff --check passed. B7 moves to Review; next slice is B8 CSAT account/public/report/send parity.
  • 2026-06-05: B8 account-side CSAT checkpoint committed as f441680 feat(csat): align account report payloads; CSAT report list now returns the raw Chatwoot response array with nested contact, assigned agent, reviewer, display conversation ID, Unix timestamps, and message ID. Filters now cover frontend since/until, user_ids, inbox_id, team_id, and rating; metrics returns total_count, ratings_count, and total_sent_messages_count; update/review-note actions return the Chatwoot CSAT serializer. Focused CSAT handler/service tests, handler and automation package tests, full go test ./..., and git diff --check passed. Next B8 checkpoint is public CSAT submit/update and resolve-triggered send parity.
  • 2026-06-05: B8 public/send CSAT checkpoint committed as ef3a909 feat(csat): send surveys on resolved conversations; public CSAT update now accepts object-shaped message.submitted_values, repeated submissions update the same message-linked response, and the 14-day lock still follows the survey message creation date. The resolve listener now creates one input_csat template message only when the resolved conversation's inbox has CSAT enabled and survey rules allow it, skips duplicates, and no longer creates pending rating-0 responses. Message-updated events build responses from submitted CSAT values. Focused CSAT listener/service/handler/widget tests, automation and API handler package tests, full go test ./..., and git diff --check passed. Next B8 checkpoint is CSAT download CSV parity.