1605 lines
290 KiB
Markdown
1605 lines
290 KiB
Markdown
# 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: `feat(captain): gate document sync backend`.
|
|
- Latest documentation checkpoint: this checkpoint, recorded with the B11.3d/B11.3e/B12 executable landing plan.
|
|
- Worktree status at this documentation checkpoint: B11.1a aligns Captain assistant CRUD/tools/inbox bindings; B11.1b aligns Captain scenarios and custom tools; B11.1c aligns Captain documents, assistant responses, bulk actions, and custom-tool test payloads; B11.2 aligns Copilot thread/message create/list/get/delete payloads, account/user scoping, and no-LLM fallback persistence; B11.3a aligns Captain preferences show/update payloads and account-level model/feature storage; B11.3b aligns Captain playground request/response payloads, account scoping, v2 history handling, and no-LLM fallback; B11.3c adds the fakeable Captain document sync backend gate with disabled, failed, and fake-success states. Next active implementation slice is B11.3d Copilot tasks/tool-call depth, followed by B11.3e streaming/realtime compatibility and B12 reused frontend smoke.
|
|
- `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 | B11.3d | Copilot tasks, suggestions, and tool-call records persist enough frontend state for reloads. | Copilot task/tool-call handler/service fixtures and serializer tests. |
|
|
| 2 | B11.3e | Streaming/realtime paths have frontend-compatible non-streaming or disabled fallbacks until true streaming is wired. | Deterministic response tests and event/push payload tests. |
|
|
| 3 | 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, B8, B9, and B10 are in Review; B11 Captain resources, preferences, playground, document sync gates, and Copilot thread/message payloads are landed, while Copilot tasks/tool-call depth and streaming remain 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`. | Continue B5 with create/update request binding for Chatwoot multipart `channel[...]` payloads, working-hours/settings persistence, inbox members, and channel-specific config depth. |
|
|
| `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. |
|
|
| `5c771d8 docs: record csat public send checkpoint` | Recorded the B8.2/B8.3 public CSAT and resolve-send checkpoint, made B8.4 the next active task, and kept external WhatsApp/Twilio template delivery as a named Phase 5 integration follow-up. | Documentation-only checkpoint after `ef3a909`; `git diff --check` passed before commit. | Continue B8.4 CSAT download CSV parity. |
|
|
| `b36cf07 feat(csat): align report download csv` | Completed B8.4 CSAT CSV download parity for the reused reports screen: `/csat_survey_responses/download` now writes real CSV via `encoding/csv`, uses Chatwoot's report headers, includes enterprise review notes, emits absolute conversation links using conversation display IDs, appends the reporting-period row, and applies the same account/date/agent/inbox/team/rating filters as list/metrics. | `go test ./internal/handler/api/v1 -run CsatSurvey -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`. | B8 moves to Review. Continue B9 automation and macro side-effect parity. |
|
|
| `20941b9 docs: record csat download checkpoint` | Recorded the B8.4 CSAT download checkpoint and moved the active queue to B9 automation/macro side-effect parity. | Documentation-only checkpoint; no route changes. | Continue B9.1a automation-rule CRUD request/response and validation parity. |
|
|
| `docs: land automation macro execution plan` | Landed the B9 execution contract into this tracker before code changes: Chatwoot automation-rule controller/frontend contracts, current Go handler/service gaps, B9.1a implementation checklist, focused verification commands, and commit/documentation rules. | Documentation-only checkpoint; `git diff --check` passed before commit. | Start B9.1a implementation. |
|
|
| `feat(automation): align automation rule payloads` | Completed B9.1a automation-rule CRUD contract parity for the reused Chatwoot frontend: list/show/update/clone now return `{ payload }`, create returns the raw rule object, delete returns empty `200 OK`, conditions accept `attribute_key` and Chatwoot operators such as `equal_to`, action params accept frontend arrays, responses serialize `attribute_key`, array `action_params`, Unix `created_on`, and update/delete/clone are account-scoped. | `go test ./internal/handler/api/v1 -run AutomationRule -count=1`; `go test ./internal/automation -run 'AutomationRule\|Condition\|Action' -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 B9.1b trigger coverage, B9.1c execution logs/stop-on-match, and B9.2 macro payload/execute parity. |
|
|
| `docs: detail automation trigger plan` | Fully landed the next B9 tracking contract: current baseline now points at `3403770`, the control board is ordered from B9.1b through B12, B9 status is `Doing`, and B9.1b explicitly follows the current Chatwoot listener events while excluding non-reference `contact_updated` automation triggers. | Documentation-only checkpoint; `git diff --check` passed before commit. | Start B9.1b implementation from the recorded listener/event contract. |
|
|
| `feat(automation): align rule trigger coverage` | Completed B9.1b listener/event coverage: automation rules now honor Chatwoot skip rules for automation-origin events, auto-reply conversations, activity messages, and auto-reply emails; listener extraction works from event conversation ID, `conversation_id`, conversation payloads, and message payloads; conversation update/status/priority paths dispatch `changed_attributes`; initial conversation messages and provider webhook messages carry the message/conversation data needed by automation. | `go test ./internal/automation -run 'AutomationRuleListener\|MatchAndExecute\|Condition' -count=1`; `go test ./internal/service -run 'Conversation.*Automation\|Message.*Automation\|ConversationService' -count=1`; `go test ./internal/handler/webhook -run 'Incoming\|Webhook' -count=1`; `go test ./internal/automation -count=1`; `go test ./internal/service -count=1`; `go test ./internal/handler/webhook -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B9.1c execution logs and stop-on-match behavior, then B9.1d retryable webhook/email transcript actions and B9.2 macro parity. |
|
|
| `feat(automation): record rule execution outcomes` | Completed B9.1c observability/parity guard: rule evaluations now record skipped condition outcomes, condition failures, event names, aggregate action counts, and per-action success/failure results. Matching rules continue executing in ID order without stop-on-match, matching the current Chatwoot listener loop, and failed actions do not prevent later actions from running. | `go test ./internal/automation -run 'AutomationRuleService_MatchAndExecute\|ExecutionLogService' -count=1`; `go test ./internal/automation -count=1`; `go test ./internal/handler/webhook -run 'Incoming\|Webhook' -count=1`; `go test ./internal/service -run 'Conversation.*Automation\|Message.*Automation\|ConversationService' -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B9.1d retryable webhook/email transcript actions, then B9.2 macro payload/execute parity. |
|
|
| `docs: land automation delivery plan` | Recorded the complete B9.1d retryable external-action landing contract: webhook payload/delivery, transcript mailer boundary, retry/timeout behavior, action-result observability fields, B9.3 durable-worker deferral, and exit commands. It also refreshed stale B9 tracker entries that still pointed at B9.1b. | Documentation-only checkpoint; `git diff --check` before commit. | Start B9.1d implementation, then continue B9.2 macro CRUD/execute parity. |
|
|
| `feat(automation): deliver retryable external actions` | Completed B9.1d external action delivery: automation webhook actions now build Chatwoot-style `automation_event.<event>` payloads and deliver through a timeout-bound 3-attempt HTTP boundary; email transcript actions now split comma recipients, build transcript subject/body, deliver through a fakeable SMTP/mailer boundary, and both action types write delivery metadata into `action_results`. Chatwoot array params now round-trip `send_webhook_event` URLs. | `go test ./internal/automation -run 'ActionService\|AutomationRuleService_MatchAndExecute\|ExecutionLogService' -count=1`; `go test ./internal/automation -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B9.2 macro CRUD and macro execute side-effect parity; durable queued worker scheduling remains B9.3/Phase 5. |
|
|
| `feat(macros): align chatwoot macro payloads` | Completed B9.2 macro CRUD/execute parity for the reused frontend: macro list/show/create/update now return Chatwoot `{ payload }` serializers with string visibility and nested created/updated agents, create/update accept Chatwoot array action params, public macro permissions follow `MacroPolicy`, delete/execute return empty `200 OK`, and execute resolves `conversation_ids` by account-scoped display ID before mutating conversations. | `go test ./internal/handler/api/v1 -run Macro -count=1`; `go test ./internal/automation -run Macro -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 B10 audit/custom roles/remaining limits; keep B9.3 durable delayed worker scheduling as Phase 5 follow-up. |
|
|
| `feat(audit): align chatwoot audit log payloads` | Completed B10.1 audit log list parity for the reused enterprise settings screen: `/audit_logs` now returns Chatwoot top-level `per_page`, `total_entries`, `current_page`, and `audit_logs`; pagination is fixed at 25 per page; list/get require administrator/super_admin role; account scoping matches associated audits as well as local `account_id`; and serializer fields match the enterprise Jbuilder shape with Unix `created_at`. | `go test ./internal/handler/api/v1 -run Audit -count=1`; `go test ./internal/repository -run Audit -count=1`; `go test ./internal/service -run Audit -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B10.2 audit writer coverage, then B10.3 CustomRole permission-key/account-user parity. |
|
|
| `feat(audit): record enterprise mutations` | Completed B10.2a audit writer boundary and first enterprise mutation coverage: `AuditService.Record` now creates account-associated audit rows with actor, request UUID, remote address, action, auditable type/id, and JSON changes; automation-rule create/update/delete/clone/toggle, macro create/update/delete, custom-role create/update/delete, and CSAT review-note update call the shared writer. | `go test ./internal/handler/api/v1 -run 'CustomRole\|AutomationRule\|Macro\|CsatSurvey' -count=1`; `go test ./internal/service -run Audit -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B10.2b inbox/conversation/SLA/capacity audit writer coverage, then B10.3 CustomRole permission parity. |
|
|
| `feat(audit): cover operational mutations` | Completed B10.2b audit writer coverage for the remaining named operational mutations: inbox create/update, conversation update/delete/assign/status, SLA policy create/update/delete, AgentCapacityPolicy create/update/delete, inbox capacity limit create/update/delete, and capacity-policy user assignment/removal now call the shared audit writer. | `go test ./internal/handler/api/v1 -run 'SlaPolicy\|AgentCapacity\|Inbox\|Conversation' -count=1`; `go test ./internal/service -run Audit -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 830`. | Continue B10.3 CustomRole permission-key/account-user parity and B10.4 remaining InboxLimit create-path enforcement. |
|
|
| `docs: land remaining parity tracker` | Converted the immediate remaining plan into executable tracking contracts: B10.3 now lists CustomRole reference files, permission-array migration, AccountUser role resolution, delete nullification, admin gates, and test exits; B10.4 records InboxLimit create-path enforcement; B11/B12 now have route/persistence/feature-gate and smoke-report landing rules. | Documentation-only checkpoint; `git diff --check` passed. | Start B10.3 implementation from the recorded CustomRole contract. |
|
|
| `feat(custom-roles): align chatwoot permissions` | Completed B10.3 CustomRole parity: custom roles now accept and serialize Chatwoot permission arrays, raw list/show/create/update/delete payloads match the enterprise Jbuilder views, admin gates are enforced, `AccountUser.role` stays `agent` while `custom_role_id` drives custom permissions, profile account payloads include `custom_role_id/custom_role/permissions`, and delete clears related account users. | `go test ./internal/handler/api/v1 -run CustomRole -count=1`; `go test ./internal/service -run 'CustomRole\|RBAC' -count=1`; `go test ./internal/model -run 'CustomRole\|AccountUser' -count=1`; `go test ./internal/middleware -run AccountScope -count=1`; handler/service/model packages passed; full verification recorded below. No route changes; route dump remains `TOTAL: 830`. | Continue B10.4 remaining InboxLimit/account-limit create-path enforcement. |
|
|
| `docs: land inbox limit handoff tracker` | Landed the B10.4 reference findings and implementation contract: Chatwoot account inbox limits come from `Current.account.usage_limits[:inboxes]`, over-limit create returns `402 { error: "Account limit exceeded. Upgrade to a higher plan" }`, and capacity-policy `InboxCapacityLimit` is explicitly separate assignment-capacity data. | Documentation-only checkpoint; `git diff --check` passed. | Start B10.4 implementation from the recorded guard, migration, handler, and test plan. |
|
|
| `feat(inboxes): enforce chatwoot inbox limits` | Completed B10.4 account-level inbox limit parity: `accounts.inbox_limit` now represents Chatwoot `usage_limits[:inboxes]`, `InboxService.EnsureCanCreateInbox` blocks over-limit creates before persistence, generic inbox and dedicated channel create paths return `402 { error: "Account limit exceeded. Upgrade to a higher plan" }`, and LINE/email/Twilio plus other channel handlers avoid orphan channel rows. | `go test ./internal/handler/api/v1 -run 'Inbox\|Channel\|Capacity\|Limit' -count=1`; `go test ./internal/service -run 'Inbox\|Channel\|Capacity\|Limit' -count=1`; handler and service packages passed; full verification recorded below. No route changes; route dump remains `TOTAL: 830`. | Continue B11 Captain/Copilot persistence, safe disabled states, and frontend payload parity. |
|
|
| `feat(captain): align assistant resources` | Completed B11.1a Captain assistant resource parity for the reused dashboard assistant client: assistant list/show/create/update/delete now use raw Chatwoot/Jbuilder payloads, nested `{ assistant: ... }` bodies are accepted, assistant access is account-scoped, `/captain/assistants/tools` returns the built-in tool array, and assistant inbox bind/list/delete routes use Chatwoot `{ payload, meta }`, raw inbox, and `204` contracts. | `go test ./internal/handler/api/v1 -run CaptainAssistant -count=1`; `go test ./internal/service -run CaptainAssistant -count=1`; route dump/parity regenerated with unchanged `TOTAL: 830`; focused handler fixtures cover account scoping, tools, inbox binding, and no local `{ success, data }` envelopes. | Continue B11.1b with Captain documents, scenarios, assistant responses, bulk actions, and custom tools payload depth; B11 stays Doing until Copilot, LLM/document gates, and smoke coverage land. |
|
|
| `feat(captain): align scenario and tool payloads` | Advanced B11.1b for Captain scenarios and custom tools: scenario create/list/show/update/delete now accept nested `{ scenario }`, enforce account/assistant scope, return Chatwoot raw scenario serializers and `{ payload, meta }` lists, hide disabled scenarios from index, and return `204` on delete; custom tools now accept nested `{ custom_tool }`, auto-generate slugs from title, enforce account scope, and return raw tool serializers or `{ payload, meta }` lists. | `go test ./internal/handler/api/v1 -run 'CaptainScenario\|CaptainCustomTool\|CaptainResource' -count=1`; `go test ./internal/handler/api/v1 -run 'Captain\|Copilot' -count=1`; `go test ./internal/service -run 'Captain\|Copilot' -count=1`; focused fixtures cover nested frontend bodies, account scoping, disabled scenario filtering, raw payloads, custom-tool slug generation, and no local envelopes. | Continue B11.1c with Captain documents, assistant responses, and bulk actions; custom-tool `test` still needs the reference unsaved-tool execution contract and safe network boundary before B11 leaves Review. |
|
|
| `feat(captain): align document response actions` | Completes B11.1c frontend payload depth for Captain documents, assistant responses, bulk actions, and custom-tool test: document list/create/show/sync/delete now use account-scoped raw serializers and `{ payload, meta }`; assistant responses use nested `{ assistant_response }`, account-scoped raw serializers, filters, edited flag, and `204` delete; bulk actions accept Chatwoot `{ type, ids, fields.status }` for `AssistantResponse` and `AssistantDocument`; custom-tool test accepts unsaved nested `{ custom_tool }` configs and returns raw `{ status, body }` with a fakeable HTTP boundary. | Focused B11.1c handler tests cover document list/create/show/sync/delete, response list/create/show/update/delete, bulk approve/delete/sync/delete shapes, custom-tool test success/error contract, account scoping, sync status, and no local envelopes. Verification passed: focused B11.1c handler tests, Captain/Copilot service tests, Captain repository tests, handler/service package tests, escalated full `go test ./...`, and `git diff --check`. | Continue B11.2 with Copilot persistence, disabled-state payloads, and document/LLM/Meilisearch gates. B11 remains Doing until Copilot and external-provider gates are tracked and tested. |
|
|
| `feat(copilot): align thread message payloads` | Completes the B11.2 Copilot thread/message frontend contract: thread create accepts Chatwoot `{ message, assistant_id, conversation_id }`, creates the initial user message plus deterministic assistant fallback when no LLM provider is configured, validates assistant account scope, and returns raw thread payloads. Thread list returns `{ payload }` scoped to current account/user with Chatwoot page size/order. Nested `copilot_messages` list/create returns raw message payloads with embedded thread/user/assistant push data and `204` delete remains available for local compatibility. | `go test ./internal/handler/api/v1 -run 'Copilot' -count=1`; `go test ./internal/handler/api/v1 -run 'Captain\|Copilot' -count=1`; `go test ./internal/service -run 'Captain\|Copilot' -count=1`; `go test ./internal/repository -run 'Copilot' -count=1`; handler/service/repository package tests; escalated full `go test ./...`; `git diff --check`. Focused handler fixtures cover no local envelopes, account/user isolation, assistant scope, nested message order, and no-LLM assistant fallback persistence. | Continue B11.3 with Captain document sync/indexing, Meilisearch/embedding gates, Copilot tasks/preferences/tool-call/playground depth, streaming/realtime compatibility, and reused frontend smoke coverage. |
|
|
| `feat(captain): align preferences payloads` | Starts B11.3a by replacing the legacy local `captain_preferences` CRUD response with Chatwoot's account-level Captain preferences contract: `GET/PUT /captain/preferences` now return raw `{ providers, models, features }`, `captain_models` and `captain_features` persist on accounts, updates merge with existing account settings, invalid model selections return `422`, and update is administrator-gated. Route artifacts were regenerated after removing frontend-unused POST/DELETE preference routes and adding exact no-trailing-slash GET/PUT paths. | `go test ./internal/handler/api/v1 -run 'CaptainPreference' -count=1`; `go test ./internal/service -run 'CaptainPreference' -count=1`; `go test ./internal/handler/api/v1 -run 'Captain\|Copilot' -count=1`; `go test ./internal/service -run 'Captain\|Copilot' -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./internal/service -count=1`; `go test ./internal/model -count=1`; `go test ./internal/router -count=1`; `go test ./cmd/migrate -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; escalated full `go test ./...`; `git diff --check`. | Continue B11.3b with document sync/indexing, Meilisearch/embedding gates, remaining Copilot task/tool-call/playground depth, streaming/realtime compatibility, and reused frontend smoke coverage. |
|
|
| `docs: land captain copilot completion plan` | Documentation-only handoff checkpoint that breaks the remaining B11.3 work into explicit Captain playground, document sync/indexing, Copilot task/tool-call, streaming/realtime, and B12 smoke slices. It also records the reference files, required payload contracts, fallback behavior, verification commands, and status gates so no Hermes-era plan remains implicit. | `git diff --check`; no Go code changed. | Start B11.3b Captain playground implementation from the recorded contract. |
|
|
| `feat(captain): align playground fallback` | Completes B11.3b Captain assistant playground parity: `POST /captain/assistants/:assistant_id/playground` now accepts the Chatwoot frontend top-level `message_content` and `message_history` payload, enforces account-scoped assistant lookup, returns raw legacy `{ content }` or v2 `{ response }` payloads, appends the current user message once for `captain_integration_v2`, preserves history ordering through the LLM boundary, and returns a deterministic raw disabled response when no LLM provider is configured. | `go test ./internal/handler/api/v1 -run CaptainAssistant -count=1`; `go test ./internal/service -run Captain -count=1`; `go test ./internal/handler/api/v1 -run 'Captain\|Copilot' -count=1`; `go test ./internal/service -run 'Captain\|Copilot' -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./internal/service -count=1`; `go test ./...`; `git diff --check`. Focused handler fixtures cover raw no-envelope fallback, no-history default, cross-account rejection, v2 append/no-duplicate history behavior, and fake LLM request capture. | Continue B11.3c with Captain document sync/indexing and Meilisearch/embedding gates. |
|
|
| `feat(captain): gate document sync backend` | Completes B11.3c's first document sync gate: Captain document sync now has a fakeable `CaptainDocumentSyncBackend` boundary and account-scoped `SyncDocumentByAccount` worker entry that marks disabled config as `sync_disabled`, records backend errors as `sync_error`, preserves backend error codes such as `access_denied`, stores successful content/title/fingerprint updates as `synced`, and avoids external HTTP/LLM calls in default tests. | `go test ./internal/service -run CaptainDocument -count=1`; `go test ./internal/service -run 'Captain\|Copilot' -count=1`; `go test ./internal/handler/api/v1 -run 'Captain\|Copilot' -count=1`; `go test ./internal/service -count=1`; `go test ./internal/handler/api/v1 -count=1`; `go test ./...`; `git diff --check`. Focused service fixtures cover disabled, fake success, backend error, backend error-code, content fingerprint normalization, and account scoping. | Continue B11.3d with Copilot task/tool-call persistence and suggestion payload depth. |
|
|
| `docs: land copilot task execution plan` | Documentation-only checkpoint that fully lands the B11.3d/B11.3e/B12 execution plan: Captain task routes now have exact Chatwoot request/response contracts, current Go gaps, task persistence rules, follow-up context behavior, no-provider disabled-state rules, focused verification commands, streaming/realtime fallback gates, and reused frontend smoke deliverables. | `git diff --check`; no Go code changed. | Start B11.3d implementation from the recorded task/tool-call contract before moving to streaming or frontend smoke. |
|
|
|
|
## Next Slice Contract
|
|
|
|
Completed implementation slice: B11.3c now adds Captain document sync backend gates and observable disabled/failure/success states, building on B11.1a-B11.3b Captain/Copilot resource contracts.
|
|
|
|
Completed documentation slice: the remaining development plan is now fully landed in this tracker. B11.3 is split into concrete Captain playground, document sync/indexing, Copilot task/tool-call, and streaming/realtime checkpoints. B12 has a repeatable smoke-report contract. B9.3 delayed/durable worker scheduling and B9.4 macro attachment depth remain named Phase 5 follow-ups.
|
|
|
|
Next implementation slice: start B11.3d Copilot task/tool-call persistence and suggestion payload depth. Do not expand to streaming until rewrite/summarize/reply suggestion/label suggestion/follow-up request payloads, raw disabled states, and reloadable task/tool-call serializers are covered by tests.
|
|
|
|
| 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 CSAT account/public/send/download parity as the current CSAT baseline. | `CsatSurveyResponsesController`, public CSAT controller/views, `CsatSurveyService`, `CsatSurveyListener`, dashboard reports and survey frontend clients. | Done by `f441680`, `ef3a909`, and `b36cf07`; list/metrics/review-note payloads, public object-shaped submission, lock window, response idempotency, resolve-triggered message send, and CSV download are covered. |
|
|
| N10 | Land B9.1a automation-rule controller/frontend contracts before implementation. | Chatwoot automation controller, Jbuilder views, dashboard automation API/store/helper files, current Go handler/service/validator. | Done by `docs: land automation macro execution plan`; reference contracts, Go gaps, implementation checklist, and exit commands are tracked below. |
|
|
| N11 | Keep B9.1a automation-rule CRUD payload parity as current automation API baseline. | Chatwoot automation controller/Jbuilder views, dashboard automation API/store/helper files, current Go handler/service/validator. | Done by `feat(automation): align automation rule payloads`; frontend envelopes, `attribute_key`, operator aliases, array action params, account-scoped update/delete/clone, and empty delete are covered. |
|
|
| N12 | Keep B9.1b automation listener trigger coverage as current event baseline. | `AutomationRuleListener`, `BaseListener`, conversation/message service dispatch, provider webhook persister. | Done by `feat(automation): align rule trigger coverage`; reference listener events, skip rules, changed attributes, and provider message reachability are covered. |
|
|
| N13 | Keep B9.1c execution outcome logging as current automation observability baseline. | Chatwoot automation listener/action service rescue behavior and Go execution log service. | Done by `feat(automation): record rule execution outcomes`; skipped/failed/success action results, event names, and no-stop-on-match behavior are covered. |
|
|
| N14 | Keep B9.1d external action delivery boundary as current action-delivery baseline. | Chatwoot `AutomationRules::ActionService`, `WebhookJob`, transcript mailer path, current Go action service. | Done by `feat(automation): deliver retryable external actions`; webhook/email transcript actions are timeout-bound, retryable, observable in action results, and covered by fake HTTP/mailer tests. |
|
|
| N15 | Keep B9.2 macro CRUD/execute parity as current macro baseline. | Chatwoot `MacrosController`, `Macro`, `MacrosExecutionJob`, `Macros::ExecutionService`, dashboard macro API/store. | Done by `feat(macros): align chatwoot macro payloads`; frontend payloads, visibility/authorization, display-ID execution, and real conversation/message/label/status side effects are covered. |
|
|
| N16 | Keep B10.4 account inbox limits as current limit baseline. | Chatwoot account `usage_limits[:inboxes]` and request exception handler. | Done by `feat(inboxes): enforce chatwoot inbox limits`; generic and dedicated channel create paths return the exact over-limit `402` response and avoid orphan rows. |
|
|
| N17 | Keep B11.1a Captain assistant resources as current Captain baseline. | `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb`, Captain inboxes controller, assistant Jbuilder views, dashboard Captain assistant/inbox API clients, `config/agents/tools.yml`. | Done by `feat(captain): align assistant resources`; assistant CRUD/tools/inbox binding payloads are account-scoped and frontend-shaped. |
|
|
| N18 | Keep B11.1b Captain scenario/custom-tool resources as current Captain baseline. | Captain scenarios/custom tools controllers, Jbuilder views, and dashboard clients. | Done by `feat(captain): align scenario and tool payloads`; scenarios and custom tools use nested frontend bodies, account scoping, raw payloads, and Chatwoot list meta. |
|
|
| N19 | Keep B11.1c Captain document/response/bulk/test resources as current Captain baseline. | Captain documents, assistant responses, bulk actions, custom-tool test controllers, Jbuilder views, and dashboard clients. | Done by `feat(captain): align document response actions`; documents, assistant responses, bulk resource actions, and unsaved custom-tool test use account-scoped Chatwoot payloads with a fakeable HTTP boundary. |
|
|
| N20 | Keep B11.2 Copilot thread/message resources as current Copilot baseline. | `copilot_threads_controller.rb`, `copilot_messages_controller.rb`, Copilot Jbuilder partials, and dashboard `copilotThreads.js`/`copilotMessages.js` clients. | Done by `feat(copilot): align thread message payloads`; threads/messages persist account/user-scoped Chatwoot payloads and no-LLM fallback assistant messages. |
|
|
| N21 | Keep B11.3a Captain preferences as current account-level Captain config baseline. | `preferences_controller.rb`, `CaptainFeaturable`, `config/llm.yml`, dashboard `preferences.js` API/store. | Done by `feat(captain): align preferences payloads`; raw model/provider/feature payloads, merge update behavior, account storage, admin gate, and exact preference routes are covered. |
|
|
| N22 | Keep B11.3b Captain assistant playground payload parity as current playground baseline. | `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb#playground`, dashboard `api/captain/assistant.js`, assistant playground component/store. | Done by `feat(captain): align playground fallback`; top-level `message_content` and `message_history` are accepted, account-scoped assistant lookup is enforced, legacy `{ content }` and v2 `{ response }` shapes are covered, and no-LLM fallback returns raw frontend-compatible JSON without 500. |
|
|
| N23 | Keep B11.3c Captain document sync/indexing gates as current document-sync baseline. | Captain document sync service/jobs, document controller `sync`, existing Meilisearch engine, and any local embedding boundary. | Done by `feat(captain): gate document sync backend`; disabled config, fake successful indexing, failed sync metadata, fingerprint normalization, and account-scoped document lookup are covered without opening external network connections in default tests. |
|
|
| N24 | Implement B11.3d Copilot task/tool-call persistence. | `resource :tasks` routes, Copilot/Captain task services, dashboard Copilot clients, current local `copilot_*` models. | Rewrite/summarize/reply suggestion/label suggestion/follow-up requests persist task/tool-call state or return documented raw disabled states; serializers survive frontend reload. |
|
|
| N25 | Implement B11.3e streaming/realtime compatibility. | Chatwoot Copilot/Captain streaming, push/event payloads, current Go channel dispatcher. | If streaming is not implemented, the endpoint returns deterministic non-streaming payloads and records the fallback; no route returns placeholder success or local envelope. |
|
|
| N26 | Start B12 smoke harness after B11.3 has a tested disabled/external-provider story. | `reference/chatwoot` frontend boot scripts and GoChat dev/test boot flow. | A checked command and `docs/parity/frontend_smoke_report.md` record core and enterprise smoke status. |
|
|
| N27 | Keep Hermes source plans mapped but inactive. | `.hermes/plans/2025-05-24-global-search-meilisearch.md`, `.hermes/plans/2026-05-24-automation-macro-csat.md`. | New work must update this tracker directly; Hermes files are read-only source notes unless the user asks otherwise. |
|
|
| N28 | 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:
|
|
|
|
```bash
|
|
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. | Done by `b36cf07` |
|
|
| B9 | B9.1 | Align automation rule CRUD payloads, validation, condition groups, event names, listener skip rules, event data, execution outcomes, and external action delivery. | Chatwoot automation rule controllers/models, `AutomationRuleListener`, action services/jobs, and dashboard automation builder. | Handler/service/listener tests for CRUD, validation, event mapping, skip rules, changed attributes, provider-dispatched events, execution logs, and retry metadata. | Review; B9.1a done by `3403770`, B9.1b done by `feat(automation): align rule trigger coverage`, B9.1c done by `4e28559`, B9.1d done by `feat(automation): deliver retryable external actions` |
|
|
| B9 | B9.2 | 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. | Review; frontend-critical CRUD/execute done by `feat(macros): align chatwoot macro payloads` |
|
|
| B9 | B9.3 | Add delayed action scheduling and durable worker parity after the B9.1d synchronous retry boundary lands. | Chatwoot automation jobs, delayed action handling, and Phase 5 worker plan. | Worker tests cover queued delayed actions, retries, and observable failures. | Todo |
|
|
| B9 | B9.4 | Review deeper macro attachments/files and durable queued execution once the worker path is selected. | Chatwoot macro attachments and `MacrosExecutionJob`. | Attachment/file tests or explicit durable-worker split. | 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. | Done by `feat(audit): align chatwoot audit log payloads`; writer coverage continues in B10.2 |
|
|
| B10 | 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. | Done by `feat(audit): record enterprise mutations` and `feat(audit): cover operational mutations` |
|
|
| B10 | B10.3 | 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. | Done by `feat(custom-roles): align chatwoot permissions` |
|
|
| B10 | B10.4 | 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. | Done by `feat(inboxes): enforce chatwoot inbox limits` |
|
|
| 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. | Review; assistant CRUD/tools/inbox binding, scenarios, custom tools, documents, assistant responses, bulk actions, and custom-tool test payloads are landed |
|
|
| 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. | Review; thread/message payloads, account/user scoping, assistant scope, and no-LLM fallback are landed; tasks/preferences/tool-call/playground depth remains in B11.3 follow-up |
|
|
| B11 | B11.3 | Add document sync/embedding/LLM job boundaries where external dependencies are required and finish remaining Copilot task/preference/tool-call/streaming depth. | Captain/Copilot jobs, document services, Copilot controllers/services/frontend clients. | Worker tests or explicit feature-gated fallback tests plus Copilot task/preference/tool-call fixtures. | Doing; B11.3a Captain preferences show/update payloads are landed, while document sync/indexing and remaining Copilot task/tool-call depth remain active |
|
|
| 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. | Done by `b36cf07` |
|
|
|
|
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. | Done by `feat(automation): align automation rule payloads` |
|
|
| B9.1b | Implement real rule trigger coverage for Chatwoot automation events: conversation created/updated/opened/resolved and message created. Provider webhook persistence must dispatch into those same conversation/message events. `contact_updated` is not a current Chatwoot automation trigger and stays out unless the local reference/frontend proves otherwise. | `reference/chatwoot/app/listeners/automation_rule_listener.rb`, `reference/chatwoot/app/listeners/base_listener.rb`, existing `channel.Dispatcher` events. | Listener tests prove matching rules execute once per event with account/inbox filters, changed attributes, provider-event reachability, and Chatwoot skip rules. | Done by `feat(automation): align rule trigger coverage` |
|
|
| B9.1c | Persist execution logs and stop-on-match behavior, including failed action records. The current Chatwoot listener runs every matching rule, so Go explicitly preserves no-stop-on-match behavior and records it in tests. | `reference/chatwoot/app/listeners/automation_rule_listener.rb`, `reference/chatwoot/app/services/automation_rules/action_service.rb`, Go execution log service. | Service tests cover success, skipped, failed/partial, per-action result records, and all-matching-rules execution. | Done by `feat(automation): record rule execution outcomes` |
|
|
| 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. | Done by `feat(automation): deliver retryable external actions` |
|
|
| 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. | Done by `feat(macros): align chatwoot macro payloads` |
|
|
| 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. | Review by `feat(macros): align chatwoot macro payloads`; labels/status/messages/private notes/display-ID execution covered, deeper file attachments remain B9.4/Phase 5 |
|
|
|
|
B9.2 current checkpoint:
|
|
|
|
- Macro `index`, `show`, `create`, and `update` now return Chatwoot `{ payload }` envelopes with `id`, `name`, string `visibility`, `account_id`, array-shaped `actions`, and nested `created_by`/`updated_by` agent payloads.
|
|
- Macro create/update accept Chatwoot dashboard action arrays such as `action_params: ["self"]` and normalize them into the existing action execution map without exposing local numeric visibility in responses.
|
|
- Macro show/update/delete/execute are account-scoped. Public macro update/delete now require administrator role; personal macro update/delete require the author; show/execute allow public macros or the author, matching `MacroPolicy`.
|
|
- Macro delete and execute now return empty `200 OK`, matching `MacrosController#destroy/#execute`.
|
|
- Macro execute accepts `conversation_ids`, resolves them as account-scoped conversation display IDs like `MacrosExecutionJob`, and executes real action side effects through `ActionService`: labels, status, outgoing messages, and private notes are covered by handler tests.
|
|
- Deeper ActiveStorage-backed macro file attachment behavior and durable queued execution stay visible under B9.4/B9.3 instead of being hidden under this API checkpoint.
|
|
|
|
B9.2 verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run Macro -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run Macro -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1a automation-rule landing contract:
|
|
|
|
| Area | Reference contract | Current Go gap | Landing requirement |
|
|
| --- | --- | --- | --- |
|
|
| Reference files | `reference/chatwoot/app/controllers/api/v1/accounts/automation_rules_controller.rb`; automation rule Jbuilder views; `reference/chatwoot/app/javascript/dashboard/api/automation.js`; dashboard automation store and helpers. | Current Go code was built around local envelopes and local condition names. | Inspect these reference files before code changes and keep any new behavior tied to their request/response shapes. |
|
|
| List/show/create/update envelopes | Chatwoot `index` returns `{ payload: [...] }`; `show`, `update`, and `clone` return `{ payload: rule }`; `create` returns the raw rule object; `destroy` returns empty `200 OK`. | `automation_rule_handler.go` currently returns local `{ success, data }`, `{ automation_rules, meta }`, or delete JSON. | Add a Chatwoot serializer/binder boundary and update handler tests to assert the exact envelopes and empty delete body. |
|
|
| Rule serializer | Jbuilder emits `id`, `account_id`, `name`, `description`, `event_name`, `conditions`, `actions`, Unix `created_on`, `active`, and optional `files`. | Go currently exposes internal model JSON and timestamp names. | Serialize only Chatwoot fields for frontend routes; preserve internal structs behind the handler/service boundary. |
|
|
| Conditions | Frontend sends `conditions[].attribute_key`, `filter_operator`, `query_operator`, optional `custom_attribute_type`, and `values`; common operators include Chatwoot names such as `equal_to`. | Go `Condition` uses `attribute`; validator mainly expects local operator names such as `equal`. | Accept `attribute_key`, normalize aliases for execution, and respond with `attribute_key` plus Chatwoot operator names where the frontend expects them. |
|
|
| Actions | Frontend sends `actions[].action_name` and `action_params`, often as arrays. | Go `ActionParams` is map-oriented in several paths; direct binding can fail for array params. | Add request DTO/custom normalization that accepts Chatwoot array params without breaking existing map-based action execution. |
|
|
| Update scoping | Chatwoot updates an existing account-scoped rule through permitted fields. | Current update path can save a payload with zero `account_id` if the client omits it. | Load the existing account-scoped rule first, apply permitted fields, and keep `account_id`/ID ownership intact. |
|
|
| Clone/delete scoping | Chatwoot fetches rules within account context and clone response uses the clone view. | Go clone/delete need account-scope and not-found checks reviewed. | Enforce account scope for clone/delete and match clone response shape; delete returns empty `200 OK`. |
|
|
|
|
B9.1a implementation checklist:
|
|
|
|
- Add handler-level DTOs for Chatwoot automation-rule requests instead of binding frontend JSON directly into persistence models.
|
|
- Add serializer helpers for automation rules, conditions, and actions with Chatwoot field names and Unix `created_on`.
|
|
- Keep backward-compatible service/model normalization so existing automation tests that construct local `Condition{Attribute: ...}` and map action params still pass.
|
|
- Update or replace handler tests that currently assert local envelopes.
|
|
- Do not begin B9.1b side-effect expansion until B9.1a CRUD contracts pass focused tests and this document records the checkpoint.
|
|
|
|
B9.1a exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run AutomationRule -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'AutomationRule|Condition|Action' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1a current checkpoint:
|
|
|
|
- Automation rule `index`, `show`, `update`, and `clone` now return the Chatwoot `{ payload }` envelopes; `create` returns the raw automation rule partial; `destroy` returns empty `200 OK`.
|
|
- The handler accepts Chatwoot frontend conditions with `attribute_key`, `custom_attribute_type`, and operators such as `equal_to`/`not_equal_to`, while normalizing to internal operators for matching.
|
|
- The handler accepts frontend action `action_params` arrays and normalizes common Chatwoot actions into the existing map-based action execution format without breaking existing automation service tests.
|
|
- The serializer returns `attribute_key`, Chatwoot operator names, array-shaped `action_params`, Unix `created_on`, and no local `{ success, data }` envelope.
|
|
- Get/update/delete/clone now scope rules by account before mutating; update preserves account ownership instead of saving zero-value `account_id` from partial client payloads.
|
|
- B9.1 remains `Doing` because trigger coverage, durable execution logs, stop-on-match, webhook retry, and email transcript boundaries are still active B9 follow-ups.
|
|
|
|
B9.1a verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run AutomationRule -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'AutomationRule|Condition|Action' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1b automation trigger landing contract:
|
|
|
|
| Area | Reference contract | Current Go gap | Landing requirement |
|
|
| --- | --- | --- | --- |
|
|
| Listener events | `AutomationRuleListener` implements `conversation_updated`, `conversation_created`, `conversation_opened`, `conversation_resolved`, and `message_created`. | Go maps these event names, but focused coverage is thin and event payload extraction is incomplete for some service/provider paths. | Add listener/service tests for every reference event and make conversation ID extraction work from `event.ConversationID`, `event.Data["conversation_id"]`, `conversation`, or `message` as applicable. |
|
|
| Conversation skip rules | Chatwoot skips conversation events performed by an `AutomationRule`; for `conversation_created` and `conversation_opened`, it also skips conversations whose `additional_attributes.auto_reply` is present. | Go listener currently executes solely from event type and conversation ID. | Implement the same skip rules and cover them with listener tests. |
|
|
| Message skip rules | Chatwoot skips `message_created` when performed by an automation rule, when the message is activity, or when the message is auto-reply email. | Go listener currently does not enforce these message guards. | Implement message guard helpers using current `model.Message` fields/content attributes and cover automation-origin, activity, and auto-reply email cases. |
|
|
| Changed attributes | Chatwoot forwards `changed_attributes` into condition filtering for conversation and message events. | Go condition matching supports `changed_attributes`, but conversation update/toggle dispatch paths do not consistently populate it. | Populate changed attributes for status/open/resolved/update paths and verify `attribute_changed` rules fire only when expected. |
|
|
| Provider webhook reachability | Chatwoot provider ingress eventually broadcasts the same conversation/message events consumed by automation. | Go webhook persister already dispatches conversation/message events; B9.1b needs regression coverage that automation remains reachable from persisted provider messages. | Add focused provider/persister or service-level coverage proving incoming provider message persistence can trigger a `message_created` automation rule once. |
|
|
| Non-reference events | Current Chatwoot automation listener does not subscribe to `contact_updated`. | Older Hermes notes mention contact/provider events broadly. | Keep `contact_updated` out of automation triggers unless a later reference/frontend audit proves it is consumed; document provider behavior through conversation/message events instead. |
|
|
|
|
B9.1b exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'AutomationRuleListener|MatchAndExecute|Condition' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Conversation.*Automation|Message.*Automation|ConversationService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -run 'Incoming|Webhook' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1b current checkpoint:
|
|
|
|
- `AutomationRuleListener` now follows the current Chatwoot reference listener event set: `conversation_created`, `conversation_updated`, `conversation_opened`, `conversation_resolved`, and `message_created`; `contact_updated` remains excluded because the local reference listener does not subscribe to it.
|
|
- Listener skip rules now match Chatwoot: events performed by automation are ignored, auto-reply conversation-created/opened events are ignored, and message-created ignores activity messages plus email auto-replies.
|
|
- Listener context extraction now supports `event.ConversationID`, `event.Data["conversation_id"]`, `event.Data["conversation"]`, and `event.Data["message"]`, with account fallback from conversation/message payloads.
|
|
- Conversation update/status/priority paths now dispatch Chatwoot-style `changed_attributes` so `attribute_changed` automation conditions can match status, priority, and SLA policy changes.
|
|
- Conversation creation with an initial message now dispatches `message.created` with `ConversationID`, `ContactID`, message payload, and conversation payload; provider webhook persistence is covered by an automation regression proving incoming provider messages trigger `message_created` rules once.
|
|
- B9 remains `Doing` because B9.2 macro parity and B9.3 delayed/durable worker parity are still active.
|
|
|
|
B9.1b verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'AutomationRuleListener|MatchAndExecute|Condition' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Conversation.*Automation|Message.*Automation|ConversationService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -run 'Incoming|Webhook' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1c current checkpoint:
|
|
|
|
- Automation execution logs now include `event_name`, aggregate action counts, and JSON `action_results` for each action in a matched rule.
|
|
- Rules whose event matches but conditions do not match now write `skipped` execution records with `conditions_not_matched`, giving the B9 tracker explicit skipped-flow evidence.
|
|
- Condition matcher errors are logged as failed rule evaluations instead of disappearing into process logs only.
|
|
- Failed actions record per-action errors while later actions still run, matching Chatwoot `AutomationRules::ActionService#perform` rescue-and-continue behavior.
|
|
- The current Chatwoot listener loops through every matching rule; Go preserves this no-stop-on-match behavior and has regression coverage proving two matching rules both execute.
|
|
- B9 remains `Doing` because B9.2 macro parity and B9.3 delayed/durable worker parity are still active.
|
|
|
|
B9.1c verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'AutomationRuleService_MatchAndExecute|ExecutionLogService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -run 'Incoming|Webhook' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Conversation.*Automation|Message.*Automation|ConversationService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1d retryable external action landing contract:
|
|
|
|
| Area | Reference contract | Current Go gap | Landing requirement |
|
|
| --- | --- | --- | --- |
|
|
| Webhook action payload | `reference/chatwoot/app/services/automation_rules/action_service.rb#send_webhook_event` builds `conversation.webhook_data` plus `event: "automation_event.<rule event>"` and enqueues `WebhookJob`. | `internal/automation/action_service.go` currently treats `send_webhook_event` as a TODO/no-op. | Build a Chatwoot-shaped automation webhook payload from the rule event and conversation context, deliver it through a testable boundary, and expose target/status/attempt metadata in action results. |
|
|
| Webhook delivery retries | `reference/chatwoot/app/jobs/webhook_job.rb` delegates to `Webhooks::Trigger`; related webhook job paths retry retryable failures. | Go has webhook subscription delivery models/config, but automation actions are not timeout-bound or retryable. | Add timeout-bound HTTP delivery with configured max attempts/delay or a documented synchronous retry fallback until the durable worker slice lands. Tests must prove success, retry-to-success, and retry-exhausted failure metadata. |
|
|
| Email transcript action | Chatwoot automation calls the transcript mailer path used by conversation transcript delivery, splitting recipient inputs and respecting email delivery availability. | `handleSendEmailTranscript` is also a TODO/no-op. | Add a transcript mailer interface that can be faked in tests, parses Chatwoot array/comma recipient params, sends per recipient with timeout/retry metadata, and records failures without blocking later actions. |
|
|
| Observability | B9.1c stores per-action `action_results`; B9.1d should extend that record rather than adding hidden logs only. | Action results currently record action name, success/failure, and error only. | Extend action result JSON with optional delivery fields such as `delivery_type`, `target`, `attempts`, `response_code`, `response_body`, and `retryable` while preserving existing tests. |
|
|
| Worker boundary | Chatwoot external delivery is asynchronous; Go can land a synchronous retry boundary first only if the durable queue gap is named. | Phase 5 durable worker parity is still planned. | Keep B9.1d scoped to retryable/testable delivery and record any durable queue gap under B9.3/Phase 5, not as hidden Done work. |
|
|
|
|
B9.1d implementation checklist:
|
|
|
|
- Inspect `internal/automation/action_service.go`, `internal/automation/service.go`, existing webhook delivery code, and current transcript mailer/service boundaries before edits.
|
|
- Add small action delivery interfaces so tests can fake HTTP and mailer behavior without relying on live SMTP or external endpoints.
|
|
- Preserve B9.1c no-stop-on-match and failed-action-continues behavior while enriching `action_results` metadata.
|
|
- Keep route artifacts unchanged unless implementation adds or changes a route.
|
|
- Update this tracker and commit the B9.1d implementation with verification commands before starting B9.2 macro work.
|
|
|
|
B9.1d exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'ActionService|AutomationRuleService_MatchAndExecute|ExecutionLogService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B9.1d current checkpoint:
|
|
|
|
- `send_webhook_event` is no longer a no-op. It accepts Chatwoot array params as `url`, builds a conversation webhook payload with `event: automation_event.<rule event>`, latest message data, conversation IDs/status/priority/attributes, and posts through a timeout-bound HTTP deliverer.
|
|
- The HTTP webhook deliverer retries retryable failures up to three attempts by default, treats `5xx` and `429` as retryable, records response code/body, and is injectable for tests.
|
|
- `send_email_transcript` is no longer a no-op. It parses comma-separated recipients, builds a Chatwoot-style `[#display_id] Conversation Transcript` subject plus transcript body, and delivers per recipient through a fakeable SMTP transcript boundary.
|
|
- `ActionService.ExecuteWithResult` preserves the existing `Execute` API while returning per-action metadata for automation execution logs.
|
|
- `AutomationExecution.action_results` now includes optional `delivery_type`, `target`, `attempts`, `response_code`, `response_body`, and `retryable` fields. Failed external actions still allow later actions/rules to continue under the B9.1c behavior.
|
|
- B9.1 moves to `Review`. Durable queued scheduling and delayed action parity remain B9.3/Phase 5; the next active implementation slice is B9.2 macro CRUD and execution parity.
|
|
|
|
B9.1d verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -run 'ActionService|AutomationRuleService_MatchAndExecute|ExecutionLogService' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
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. | Done by `feat(audit): align chatwoot audit log payloads` |
|
|
| 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. | Done by `feat(audit): record enterprise mutations` and `feat(audit): cover operational mutations` |
|
|
| 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. | Done by `feat(custom-roles): align chatwoot permissions` |
|
|
| 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. | Done by `feat(inboxes): enforce chatwoot inbox limits` |
|
|
|
|
B10.1 current checkpoint:
|
|
|
|
- `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/audit_logs_controller.rb`, `enterprise/app/views/api/v1/accounts/audit_logs/show.json.jbuilder`, `enterprise/app/models/enterprise/audit_log.rb`, and dashboard `auditLogs.js`/store were used as the reference contract.
|
|
- `GET /api/v1/accounts/:account_id/audit_logs/` now returns Chatwoot's top-level audit payload: `per_page`, `total_entries`, `current_page`, and `audit_logs`, without the local `{ success, data, meta }` wrapper.
|
|
- Audit list pagination is fixed to 25 rows per page like `RESULTS_PER_PAGE = 25`; client `per_page` overrides are ignored.
|
|
- Audit rows are account-scoped through either local `account_id` or Chatwoot-style `associated_type = Account` plus `associated_id = account_id`.
|
|
- Audit list/get now require `administrator` or `super_admin` role, matching the enterprise controller's admin authorization gate.
|
|
- The audit serializer emits Jbuilder-compatible fields including actor metadata, request UUID, remote address, version, comment, audited changes, Unix `created_at`, and an explicit `auditable` key. Full polymorphic `auditable.push_event_data` depth remains a B10.2/B10 writer-depth follow-up where the mutating resources are wired.
|
|
|
|
B10.1 verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run Audit -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/repository -run Audit -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run Audit -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B10.2a current checkpoint:
|
|
|
|
- `AuditService.Record` is now the shared audit writer boundary for account-associated mutations. It records `account_id`, `associated_type: Account`, `associated_id`, `auditable_type`, `auditable_id`, `action`, JSON `audited_changes`, optional `user_id/user_type`, request UUID, remote address, and comment.
|
|
- The handler-level audit writer is non-blocking for HTTP responses but logs skipped audit writes, keeping product mutations available while still making missing audit rows visible in tests/logs.
|
|
- Automation rule create/update/delete/clone/toggle now write `AutomationRule` audit rows, matching the enterprise audit concern that audits automation rules associated with the account.
|
|
- Macro create/update/delete now write `Macro` audit rows, matching the enterprise macro audit concern.
|
|
- CustomRole create/update/delete now write `CustomRole` audit rows for this Go enterprise scope. B10.3 later completed the deeper permission-array and deletion-nullification parity.
|
|
- CSAT review-note update now writes a `CsatSurveyResponse` update audit row, covering the enterprise reporting mutation exposed in the reused reports screen.
|
|
- Inbox/channel, conversation assignment/status, SLA policy, and AgentCapacityPolicy writer coverage remain explicit B10.2b work rather than being hidden under this boundary checkpoint.
|
|
|
|
B10.2a verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'CustomRole|AutomationRule|Macro|CsatSurvey' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run Audit -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B10.2b current checkpoint:
|
|
|
|
- Inbox create/update now write `Inbox` audit rows through the shared writer, matching Chatwoot enterprise `audited associated_with: :account, on: [:create, :update]` for inboxes.
|
|
- Conversation update/delete/agent assignment/status toggle now write `Conversation` audit rows, covering the tracker's representative assignment/status/delete paths while keeping Chatwoot's destroy-audit concern visible.
|
|
- SLA policy create/update/delete now write `SlaPolicy` audit rows with serialized threshold/name fields.
|
|
- AgentCapacityPolicy create/update/delete now write `AgentCapacityPolicy` audit rows; nested inbox capacity limit create/update/delete and policy user assignment/removal write `InboxCapacityLimit` and `AgentCapacityPolicyUser` rows.
|
|
- B10.2a and B10.2b together cover the full named B10.2 representative mutation set: inbox, conversation assignment/status, SLA policy, capacity policy, custom role, automation, macro, and CSAT review notes.
|
|
|
|
B10.2b verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'SlaPolicy|AgentCapacity|Inbox|Conversation' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run Audit -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B10.3 CustomRole landing contract:
|
|
|
|
| Area | Chatwoot reference contract | Current Go gap to close | Required landing work |
|
|
| --- | --- | --- | --- |
|
|
| Reference files | `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb`, `enterprise/app/models/custom_role.rb`, `enterprise/app/models/enterprise/account_user.rb`, `enterprise/app/views/api/v1/models/_custom_role.json.jbuilder`, `enterprise/app/views/api/v1/models/_account_user.json.jbuilder`, dashboard `customRole.js`, and `permissionsHelper.js`. | Prior checkpoints audited audit emission, not permission-array or account-user role behavior. | Re-check these files immediately before code changes and keep tests tied to their request/response/status contracts. |
|
|
| Permission keys | Chatwoot stores `permissions` as a text array containing only `conversation_manage`, `conversation_unassigned_manage`, `conversation_participating_manage`, `contact_manage`, `report_manage`, and `knowledge_base_manage`. | Go currently models permissions as a JSON map with local keys and read/full/none levels. | Accept and serialize Chatwoot arrays, reject invalid keys, and either migrate storage to array semantics or provide a compatibility shim that never leaks the local map shape to frontend APIs. |
|
|
| CustomRole API payloads | Index returns a raw array; show/create/update return the raw custom role partial with `id`, `name`, `description`, `permissions`, `created_at`, `updated_at`; destroy is `head :ok`. Permitted params are `custom_role.name`, `description`, and `permissions: []`. | Go custom role handlers still use local response envelopes/statuses and map-shaped permissions. | Replace handler serializer/binder boundary for frontend routes, allow nested `custom_role` params, return raw payloads, make delete return empty `200 OK`, and keep audit writer calls from B10.2. |
|
|
| AccountUser role resolution | Chatwoot `AccountUser.role` remains `agent` or `administrator`; custom role is represented by `custom_role_id`. `permissions` returns `custom_role.permissions + ['custom_role']` when present. | Go allows/uses `role = custom_role` in multiple paths and `HasCustomRole` currently depends on the role string. | Keep persisted role as `agent` when assigning a custom role, treat `custom_role_id > 0` as the source of custom-role permissions, and expose `custom_role_id` plus nested `custom_role` in account-user serializers. |
|
|
| Authorization semantics | CustomRole admin screens are administrator/super_admin only. Custom-role agents should be authorized by the six Chatwoot permission keys, including the conversation manage/unassigned/participating hierarchy. | Go policy code maps local dimensions and levels; custom-role load checks currently depend on `role == custom_role`. | Load custom roles by `custom_role_id`, translate permission arrays into policy checks, add conversation-scope tests for manage/unassigned/participating, and preserve Chatwoot-style denial payload/status. |
|
|
| Delete nullification | `CustomRole has_many :account_users, dependent: :nullify`; deleting a role clears member `custom_role_id` without deleting users. | Go delete currently soft-deletes the role and role cleanup is split across repository/RBAC paths. | On delete, account-scope the role, clear related `account_users.custom_role_id`, keep role as `agent` for legacy rows, and cover reload assertions. |
|
|
| Tests and docs | The reused frontend reads raw `response.data` from `customRole.js` and role labels from `permissionsHelper.js`. | Current tests are not enough to prove frontend compatibility. | Add handler/service/policy tests for raw list/create/update/delete, invalid permissions, admin/non-admin access, `Role=agent + CustomRoleID` permission resolution, and delete nullification. Update this tracker and commit with verification. |
|
|
|
|
B10.3 exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run CustomRole -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CustomRole|RBAC' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/model -run 'CustomRole|AccountUser' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/middleware -run AccountScope -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B10.3 current checkpoint:
|
|
|
|
- `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb`, `enterprise/app/models/custom_role.rb`, `enterprise/app/models/enterprise/account_user.rb`, `enterprise/app/views/api/v1/models/_custom_role.json.jbuilder`, `enterprise/app/views/api/v1/models/_account_user.json.jbuilder`, dashboard `customRole.js`, and `permissionsHelper.js` were used as the reference contract.
|
|
- CustomRole create/update now accepts `custom_role.permissions` as Chatwoot string arrays and stores new rows as JSON arrays while retaining legacy JSON-map reads for old local rows.
|
|
- CustomRole list/show/create/update now return raw Chatwoot custom-role serializers with array `permissions` and no local `{ success, data, meta }` envelope; destroy returns empty `200 OK`.
|
|
- CustomRole admin gates now require administrator/super_admin role and focused handler tests cover non-admin denial.
|
|
- `AccountUser.role` remains `agent` for custom-role users. `custom_role_id > 0` drives `HasCustomRole`, RBAC policy construction, AccountScope custom-role permission loading, and profile account payload permissions.
|
|
- Profile account payloads now include enterprise `custom_role_id`, `custom_role`, and `permissions` values matching Chatwoot's enterprise account-user partial and `AccountUser#permissions` extension.
|
|
- CustomRole delete clears related `account_users.custom_role_id` and keeps those users as agents, matching `dependent: :nullify` behavior.
|
|
- Migration `000023_align_custom_role_permissions` updates the default permission storage from `{}` to `[]` for new deployments and existing empty rows.
|
|
|
|
B10.3 verification:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run CustomRole -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CustomRole|RBAC' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/model -run 'CustomRole|AccountUser' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/middleware -run AccountScope -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/model -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B10.4 InboxLimit landing contract:
|
|
|
|
| Area | Chatwoot reference contract | Current Go gap to close | Required landing work |
|
|
| --- | --- | --- | --- |
|
|
| Limit source of truth | `reference/chatwoot/app/controllers/api/v1/accounts/inboxes_controller.rb` calls `validate_limit` before `create`; `app/helpers/api/v1/inboxes_helper.rb` compares `Current.account.inboxes.count` with `Current.account.usage_limits[:inboxes]`; enterprise `account/plan_usage_and_limits.rb` resolves `limits['inboxes']`, `ACCOUNT_INBOXES_LIMIT`, then `ChatwootApp.max_limit`. Capacity-policy `InboxCapacityLimit` is separate assignment-capacity data. | B10.4 adds `model.Account.InboxLimit`; capacity-policy inbox limits remain separate. | Covered by `feat(inboxes): enforce chatwoot inbox limits`. |
|
|
| Generic inbox create | Over-limit create fails before persistence with HTTP `402` and body `{ "error": "Account limit exceeded. Upgrade to a higher plan" }`. | `InboxService.Create` now calls the account-limit guard before persistence. | Covered by service and handler tests. |
|
|
| Dedicated channel create | Channel-specific create actions must not leave orphan channel rows when the account is already over limit. | Dedicated handlers now precheck where channel rows are created first; service create methods also guard direct callers. | Covered by LINE no-orphan test plus shared error mapping/rollback paths. |
|
|
| Schema and compatibility | Community Chatwoot defaults effectively allow a large maximum; enterprise can lower limits through account/global config. | `accounts.inbox_limit` migration exists and defaults to `0` unlimited for local compatibility. | Covered by `000024_add_account_inbox_limit` and updated init schema. |
|
|
| Tests and docs | Under-limit and over-limit paths must be covered for generic inbox and at least one dedicated channel path, including no-persistence assertions. | Focused B10.4 tests are present. | Covered by inbox service tests, generic handler over-limit test, and LINE no-orphan channel test. |
|
|
|
|
B10.4 current checkpoint:
|
|
|
|
- `reference/chatwoot/app/controllers/api/v1/accounts/inboxes_controller.rb`, `app/helpers/api/v1/inboxes_helper.rb`, `app/controllers/concerns/request_exception_handler.rb`, `app/models/account.rb`, and `enterprise/app/models/enterprise/account/plan_usage_and_limits.rb` were used as the reference contract.
|
|
- `model.Account.InboxLimit` and migration `000024_add_account_inbox_limit` now represent Chatwoot `usage_limits[:inboxes]`; local `0` keeps the community/unlimited default behavior.
|
|
- `InboxService.EnsureCanCreateInbox` checks the current account and `InboxRepo.CountByAccount` before persistence; `ErrInboxLimitExceeded` carries the exact Chatwoot message `Account limit exceeded. Upgrade to a higher plan`.
|
|
- Generic inbox create maps the sentinel error to HTTP `402` with `{ "error": "Account limit exceeded. Upgrade to a higher plan" }` and does not create an inbox row.
|
|
- Dedicated channel create paths now precheck the same guard before channel persistence where handlers create channel rows first; service-level create methods keep the guard for web widget, Telegram, Instagram, Facebook, and direct service callers.
|
|
- LINE over-limit coverage proves no orphan `channel_line` row is left behind. Email, Twilio, TikTok, Twitter, Google, and Microsoft paths also roll back channel rows if the service boundary rejects after channel creation.
|
|
- Capacity-policy `InboxCapacityLimit` remains separate assignment-capacity data and is not used as the account create-limit source.
|
|
|
|
B10.4 exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'Inbox|Channel|Capacity|Limit' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Inbox|Channel|Capacity|Limit' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B11 Captain/Copilot breakdown:
|
|
|
|
| Step | Implementation target | Reference source | Required tests | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| B11.1a | Align Captain assistant CRUD, tools, and inbox binding/list/delete payloads used by `assistant.js` and `inboxes.js`. | Assistant controller, inboxes controller, assistant Jbuilder views, `config/agents/tools.yml`, dashboard Captain assistant/inbox clients. | Handler tests for assistant lifecycle, account scoping, tools array, raw inbox binding, list meta, and `204` delete. | Done by `feat(captain): align assistant resources` |
|
|
| B11.1b | Align Captain scenario and custom-tool CRUD/list payloads. | `scenarios_controller.rb`, `custom_tools_controller.rb`, scenario/custom-tool Jbuilder views, dashboard Captain clients. | Handler fixtures for nested bodies, list/show/create/update/delete, account scoping, disabled scenario filtering, and no local response envelopes. | Done by `feat(captain): align scenario and tool payloads` |
|
|
| B11.1c | Align Captain documents, assistant responses, bulk actions, and custom-tool test execution. | `documents_controller.rb`, `assistant_responses_controller.rb`, `bulk_actions_controller.rb`, `custom_tools_controller#test`, dashboard Captain clients. | Handler/service fixtures for document list/create/show/sync/delete, response list/show/create/update/delete, bulk-action payloads, test execution errors, account scoping, sync status, and no local response envelopes. | Done by `feat(captain): align document response actions` |
|
|
| B11.2 | Align Copilot thread/message APIs, account/user scoping, assistant scope, frontend payloads, and no-LLM fallback persistence. | `copilot_threads_controller.rb`, `copilot_messages_controller.rb`, Copilot Jbuilder partials, dashboard `copilotThreads.js` and `copilotMessages.js`. | Handler tests cover thread/message create/list/get/delete, nested message ordering, assistant/account isolation, user isolation, and deterministic disabled LLM fallback. | Done by `feat(copilot): align thread message payloads` |
|
|
| B11.3a | Align Captain preferences show/update payloads, account-level model/feature storage, and admin update gate. | `preferences_controller.rb`, `CaptainFeaturable`, `config/llm.yml`, dashboard `preferences.js` API/store. | Handler tests cover raw `{ providers, models, features }`, default selected models, merge update behavior, account persistence, invalid model rejection, admin gate, and exact preference routes. | Done by `feat(captain): align preferences payloads` |
|
|
| B11.3b | Align Captain assistant playground request/response behavior and no-LLM fallback. | `assistants_controller.rb#playground`, `Captain::Llm::AssistantChatService`, `AgentRunnerService`, dashboard `api/captain/assistant.js`, playground component/store. | Handler/service tests cover top-level `message_content`, `message_history`, account-scoped assistant lookup, no-history payloads, no duplicate latest user message for v2 behavior if enabled, raw `{ content }` or `{ response }` response shape, and safe no-LLM fallback. | Done by `feat(captain): align playground fallback` |
|
|
| B11.3c | Implement document sync/indexing gates for Meilisearch or the chosen embedding/search backend. | Captain document sync/indexing services, document controller `sync`, existing Meilisearch engine, local embedding/config boundaries. | Tests cover disabled config, failed sync observability, successful fake backend indexing, account-scoped document lookup, and no external network call in default test mode. | Done by `feat(captain): gate document sync backend` |
|
|
| B11.3d | Finish Copilot task, suggestion, and tool-call persistence depth. | `resource :tasks` routes in `reference/chatwoot/config/routes.rb:62-89`, Copilot/Captain task services, dashboard Copilot clients. | Tests cover rewrite/summarize/reply suggestion/label suggestion/follow-up request payloads, persisted task/tool-call records, reloadable serializers, account/user scoping, and raw disabled state when provider config is missing. | Todo |
|
|
| B11.3e | Add streaming/realtime compatibility or deterministic non-streaming fallback. | Chatwoot Captain/Copilot streaming paths, push payloads, current Go dispatcher/realtime boundaries. | Tests prove endpoints never return placeholder success/local envelopes; non-streaming fallback payloads are deterministic and frontend-compatible until true streaming is implemented. | Todo |
|
|
|
|
B11.1a current checkpoint:
|
|
|
|
- Reference files inspected for this landing slice: `reference/chatwoot/config/routes.rb:62-89`, enterprise Captain `assistants_controller.rb`, `inboxes_controller.rb`, assistant/inbox Jbuilder views, dashboard `assistant.js`/`inboxes.js`, and `reference/chatwoot/config/agents/tools.yml`.
|
|
- Assistant list now returns `{ payload: [...], meta: { total_count, page: 1 } }`; show/create/update return the raw assistant serializer with Unix `created_at`/`updated_at`, JSON `config`, `guardrails`, and `response_guidelines`; delete returns `204 No Content`.
|
|
- Create/update accept Chatwoot nested `{ assistant: { ... } }` bodies while retaining flat compatibility for local callers.
|
|
- Assistant get/update/delete are scoped by account, so another account cannot access or remove the resource by primary key.
|
|
- `/api/v1/accounts/:account_id/captain/assistants/tools` returns the raw built-in tool array from the current reference `tools.yml` contract and is registered before dynamic assistant routes.
|
|
- Assistant inbox binding accepts `{ inbox: { inbox_id } }`, validates both assistant and inbox account ownership, returns the raw inbox serializer on create, returns `{ payload, meta }` on list, and returns `204` for account-scoped delete.
|
|
- Route dump/parity were regenerated after the router handler remap; external route count remains `TOTAL: 830` and tracked route parity remains unchanged.
|
|
|
|
B11.1b current checkpoint:
|
|
|
|
- Reference files inspected for this slice: enterprise Captain `scenarios_controller.rb`, `custom_tools_controller.rb`, scenario/custom-tool Jbuilder partials, dashboard `scenarios.js`, and `customTools.js`.
|
|
- Scenario create/update now accept nested `{ scenario: ... }`, show/update/delete are scoped by account plus assistant, index returns enabled scenarios only with `{ payload, meta }`, and single-resource mutations return the raw scenario serializer with assistant ID/name.
|
|
- Custom-tool create/update now accept nested `{ custom_tool: ... }`; create auto-generates the Chatwoot-style slug from title when the frontend omits `slug`; list returns `{ payload, meta }`; show/update/delete are account-scoped; single-resource mutations return the raw custom-tool serializer with Unix timestamps and JSON `auth_config`/`param_schema` values.
|
|
- Focused tests cover nested frontend bodies, account-scope isolation, disabled scenario filtering, custom-tool slug generation, and absence of local `{ success, data }` envelopes.
|
|
- B11.1c has now taken over the remaining Captain resource gaps; B11.2 has landed Copilot thread/message persistence, while external document/LLM gates and remaining Copilot task/tool-call depth remain in B11.3.
|
|
|
|
B11.1c current checkpoint:
|
|
|
|
- Reference files inspected for this slice: enterprise Captain `documents_controller.rb`, `assistant_responses_controller.rb`, `bulk_actions_controller.rb`, `custom_tools_controller#test`, document/assistant-response/bulk Jbuilder views, and dashboard `document.js`, `response.js`, `bulkActions.js`, and `customTools.js` clients.
|
|
- Document create accepts nested `{ document: ... }` with `assistant_id`, validates account/assistant scope, and returns the raw document serializer. Document list returns `{ payload, meta }` with filters for assistant, source, sync state, search, sort, and pagination. Show/delete/sync are account-scoped, delete returns `204`, and sync returns `202 Accepted` while marking `sync_status` as frontend-visible `syncing` through a no-network boundary.
|
|
- Assistant responses accept nested `{ assistant_response: ... }`, return raw response serializers with assistant payloads, support list filters for assistant/document/status/search, mark `edited` when question or answer changes, scope all mutations to account, and delete with `204`.
|
|
- Bulk actions accept Chatwoot `{ type, ids, fields: { status } }`: `AssistantResponse` supports `approve` and `delete`; `AssistantDocument` supports `sync` and `delete`; invalid requests return `422 { success: false }`.
|
|
- Custom-tool `test` now accepts an unsaved nested `{ custom_tool: ... }` config and returns raw `{ status, body }`. The HTTP call boundary is injectable so default tests never open sockets or hit external networks.
|
|
- Focused tests cover document list/create/show/sync/delete, response list/create/show/update/delete, bulk resource actions, invalid bulk errors, custom-tool test payloads, account-scope isolation, sync status mapping, and no local `{ success, data }` envelopes.
|
|
|
|
B11.2 current checkpoint:
|
|
|
|
- Reference files inspected for this slice: enterprise `copilot_threads_controller.rb`, `copilot_messages_controller.rb`, Copilot thread/message Jbuilder partials and index/create views, dashboard `copilotThreads.js`, `copilotMessages.js`, and the Copilot Vue/store callers.
|
|
- Thread create now accepts Chatwoot `{ message, assistant_id, conversation_id }`, rejects blank messages with `422 { error: "Message is required" }`, validates assistant account ownership, stores the thread title from the initial message, creates the initial user message, and creates a deterministic assistant fallback message when no LLM provider is configured.
|
|
- Thread list is scoped to current account plus current user, ordered `created_at DESC`, paged at 5 records, and returns `{ payload: [...] }` with raw thread serializers containing `user`, `assistant`, `account_id`, and Unix `created_at`.
|
|
- Nested `copilot_messages` list/create are scoped through the current user's thread, return `{ payload }` lists ordered ascending and raw message create payloads, and include embedded `copilot_thread` push data for the reused frontend.
|
|
- `GetThread`/`DeleteThread` remain available for local compatibility but now use the same account/user scoping; delete removes thread messages and returns `204`.
|
|
- Focused tests cover raw payloads without local `{ success, data }`, assistant/account isolation, user isolation, nested message persistence/order, and the safe no-LLM assistant fallback message.
|
|
|
|
B11.3a current checkpoint:
|
|
|
|
- Reference files inspected for this slice: `reference/chatwoot/app/controllers/api/v1/accounts/captain/preferences_controller.rb`, `app/models/concerns/captain_featurable.rb`, `config/llm.yml`, dashboard `api/captain/preferences.js`, and store `captain/preferences.js`.
|
|
- Captain preferences now follow Chatwoot `resource :preferences, only: [:show, :update]`: exact no-trailing-slash GET/PUT routes are registered and the frontend-unused local POST/DELETE preference routes are no longer mounted.
|
|
- Preference responses now return raw `{ providers, models, features }` instead of local `{ success, data }` or tone/language rows. Feature payloads include configured models, default model, account-selected model, and enabled state.
|
|
- Account-level `captain_models` and `captain_features` JSON fields now persist the same settings Chatwoot stores through `Account#settings`; updates merge partial incoming maps with existing account settings.
|
|
- Invalid model selections return `422 { error }`, non-admin updates return `401`, and agents can still read the preferences payload.
|
|
- Route dump was regenerated and remains `TOTAL: 830`; route parity remains `270 exact, 7 parameter-compatible, 0 missing`.
|
|
|
|
B11.3b Captain playground landing contract:
|
|
|
|
| Area | Reference contract | Required Go work | Required tests |
|
|
| --- | --- | --- | --- |
|
|
| Route and request body | `POST /api/v1/accounts/:account_id/captain/assistants/:assistant_id/playground` receives top-level `message_content` and `message_history` from dashboard `api/captain/assistant.js`. | Mount the playground route in production and focused test routers; bind top-level payloads while retaining harmless local compatibility only inside the service boundary. | Handler test posts the exact frontend body and proves no local `{ success, data }` envelope. |
|
|
| Account scope | Rails loads the assistant through the current account before executing playground logic. | Fetch assistant by `account_id` and `assistant_id`; reject cross-account assistant IDs. | Cross-account assistant test returns not found/unauthorized and never calls the LLM boundary. |
|
|
| Legacy response shape | When Captain v2 is disabled, Rails calls `Captain::Llm::AssistantChatService#generate_response` and renders raw `{ content: ... }`. | Add a service method that can return the legacy raw content payload when the local v2 feature flag is absent/false. | No-v2 test asserts raw `{ content }` shape for the default path. |
|
|
| V2 response shape | When `captain_integration_v2` is enabled, Rails appends the current user message unless it is already the last history item, calls `AgentRunnerService`, and renders raw `{ response: ... }`. | If a local account feature flag helper exists, implement the append/no-duplicate behavior; otherwise document the missing helper and keep v2 disabled until B11.3d/B11.3e. | V2-enabled test, if implemented, asserts appended history and no duplicate latest user message. If deferred, a disabled-state test records the raw fallback. |
|
|
| No LLM config | The reused frontend must not fail just because provider credentials are missing in local/dev mode. | Guard nil/missing LLM providers and return deterministic raw frontend-compatible JSON with an explanatory disabled/fallback message. | No-provider test returns `200` and a raw payload, not panic/500. |
|
|
| Message history | Chatwoot forwards `message_history` entries with `role`, `content`, and optional `agent_name`. | Preserve history order through the service boundary; include the current message only according to the selected legacy/v2 behavior. | Fake LLM/service test captures history and current message handling. |
|
|
|
|
B11.3b current checkpoint:
|
|
|
|
- Reference files inspected for this slice: `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb#playground`, `reference/chatwoot/spec/enterprise/controllers/api/v1/accounts/captain/assistants_controller_spec.rb`, dashboard `api/captain/assistant.js`, and `components-next/captain/assistant/AssistantPlayground.vue`.
|
|
- Playground now accepts the exact frontend body with top-level `message_content` and `message_history`, while retaining nested `{ assistant: ... }` compatibility inside the handler binding.
|
|
- Assistant lookup is account-scoped through `GetByAccountAndID`, so another account cannot invoke playground for an assistant primary key.
|
|
- Legacy mode returns raw `{ content: ... }`, matching `Captain::Llm::AssistantChatService`; `captain_integration_v2` returns raw `{ response: ... }`, matching `AgentRunnerService`.
|
|
- V2 history handling appends the current user message only when it is not already the last `{ role: "user", content: message_content }` entry.
|
|
- Nil/missing LLM provider now returns deterministic raw fallback JSON instead of panic/500, keeping local/dev frontend use unblocked.
|
|
- Focused tests cover raw no-envelope fallback, no-history default, cross-account rejection, v2 append/no-duplicate behavior, and fake LLM request history capture.
|
|
|
|
B11.3c-B11.3e follow-up contracts:
|
|
|
|
| Slice | Landing rule | Status gate |
|
|
| --- | --- | --- |
|
|
| B11.3c document sync/indexing | Document sync must expose frontend-visible `syncing/synced/failed` states and call a fakeable Meilisearch/embedding boundary. Missing provider config must be a raw disabled/failure state, not a placeholder success. | B11 remains `Doing` until disabled, failed, and fake-success sync tests are present. |
|
|
| B11.3d Copilot tasks/tool-calls | Rewrite, summarize, reply suggestion, label suggestion, and follow-up task routes must accept Chatwoot request payloads, persist enough task/tool-call output for reloads, and serialize raw frontend payloads. | B11 remains `Doing` until task/tool-call fixtures cover account/user scoping and no-provider fallback. |
|
|
| B11.3e streaming/realtime | Streaming can be deferred only with a deterministic non-streaming response or explicit disabled state. Realtime push payloads should use the same thread/message serializer already landed in B11.2. | B11 moves to `Review` only after every streaming/realtime endpoint has a tested frontend-compatible fallback. |
|
|
|
|
B11.3c current checkpoint:
|
|
|
|
- Reference files inspected for this slice: `reference/chatwoot/enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb#sync`, `Captain::Documents::SyncService`, `Captain::Documents::PerformSyncJob`, `Captain::Document`, and document Jbuilder payload fields.
|
|
- `CaptainDocumentService` now exposes a fakeable `CaptainDocumentSyncBackend` boundary and `SyncDocumentByAccount` worker-style entry point, separate from the controller's immediate `202 Accepted` queue marker.
|
|
- Missing sync backend marks the document `failed` with `last_sync_error_code=sync_disabled`, making disabled external configuration observable instead of silently attempting HTTP/LLM work.
|
|
- Backend execution failures mark `sync_error`; backend-supplied codes such as `access_denied` are preserved for frontend/reporting visibility.
|
|
- Fake successful sync updates content, title, normalized content fingerprint, `last_synced_at`, `last_sync_attempted_at`, `sync_status=synced`, and clears the previous error code.
|
|
- Account-scoped document lookup prevents another account from invoking sync work against a document primary key.
|
|
- Default tests do not open external sockets or call an LLM provider; fake backend tests cover success/failure states.
|
|
|
|
B11.3d Copilot task/tool-call landing contract:
|
|
|
|
| Area | Current Chatwoot contract | Current Go gap | Required Go work | Required tests |
|
|
| --- | --- | --- | --- | --- |
|
|
| Task routes and request bodies | `reference/chatwoot/app/controllers/api/v1/accounts/captain/tasks_controller.rb` exposes `rewrite`, `summarize`, `reply_suggestion`, `label_suggestion`, and `follow_up` under account-scoped Captain task routes. Dashboard `api/captain/tasks.js` posts `content`, `operation`, `conversation_display_id`, `follow_up_context`, and `message`. | `CaptainTaskHandler` returns the local `response.OK` envelope, `CaptainTaskExtendedHandler` still expects query `conversation_ids`, and request structs use local `tone`, `language`, `assistant_id`, or DB `conversation_id` fields. | Bind the exact frontend JSON bodies while keeping harmless legacy fields only behind service adapters. Resolve conversations by account plus display ID with `FindByAccountAndDisplayIDOrID`. Return raw Chatwoot JSON, never `{ success, data }`. | Handler tests post exact frontend payloads for all five actions and assert raw shapes, account scoping, display-ID lookup, and no local envelope. |
|
|
| Task response shape | `render_result` returns `{ message: nil }` for nil results, `422 { error }` for errors, or `{ message, follow_up_context }` on success. `useCaptain.js` consumes `data.message` and optional `data.follow_up_context`. | Current Go returns task-specific `rewritten_content`, `summary`, `suggestions`, `source`, or local error envelopes. Missing LLM provider can panic through `s.llmProvider.ChatCompletion`. | Standardize service output to a Chatwoot task result with `message`, optional `follow_up_context`, and raw `422 { error }`. Nil or disabled states must be deterministic and frontend-readable. | No-provider tests for each route assert raw disabled/error payloads and no panic/500. Success tests assert `data.message` and optional `follow_up_context`. |
|
|
| Rewrite operations | `Captain::RewriteService` allows `fix_spelling_grammar`, `improve`, `casual`, `professional`, `friendly`, `confident`, and `straightforward`; `improve` may use conversation context by display ID. | Current Go uses `Tone`/`Language` and does not validate Chatwoot `operation`. | Add `operation` validation, map operations to prompts, and include conversation context for `improve` through account-scoped display-ID lookup. | Rewrite tests cover every accepted operation group, invalid operation `422`, context lookup, and raw success/error shape. |
|
|
| Summary/reply/label suggestions | `SummaryService`, `ReplySuggestionService`, and `LabelSuggestionService` all take `conversation_display_id`; label suggestion may return nil/empty as `{ message: nil }`. | Current Go uses DB IDs or query `conversation_ids`; label/follow-up handler contract is from an older local design. | Implement single-conversation display-ID tasks for summarize, reply suggestion, and label suggestion. Keep any bulk query endpoints only as legacy-compatible extras. | Tests cover display-ID lookup, wrong-account rejection, label nil result shape, and frontend request bodies. |
|
|
| Follow-up context | `Captain::BaseTaskService` emits `follow_up_context` with `event_name`, `original_context`, `last_response`, `conversation_history`, and `channel_type`. `Captain::FollowUpService` accepts only known event names and appends user/assistant turns into `conversation_history`. | Current Go has no Chatwoot follow-up refinement path and the handler expects query params. | Add a follow-up DTO for `follow_up_context`, `message`, and `conversation_display_id`; validate known event names; append history; preserve `channel_type`; return updated `{ message, follow_up_context }`. | Service tests cover missing/invalid context `422`, allowed event names, history append order, and updated `last_response`. |
|
|
| Reloadable task/tool-call state | Chatwoot Copilot messages allow JSON keys `content`, `reasoning`, `function_name`, and `reply_suggestion`; thread/message serializers already landed in B11.2. | Task endpoints currently produce transient responses and do not persist enough frontend state for reload/tool-call review. | Persist task outputs/tool-call-like state either as scoped `copilot_messages` attached to a thread/conversation context or as a documented task record that serializes with the same allowed keys. Do not store unknown JSON keys. | Repository/service tests prove persisted records survive list/reload and reject or strip disallowed message keys. Serializer tests cover `content`, `reasoning`, `function_name`, and `reply_suggestion`. |
|
|
|
|
B11.3d reference files already inspected:
|
|
|
|
- `reference/chatwoot/app/controllers/api/v1/accounts/captain/tasks_controller.rb` for route actions, request params, and `render_result` response rules.
|
|
- `reference/chatwoot/app/javascript/dashboard/api/captain/tasks.js` and `reference/chatwoot/app/javascript/dashboard/composables/useCaptain.js` for frontend request/response usage.
|
|
- `reference/chatwoot/lib/captain/base_task_service.rb`, `rewrite_service.rb`, `summary_service.rb`, `reply_suggestion_service.rb`, `label_suggestion_service.rb`, and `follow_up_service.rb` for operation names, disabled/error behavior, follow-up context, and event names.
|
|
- `reference/chatwoot/enterprise/app/models/copilot_message.rb` for reloadable Copilot message JSON keys.
|
|
|
|
B11.3d exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'CaptainTask|Captain|Copilot' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CaptainTask|Captain|Copilot' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/repository -run 'Copilot|Captain' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
B11.3e streaming/realtime landing contract:
|
|
|
|
| Area | Landing requirement | Done signal |
|
|
| --- | --- | --- |
|
|
| Captain task streams | Any `*/stream` task route must accept the same request DTOs as its non-streaming route. If the configured LLM provider cannot stream, return a deterministic frontend-compatible disabled event sequence or documented non-streaming fallback. | SSE tests cover no-provider, provider error, and success chunk shapes without placeholder success. |
|
|
| Copilot realtime | Copilot message broadcasts should reuse the B11.2 raw thread/message serializer and allowed message keys. | Event/push payload tests compare created message payloads with list/show serializers. |
|
|
| Feature gates | Missing provider credentials, disabled Captain task features, or unimplemented streaming should be visible as raw disabled/error states. | No route returns a local envelope or opaque `500` for a known disabled state. |
|
|
| Route stability | Existing Chatwoot-compatible paths stay mounted; legacy local paths may remain only if they do not change frontend route matching. | Route dump remains stable unless a tracked route is intentionally added and parity artifacts are regenerated. |
|
|
|
|
B12 smoke harness landing contract:
|
|
|
|
| Area | Landing requirement | Done signal |
|
|
| --- | --- | --- |
|
|
| Boot path | Add or document one command that starts GoChat and the reused `reference/chatwoot` frontend pointed at GoChat without adapters. | The command is checked into docs or script form and can be repeated by a new agent. |
|
|
| Seed path | Provide deterministic login/account/inbox/contact/conversation data plus enterprise fixtures for SLA, CSAT, automation, macro, audit, custom role, capacity, Captain, and Copilot. | Seed command or fixture instructions are recorded under `docs/parity/`. |
|
|
| Core smoke | Cover auth/profile, inbox list/settings, conversation list/detail/message send, contact/company views, widget config/message, public inbox flow, and public CSAT. | `docs/parity/frontend_smoke_report.md` records pass/fail with failed API calls mapped to owning slices. |
|
|
| Enterprise smoke | Cover SLA reports, CSAT reports/download, automation rules, macros, audit logs, custom roles, inbox limits, agent capacity, Captain, and Copilot. | Every enterprise failure becomes a named B-slice or Phase 5 follow-up, not hidden browser-only debt. |
|
|
|
|
B11 landing rules:
|
|
|
|
| Area | Landing requirement | Done signal |
|
|
| --- | --- | --- |
|
|
| Captain assistant resources | Audit the current `reference/chatwoot` Captain route/controller/frontend client set before code changes, then implement account-scoped CRUD and nested assistant resources with raw frontend-compatible payloads. | Handler tests cover list/show/create/update/delete, inbox binding, response/scenario/document/custom-tool paths, and disabled-state responses. |
|
|
| Captain document sync | External embedding/LLM work must sit behind fakeable interfaces and config gates; missing provider config must not break the reused frontend. | Tests cover disabled config, fake successful sync, failure metadata, and no unhandled external call in default test mode. |
|
|
| Copilot persistence | Threads/messages and Captain preferences are now persisted and frontend-shaped; tasks, playground state, and tool-call records must still persist enough data for frontend reloads. | B11.2 tests cover thread/message create/list/get/delete and account/user scoping; B11.3a tests cover preferences; B11.3b must add tool-call serialization and task lifecycle tests. |
|
|
| Streaming fallback | If Chatwoot streams a response but GoChat cannot yet stream safely, return a documented frontend-compatible non-streaming or disabled state rather than a placeholder success. | Tests prove the frontend API path receives a deterministic payload/status. |
|
|
| Deferred external depth | Model/provider-specific LLM behavior may be feature-gated, but every gate must be visible in this tracker and covered by tests. | B11 stays `Review`, not `Done`, while any external-provider depth remains deferred. |
|
|
|
|
B11 exit commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'Captain|Copilot' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Captain|Copilot' -count=1
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
git diff --check
|
|
```
|
|
|
|
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 |
|
|
|
|
B12 smoke harness contract:
|
|
|
|
| Area | Landing requirement | Output artifact |
|
|
| --- | --- | --- |
|
|
| Boot command | Provide one documented command or script that starts GoChat in test/dev mode and starts the reused `reference/chatwoot` frontend pointed at GoChat without frontend adapters. | Command recorded in this tracker and, if scripted, checked into the repo. |
|
|
| Seed data | Create or document deterministic seed data for admin login, account, inbox, contact, conversation, CSAT, SLA, macro, automation, audit, and custom-role screens. | Seed command or fixture reference in `docs/parity/`. |
|
|
| Core smoke paths | Cover login/current-user, inbox list/settings, conversation list/detail/message send, contact/company views, widget config/message, and public CSAT. | `docs/parity/frontend_smoke_report.md` with pass/fail status and failed API calls. |
|
|
| Enterprise smoke paths | Cover SLA reports, CSAT reports/download, automation rules, macros, audit logs, custom roles, capacity settings, Captain, and Copilot as their slices land. | Same smoke report links each failure to the owning B-slice. |
|
|
| Exit rule | A failing smoke does not block code commits if the failure is named, scoped, and tracked; hidden failures block moving B12 out of `Doing`. | B12 moves to `Review` only with a repeatable command and checked report. |
|
|
|
|
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.
|
|
- Current rule: no active implementation task should be sourced only from `.hermes/plans`. If a later agent mines those files for additional context, the resulting task must be copied into this tracker before the checkpoint is considered planned.
|
|
|
|
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. | Review |
|
|
| B9 | Automation/macros durable side effects. | Chatwoot automation/macro services and jobs. | Action execution, logs, webhook/email transcript retry tests. | Review |
|
|
| B10 | Audit, CustomRole, InboxLimit. | Chatwoot enterprise admin behavior and policies. | Authorization, audit emission, limits enforcement, admin payload fixtures. | Review |
|
|
| B11 | Captain/Copilot deep behavior. | Chatwoot Captain/Copilot controllers, services, frontend clients. | Assistant/tool/document/scenario/copilot thread/task tests and feature gates. | Doing |
|
|
| 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, resolve-triggered generic survey message send, and CSV download filters/columns. | Channel-specific WhatsApp/Twilio template send hooks and durable queueing are tracked under Phase 5. | Review after `b36cf07`; move to Done only after channel-specific send hooks are either implemented or formally split as external integration scope. |
|
|
| B9 | Done: B9.1 automation rule CRUD/listener/log/external-action parity and B9.2 macro frontend CRUD/execute side effects. | Delayed actions, durable queued worker scheduling, and deeper macro attachment/file parity remain named B9.3/B9.4 follow-ups. | Review after `feat(macros): align chatwoot macro payloads`; move to Done only after durable worker/attachment gaps are implemented or formally split out. |
|
|
| B10 | Done: audit list payload, audit writer boundary for representative mutating core resources, CustomRole permission-key parity, AccountUser permission resolution, admin gates, delete nullification, and account-level InboxLimit enforcement in inbox/channel creation paths. | Frontend smoke coverage for enterprise settings remains B12, not a hidden B10 blocker. | Review after B10.4; move to Done only after reused frontend smoke confirms audit/custom-role/limit settings flows or any smoke gaps are split into owned follow-ups. |
|
|
| B11 | Done through B11.3a: Captain Assistant CRUD, inbox binding, scenarios, documents, responses, custom tools, Copilot threads/messages, and Captain preferences payload fixtures. | B11.3b playground, B11.3c document sync/embedding gates, B11.3d Copilot tasks/tool-calls, and B11.3e streaming/realtime 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:
|
|
|
|
- [x] Fix `internal/handler/api/v1` route param mismatches around `:account_id`, `:id`, and resource IDs.
|
|
- [x] Fix malformed test routes that conflict with Gin wildcard rules.
|
|
- [x] Add defensive handling for zero-value services in handler edge tests.
|
|
- [x] Align handler error responses where tests encode the expected Chatwoot-compatible status category.
|
|
- [x] Keep platform routes bootable and dumpable.
|
|
- [x] Verify `go test ./...`.
|
|
- [x] Verify `go run ./cmd/dump_routes`.
|
|
|
|
Verification commands:
|
|
|
|
```bash
|
|
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:
|
|
|
|
- [x] Define a stable `SearchEngine` interface for Meilisearch-backed search and indexing.
|
|
- [x] Add search config for engine, host, API key, and index prefix.
|
|
- [x] Implement Meilisearch client wrapper with index bootstrapping and settings.
|
|
- [x] Define per-entity documents for conversations, messages, contacts, companies, articles, and help-center content as required by Chatwoot frontend behavior.
|
|
- [x] Wire create/update/delete hooks from services into indexing.
|
|
- [x] Add batch reindex command for existing data.
|
|
- [x] Keep DB search only as explicit development fallback, not as final production mode.
|
|
- [x] Add tests with a mocked search engine and integration hooks that can run without a live Meilisearch instance.
|
|
- [x] 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
- [x] Dump Chatwoot route source declarations from `reference/chatwoot/config/routes.rb`.
|
|
- [x] Dump GoChat routes with `cmd/dump_routes`.
|
|
- [x] Build a first tracked route parity table covering method, path, controller, source, and route status.
|
|
- [x] 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.
|
|
- [x] Patch first-batch route names and wildcard params where Gin constraints require different internal names, while preserving external URLs.
|
|
- [x] Add regression tests for the contact conversations relation route added in this slice.
|
|
- [x] 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
- [x] SLA policies and SLA event tracking.
|
|
- [x] Audit logs and admin-readable audit endpoints.
|
|
- [x] Custom roles and permission checks.
|
|
- [x] Agent capacity and assignment limits.
|
|
- [ ] Assignment policies and auto-assignment compatibility.
|
|
- [ ] Captain/Copilot assistant, custom tools, scenarios, documents, responses, inbox bindings, preferences, and Copilot threads/messages. Captain assistant CRUD/tools/inbox binding, scenario/custom-tool CRUD, documents, assistant responses, bulk actions, custom-tool test payloads, preferences, and Copilot thread/message payloads are landed; external document/LLM gates and remaining Copilot task/tool-call depth remain active.
|
|
- [x] CSAT survey response flow, metrics, filters, and review notes.
|
|
- [x] Inbox limits and account/inbox usage enforcement.
|
|
- [x] 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` | Audit list payload, admin access, associated-account scoping, fixed pagination, shared audit writer boundary, and named representative writer coverage are implemented; deeper resource-specific `auditable.push_event_data` remains future serializer depth if frontend requires it. | Review |
|
|
| 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` | Permission arrays, raw admin payloads, administrator gates, account-user resolution, profile permissions, and delete nullification are implemented; keep in Review until frontend smoke covers enterprise settings. | Review |
|
|
| 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`, assignment-path enforcement, and account-level `usage_limits[:inboxes]` create-path enforcement are covered. | 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` | Captain resource payloads through B11.1c, Copilot thread/message payloads through B11.2, Captain preferences through B11.3a, Captain playground through B11.3b, and document sync gates through B11.3c are aligned; complete B11.3d Copilot task/tool-call depth and B11.3e streaming compatibility. | Doing |
|
|
| 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, resolve-triggered generic survey send, and download CSV are aligned; channel-specific template send hooks remain Phase 5 integration work. | Review |
|
|
| P4.8 | Automation and macros | `internal/automation/*`, `internal/handler/api/v1/automation_rule_handler.go`, `internal/handler/api/v1/macro_handler.go` | Automation-rule CRUD/listener/log/external-action delivery and macro CRUD/display-ID execution parity are implemented; durable delayed actions and deeper macro attachment/file parity remain B9.3/B9.4 follow-ups. | Review |
|
|
| 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 is covered by `b36cf07`. | Review |
|
|
| Automation rules | CRUD payload parity, condition/action parity, event listener coverage, delayed actions, execution logs, no-stop-on-match behavior, webhook and transcript actions. | B9.1a CRUD, B9.1b listener coverage, B9.1c execution outcome logging, and B9.1d retryable webhook/email transcript delivery are covered. Durable delayed actions remain B9.3/Phase 5. | Review |
|
|
| Macros | Macro CRUD, availability by account/user, execute side effects, validation, audit/log output. | CRUD payloads, visibility/authorization, display-ID execution, labels/status/messages/private notes, and empty execute/delete responses are covered by `feat(macros): align chatwoot macro payloads`; deeper attachment files and durable queue remain follow-ups. | Review |
|
|
| Audit | Audit model parity, mutating action coverage, request metadata, filters/pagination, admin endpoint payloads. | List/admin payload parity is covered by `feat(audit): align chatwoot audit log payloads`; named representative writer coverage is covered by `feat(audit): record enterprise mutations` and `feat(audit): cover operational mutations`. | Review |
|
|
| Custom roles | Permission-key parity, account-user role resolution, policy middleware, create/update/delete behavior. | Permission arrays, raw admin payloads, admin gates, RBAC/account-scope/profile resolution, and delete nullification are covered by `feat(custom-roles): align chatwoot permissions`. | Review |
|
|
| Inbox limits | Account/inbox limit models, create/update enforcement, UI-readable limit responses, admin overrides. | Chatwoot nested capacity-limit API, assignment-path enforcement, and account-level create-limit `402` behavior are covered by B5/B7/B10.4. | Review |
|
|
| Captain/Copilot | Assistants, inbox bindings, scenarios, responses, documents, tools, preferences, copilot threads/messages, tasks, streaming/tool-call behavior. | Assistant CRUD/tools/inbox binding fixtures are covered by `feat(captain): align assistant resources`; scenario/custom-tool fixtures are covered by `feat(captain): align scenario and tool payloads`; document/assistant-response/bulk/custom-tool-test fixtures are covered by `feat(captain): align document response actions`; Copilot thread/message fixtures are covered by `feat(copilot): align thread message payloads`; preferences fixtures are covered by `feat(captain): align preferences payloads`; playground fixtures are covered by `feat(captain): align playground fallback`; document sync gate fixtures are covered by `feat(captain): gate document sync backend`; B11.3d-B11.3e now track remaining Copilot task/tool-call behavior, streaming fallback, and frontend smoke screens. | Doing |
|
|
|
|
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:
|
|
|
|
```bash
|
|
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 `ChannelEvent`s 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.
|
|
- 2026-06-05: B8 CSAT download checkpoint committed as `b36cf07 feat(csat): align report download csv`; CSAT report downloads now match Chatwoot CSV headers, filters, absolute conversation links by display ID, enterprise review notes, and the final reporting-period row. Focused CSAT handler tests, handler and automation package tests, full `go test ./...`, and `git diff --check` passed. B8 moves to Review; next slice is B9 automation and macro side-effect parity.
|
|
- 2026-06-05: B9 planning checkpoint committed as `docs: land automation macro execution plan`; B9.1a now has an explicit Chatwoot automation-rule controller/frontend contract, current Go gap table, implementation checklist, and focused exit commands before automation/macro code changes begin.
|
|
- 2026-06-05: B9.1a automation-rule CRUD checkpoint committed as `feat(automation): align automation rule payloads`; automation-rule list/show/update/clone now return Chatwoot `{ payload }`, create returns the raw rule object, delete returns empty `200 OK`, frontend `attribute_key`/`equal_to` conditions and array `action_params` are accepted, responses serialize Chatwoot field names and Unix `created_on`, and get/update/delete/clone are account-scoped. Focused automation handler/service tests, handler and automation package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B9.1b trigger coverage and action side-effect parity.
|
|
- 2026-06-05: B9 planning tracker was fully landed for the next handoff: current baseline points at `3403770 feat(automation): align automation rule payloads`, the ordered control board now starts at B9.1b and runs through B12, B9 is marked `Doing`, and B9.1b records the exact Chatwoot listener contract. The current reference listener only subscribes to conversation created/updated/opened/resolved and message created, so `contact_updated` is explicitly not an automation trigger unless a later local reference/frontend audit proves otherwise.
|
|
- 2026-06-05: B9.1b automation trigger checkpoint committed as `feat(automation): align rule trigger coverage`; automation listener now covers all current Chatwoot reference events, skips automation-origin/auto-reply/activity auto-reply-email cases, extracts account/conversation context from message and conversation event payloads, conversation update/status/priority paths dispatch `changed_attributes`, initial conversation messages dispatch full message-created context, and provider webhook persistence has a regression proving message-created automation reachability. Focused automation/service/webhook tests, package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B9.1c execution log/stop-on-match parity.
|
|
- 2026-06-05: B9.1c execution outcome checkpoint committed as `feat(automation): record rule execution outcomes`; automation evaluations now persist event names, skipped condition outcomes, condition errors, per-action success/failure JSON, and aggregate action counts. Failed actions continue to later actions, and every matching rule still executes in ID order to match the current Chatwoot listener's no-stop-on-match loop. Focused automation tests, automation package tests, relevant service/webhook regressions, full `go test ./...`, and `git diff --check` passed. Next slice is B9.1d retryable webhook/email transcript action delivery.
|
|
- 2026-06-05: B9.1d planning checkpoint prepared as `docs: land automation delivery plan`; the active tracker now points at B9.1d, records the Chatwoot webhook/transcript reference contracts, scopes the retryable/testable action delivery boundary, names B9.3 durable-worker follow-up work, and lists the required focused/full verification commands before macro work starts.
|
|
- 2026-06-05: B9.1d external action delivery checkpoint prepared as `feat(automation): deliver retryable external actions`; `send_webhook_event` now builds Chatwoot-style automation webhook payloads and delivers through a timeout-bound retryable HTTP boundary, `send_email_transcript` parses recipients and sends generated transcript emails through a fakeable SMTP boundary, and automation execution logs now record delivery type, target, attempts, response code/body, retryability, and failure errors. Focused automation tests, automation package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B9.2 macro CRUD and execution parity.
|
|
- 2026-06-05: B9.2 macro payload/execute checkpoint prepared as `feat(macros): align chatwoot macro payloads`; macro CRUD now returns Chatwoot `{ payload }` serializers with string visibility and nested agent authors, accepts frontend array action params, enforces `MacroPolicy`-style public/personal access, deletes and executes with empty `200 OK`, and resolves execute `conversation_ids` by display ID before applying labels/status/messages/private notes. Focused macro handler/service tests, handler and automation package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B10 audit/custom roles/remaining limits while B9.3/B9.4 worker and attachment depth remain tracked follow-ups.
|
|
- 2026-06-05: B10.1 audit list checkpoint prepared as `feat(audit): align chatwoot audit log payloads`; audit logs now return the enterprise Jbuilder top-level payload consumed by the reused settings screen, use fixed 25-row pagination, enforce administrator/super_admin access, scope through Chatwoot associated audits or local account IDs, and emit actor/request/change fields with Unix timestamps. Focused audit handler/repository/service tests, handler package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B10.2 audit writer coverage, then B10.3 CustomRole permission parity.
|
|
- 2026-06-05: B10.2a audit writer checkpoint prepared as `feat(audit): record enterprise mutations`; a shared `AuditService.Record` boundary now writes account-associated audit rows with actor/request metadata and JSON changes, and automation-rule, macro, custom-role, and CSAT review-note mutations call it. Focused CustomRole/AutomationRule/Macro/CsatSurvey handler tests, audit service tests, handler package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B10.2b inbox/conversation/SLA/capacity writer coverage, then B10.3 CustomRole permission parity.
|
|
- 2026-06-05: B10.2b operational audit checkpoint prepared as `feat(audit): cover operational mutations`; inbox create/update, conversation update/delete/assignment/status, SLA policy CRUD, AgentCapacityPolicy CRUD, nested inbox capacity limits, and capacity-policy users now call the shared audit writer. Focused SLA/capacity/inbox/conversation handler tests, audit service tests, handler package tests, full `go test ./...`, and `git diff --check` passed. B10.2 moves to Review; next slice is B10.3 CustomRole permission parity.
|
|
- 2026-06-05: Remaining parity tracker checkpoint prepared as `docs: land remaining parity tracker`; the document now carries executable landing contracts for B10.3 CustomRole permission arrays/account-user resolution/delete nullification, B10.4 account/inbox limit create-path enforcement, B11 Captain/Copilot persistence and feature gates, and B12 reused Chatwoot frontend smoke reporting. Documentation-only checkpoint; `git diff --check` passed.
|
|
- 2026-06-05: B10.3 CustomRole checkpoint prepared as `feat(custom-roles): align chatwoot permissions`; custom roles now accept/serialize Chatwoot permission arrays, return raw enterprise Jbuilder payloads, enforce administrator-only access, keep custom-role account users as `role=agent` with `custom_role_id`, load custom-role permissions through RBAC/AccountScope, expose profile account `custom_role_id/custom_role/permissions`, and nullify account users on role delete. Focused CustomRole/RBAC/model/AccountScope tests, handler/service/model package tests, and full verification were run before commit; sandboxed middleware package tests still require socket-capable execution for miniredis. Next slice is B10.4 InboxLimit create-path enforcement.
|
|
- 2026-06-05: B10.4 handoff tracker prepared as `docs: land inbox limit handoff tracker`; the active plan now records the exact Chatwoot account inbox-limit source (`usage_limits[:inboxes]`), the `402 { error: "Account limit exceeded. Upgrade to a higher plan" }` response contract, the separation from capacity-policy `InboxCapacityLimit`, the required account schema guard, dedicated-channel precheck, no-orphan persistence tests, and updated enterprise status rows. Documentation-only checkpoint; `git diff --check` passed.
|
|
- 2026-06-05: B10.4 InboxLimit checkpoint prepared as `feat(inboxes): enforce chatwoot inbox limits`; account-level `inbox_limit` now gates generic inbox and dedicated channel creation with Chatwoot's exact `402` error body, service and handler tests cover unlimited, below-limit, over-limit, and no-orphan LINE channel behavior, and B10 moves to Review. Focused inbox/channel/limit tests, handler/service package tests, escalated full `go test ./...`, and `git diff --check` passed. Next slice is B11 Captain/Copilot.
|
|
- 2026-06-05: B11.1a Captain assistant checkpoint prepared as `feat(captain): align assistant resources`; assistant CRUD now returns Chatwoot raw assistant payloads and list meta, nested assistant request bodies are accepted, account-scoped get/update/delete prevents cross-account access, tools returns the raw built-in tool array, and assistant inbox create/list/delete use raw inbox/list/204 contracts. Focused CaptainAssistant handler/service tests passed, route dump/parity regenerated with unchanged `TOTAL: 830`, and `git diff --check` passed. Next slice is B11.1b Captain documents/scenarios/responses/custom tools.
|
|
- 2026-06-05: B11.1b Captain scenario/custom-tool checkpoint prepared as `feat(captain): align scenario and tool payloads`; scenarios now accept nested frontend bodies, enforce account/assistant scope, list only enabled scenarios, and return raw scenario/list/204 payloads. Custom tools now accept nested frontend bodies, auto-generate slugs, enforce account scope, and return raw tool/list/204 payloads. Focused CaptainScenario/CaptainCustomTool/CaptainResource tests plus Captain/Copilot focused handler/service tests passed. Next slice is B11.1c documents, assistant responses, bulk actions, and custom-tool test execution.
|
|
- 2026-06-05: B11.1c Captain document/response/action checkpoint prepared as `feat(captain): align document response actions`; documents now return Chatwoot raw/list payloads with account scope and `202` sync marking, assistant responses now support nested bodies, filters, raw serializers, edited tracking, and `204` delete, bulk actions now accept Chatwoot `AssistantResponse`/`AssistantDocument` resource payloads, and custom-tool test now executes unsaved configs through a fakeable HTTP boundary returning `{ status, body }`. Focused B11.1c handler tests, Captain/Copilot service tests, Captain repository tests, handler/service package tests, escalated full `go test ./...`, and `git diff --check` passed. Next slice is B11.2 Copilot persistence and safe LLM/document gates.
|
|
- 2026-06-05: B11.2 Copilot thread/message checkpoint prepared as `feat(copilot): align thread message payloads`; Copilot threads now accept Chatwoot `{ message, assistant_id, conversation_id }`, validate account-scoped assistants, create initial user plus safe no-LLM assistant messages, and return raw thread serializers. Nested Copilot messages now list/create raw message payloads with embedded thread push data, current account/user scoping, ascending message order, and no local envelopes. Focused Copilot handler/service/repository tests, Captain/Copilot handler/service tests, handler/service/repository package tests, escalated full `go test ./...`, and `git diff --check` passed. Next slice is B11.3 document sync/indexing, Meilisearch/embedding gates, Copilot tasks/preferences/tool-call depth, and streaming/realtime compatibility.
|
|
- 2026-06-05: B11.3a Captain preferences checkpoint prepared as `feat(captain): align preferences payloads`; Captain preferences now use Chatwoot's raw `{ providers, models, features }` show/update payload, persist `captain_models`/`captain_features` on accounts, merge partial preference updates, reject invalid model selections with `422`, gate updates to administrators, and expose exact no-trailing-slash GET/PUT preference routes. Focused CaptainPreference handler/service tests, Captain/Copilot handler/service tests, handler/service/model/router package tests, migration tests, route dump/parity regeneration, escalated full `go test ./...`, and `git diff --check` passed. Next slice is B11.3b Captain playground parity.
|
|
- 2026-06-05: B11.3b Captain playground checkpoint prepared as `feat(captain): align playground fallback`; playground now accepts Chatwoot top-level `message_content/message_history`, scopes assistant lookup to the account, returns raw legacy `{ content }` or v2 `{ response }`, appends current v2 user messages without duplication, preserves history into the fakeable LLM boundary, and returns deterministic no-LLM fallback JSON instead of 500. Focused CaptainAssistant/Captain tests, handler/service package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B11.3c document sync/indexing and Meilisearch/embedding gates.
|
|
- 2026-06-05: B11.3c Captain document sync gate checkpoint prepared as `feat(captain): gate document sync backend`; document sync now has a fakeable backend boundary and account-scoped worker entry, disabled config is recorded as `sync_disabled`, backend failures become `sync_error` or preserve explicit backend error codes, fake success updates content/title/fingerprint/timestamps as `synced`, and default tests avoid external network/LLM calls. Focused Captain/Copilot tests, handler/service package tests, full `go test ./...`, and `git diff --check` passed. Next slice is B11.3d Copilot task/tool-call persistence.
|
|
- 2026-06-05: B11.3d/B11.3e/B12 planning checkpoint prepared as `docs: land copilot task execution plan`; the active tracker now records exact Chatwoot Captain task request/response contracts, current Go handler/service gaps, follow-up context and allowed operation rules, reloadable Copilot message/tool-call persistence requirements, no-provider disabled-state expectations, streaming/realtime fallback gates, B12 smoke harness deliverables, and focused exit commands. Documentation-only checkpoint; `git diff --check` passed. Next slice is B11.3d implementation.
|