634 KiB
Chatwoot Parity Development Plan
Updated: 2026-06-06
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/chatwootis 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.
Authoritative Tracking Contract
This file is the only active execution tracker. The .hermes/plans/* files and older reports under docs/ are retained as source notes, but any runnable work must be represented here before it is considered owned.
| Decision axis | Locked rule | Tracking consequence |
|---|---|---|
| Product surface | Reuse the reference/chatwoot frontend without adapters. |
Backend routes, payloads, status codes, pagination, errors, permissions, jobs, and realtime side effects must be Chatwoot-compatible. |
| Reference order | Local reference/chatwoot beats prior docs and assumptions. |
Every parity slice records inspected reference controllers, Jbuilder views, models, jobs, and frontend API clients. |
| Test order | Keep the Go suite green before expanding behavior. | A checkpoint cannot close without focused tests, go test ./..., and git diff --check; docs-only checkpoints require at least git diff --check. |
| Search | Meilisearch is mandatory for final behavior. | DB search can exist only as explicit local fallback and must not be the production parity path. |
| Enterprise | SSO/SAML/LDAP/OIDC are out. All other paid features in this tracker are in. | SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, assignment policies, and limits stay tracked until verified or explicitly split. |
| Commit hygiene | Code, tests, generated route artifacts, and tracker updates land together. | If a route changes, regenerate docs/parity/gochat_routes.txt; if the tracked set changes, regenerate docs/parity/route_parity.md. |
Hermes plan landing map:
| Hermes source | Landed tracker area | Remaining rule |
|---|---|---|
.hermes/plans/2025-05-24-global-search-meilisearch.md |
Phase 1: Meilisearch Search Engine, B6 search payload parity, P5.3 durable search indexing. |
Future search changes start from Meilisearch behavior and live-shape frontend consumers, not from DB fallback. |
.hermes/plans/2026-05-24-automation-macro-csat.md |
B8 CSAT, B9 automation/macro, P5.4/P5.5/P5.6/P5.12 durable automation/CSAT jobs, B12 enterprise smoke. | Future automation, macro, and CSAT gaps must be opened from fresh reference/chatwoot inspection or B12 smoke failures. |
| User-confirmed enterprise scope | Phase 4 enterprise feature completion, B7-B11, B12 enterprise smoke. | SSO/SAML/LDAP/OIDC stay excluded; no other paid-feature family may be silently dropped. |
Hermes task landing checklist:
| Source plan task family | Tracker owner | Current state | Follow-up trigger |
|---|---|---|---|
| Search config, engine interface, Meilisearch client, index settings, DB fallback | Phase 1, B6 | Review. Meilisearch-first engine, bootstrap/settings, no-live tests, release-mode DB fallback rejection, and cmd/reindex_search guard are represented here. |
Reopen only from live Meilisearch gate failure, frontend search payload drift, or reference/chatwoot search behavior not covered by B6. |
| Search indexing for conversations, messages, contacts, companies, articles | P5.3, B4, B6 | Review. Service-layer indexing hooks, document builders, CRM search through Meilisearch, and entity/global payload shapes are tracked in the Commit Ledger. | Reopen when a model mutation path writes searchable data without indexing or when live smoke shows stale search results. |
| Automation rule CRUD, listener triggers, condition/action execution, external webhook/email actions | B9, P5.4 | Review. Rule payloads, trigger coverage, execution logs, retryable external actions, durable team email, and enterprise add_sla action are tracked. |
Reopen from fresh reference evidence for unsupported action params, trigger events, or failed B12 automation smoke. |
| Macro CRUD and execute side effects | B9, P5.5 | Review. Macro serializers, permissions, action params, and display-ID conversation execution are tracked. | Reopen from macro frontend smoke failures or new reference action semantics. |
| CSAT account reports, public submit/update, resolved-conversation sends, downloads, channel templates | B8, P5.6 | Review. CSAT response/report payloads, public lock behavior, resolve-triggered survey send, CSV download, and queued channel templates are tracked. | Reopen from CSAT live smoke failures, provider-template status drift, or new reference survey settings. |
| Enterprise feature set excluding SSO/SAML/LDAP/OIDC | B7-B11, B12 | Review. SLA, audit, custom roles, limits/capacity, Captain/Copilot, CSAT, automation/macros, and assignment policies have explicit tracker rows and current implementation checkpoints. | Reopen or split only from reused frontend failures or current reference/chatwoot evidence. |
| Reused Chatwoot frontend verification | B12, Phase 7 | Review. API, browser, enterprise API, and enterprise browser smoke harnesses are checked in; live execution remains environment-dependent. | Run when PostgreSQL, Redis, Meilisearch, GoChat, Vite, and Chrome are available; every failure becomes a named slice. |
Current Baseline
- Current tracking checkpoint: 2026-06-06 Notion integration destroy parity checkpoint, prepared as
feat(integrations): align notion parity. - Latest implementation checkpoint: this checkpoint, prepared as
feat(integrations): align notion parity. - Latest documentation/tooling checkpoint: this tracker update for P3.24 plus the landed parity tracker history; this document is the active follow-up plan and supersedes
.hermes/plans/*. - Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker.
- Worktree status at this implementation checkpoint: Notion account destroy from
reference/chatwoot/config/routes.rb:379,Api::V1::Accounts::Integrations::NotionController, Notion callback specs, and reused dashboard integrations API are implemented. GoChat now exposes no-trailing and trailing Notion destroy routes, finds OAuth callback-created hooks byapp_id: notionwith legacyhook_typefallback, and returns Chatwoot-compatible empty200 OKdeletes. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack. - Next executable implementation checkpoint: continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from fresh reference/smoke evidence.
go test ./...passes when run outside the restricted socket sandbox; focused Shopify handler/service tests pass in the sandbox.- Route dump succeeds with
941registered routes after Notion no-trailing destroy tracking. - Route parity artifacts now exist under
docs/parity/and are generated bycmd/route_parity. - Tracked frontend-critical route audit covers 404 Chatwoot routes: 391 exact, 0 method-compatible, 13 parameter-compatible, 0 missing. The 13 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits, dashboard app member
:idnames, plus the public article.md/.pngsuffixes served through the same external article route dispatcher. /api/v1/widgetstubs 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/chatwootsource 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
Donewhile 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 | Phase 2/3 audit pass | Route/controller/serializer drift found by B12 or new reference inspection is captured as named slices, not free-form TODOs. | Regenerated route parity artifacts and fixture-backed serializer tests. |
| 2 | Phase 6 placeholder burn-down | Remaining account/contact/conversation/message/inbox placeholder handlers are either real Chatwoot-compatible flows or explicitly tracked as unsupported reference gaps. | rg placeholder audit, route smoke, and endpoint-family tests. |
| 3 | B12 optional live smoke | Run the checked smoke harness in a full PostgreSQL/Redis/Meilisearch/Vite/Chrome environment and turn failures into named slices. | docs/parity/frontend_smoke_report.md pass/fail entries linked to owners. |
Execution queue for the next agent turn:
| Order | Slice ID | Why now | Required commit contents |
|---|---|---|---|
| 1 | Phase 2/3 drift audit | Route parity is currently green for the tracked set, but only fresh reference/frontend inspection proves the next missing reused-frontend path. | Named drift rows with inspected reference files, fixture tests where behavior is known, regenerated route artifacts if the route set changes. |
| 2 | Phase 6 placeholder audit | chatwootParityStub remains only as webhook nil-handler fallback, but every audit result must stay recorded so no frontend-critical stub becomes ownerless. |
rg audit result, either a burn-down implementation or explicit non-frontend fallback classification, focused tests if code changes. |
| 3 | B12 live smoke | The harness is checked in; live pass/fail still needs the full PostgreSQL/Redis/Meilisearch/Vite/Chrome stack. | Updated smoke report with command, environment, failures, and linked owner rows. |
Slice lifecycle:
| State | Meaning | Allowed next move |
|---|---|---|
| Todo | Reference evidence exists, but no Go implementation is started. | Move to Doing only after owner files and tests are identified. |
| Doing | Code or docs are being changed in the current checkpoint. | Move to Review after focused tests, full suite or documented blocker, git diff --check, and tracker update. |
| Review | Implemented and verified locally; optional live smoke may still reveal follow-up work. | Move to Done only when reused frontend or parity fixtures cover the route family and no linked blockers remain. |
| Done | Behavior is covered, docs are current, and no known frontend-critical drift remains for the slice. | Reopen only from new reference/chatwoot or smoke evidence. |
Handoff Contract
This checkpoint is intended to make the development plan complete enough to track without reading Hermes notes first.
- P5.13b scheduled/cached analytics is now in Review. B9.3 is also in Review: current
reference/chatwootautomation actions expose no delayed action parameter; scheduled root jobs are already covered by P5.12; this checkpoint completes remaining automation action side effects with durable team-email jobs and enterpriseadd_sla. B12 has repeatable API/browser/enterprise smoke harnesses in Review; optional live failures should be converted into named slices instead of blocking Phase 5 job work. - The Hermes search plan is fully represented by Phase 1/B6. Future search changes must be Meilisearch-first and must not reintroduce production DB fallback.
- The Hermes automation/macro/CSAT plan is fully represented by B8/B9 and Phase 5. Durable delayed execution is closed for the current reference because no explicit delayed action params exist; future delayed-action changes should be opened only from new reference or smoke evidence. Channel-specific template delivery is in Review.
- Enterprise scope is fixed: SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, assignment policies, and related limits/workflows are in scope; SSO/SAML/LDAP/OIDC are out of scope.
- Every future commit must update one of: the Current Baseline, Commit Ledger, active B-slice board, Phase 5 backlog, B12 smoke report link, or Progress Log.
Open work after the current checkpoint:
| Area | Next concrete action | Tracking location | Done boundary |
|---|---|---|---|
| Phase 5 jobs | Keep B9.3 in Review and open only evidence-backed drift slices for new async gaps. | Phase 5: Background Jobs And Integrations |
Automation tests prove remaining action side effects and reference inspection proves no delayed action params remain. |
| B12 smoke | Run optional live API/browser/enterprise smoke in a full PostgreSQL/Redis/Meilisearch/Vite environment and convert failures into named slices. | B12 reused frontend verification breakdown |
docs/parity/frontend_smoke_report.md records checked pass/fail results and maps failures to slices. |
| P3.7 help center | Portal, category, article route/payload parity, and enterprise article bulk translation are in Review for the inspected dashboard client paths. | Open Checkpoint Contracts, Phase 3 |
Reused Chatwoot help-center dashboard clients can call portal/category/article APIs and bulk article translation without adapters. |
| Phase 2/3 drift | Help-center portal/category/article routes from routes.rb:385-404, account Dyte integration routes from routes.rb:357-358, and dashboard app routes from routes.rb:130 are now in the tracked critical set; expand the next route/serializer fixtures only from B12 failures or fresh reference/frontend inspection. |
Phase 2, Phase 3, docs/parity/ |
Route parity remains 0 missing for tracked frontend routes; serializers have reference fixtures. |
| Phase 6 placeholders | Re-run placeholder audit and burn down any frontend-reachable stub. | Phase 6: Core Product Placeholder Burn-down |
Stub list has no reused-frontend critical path without a named owner. |
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 | Review | B7, B8, B9, B10, and B11 are in Review; B12 reused frontend smoke harnesses exist and optional live runs can expose follow-up slices |
| Phase 5 | Background jobs and integrations | Review | P5.1/P5.2/P5.3/P5.4/P5.5/P5.6/P5.7/P5.8/P5.9/P5.10/P5.11/P5.12 durable worker, event dispatch, search indexing, automation delivery, macro, CSAT survey/template, SLA scan, contact export, inbound webhook persistence, outbound/provider delivery status, Captain document sync/crawl/response/embedding/Copilot/conversation responses, conversation maintenance, message status update, account bulk-action cores, P5.13 analytics rollups/timeseries, and B9.3 delayed-action reference closure are in Review; further work should be named drift from smoke/reference evidence |
| Phase 6 | Core placeholder burn-down | Review | refreshed docs/parity/placeholder_audit.md shows only public webhook nil-handler fallbacks still call chatwootParityStub; no reused-frontend critical account/contact/conversation/message/inbox/widget/public path is ownerless |
| Phase 7 | Verification harness | Review | B12.1 boot/readiness, B12.2a API assertions, B12.2b browser smoke harness, B12.3a enterprise API assertions, and B12.3b enterprise browser route navigation exist; optional live Meilisearch/full-browser runs remain environment-dependent |
Active Remaining Work Board
This table is the shortest authoritative handoff view. If an older lower section still says a completed checkpoint is Todo, prefer this board and update the older row in the same commit that touches that area.
| Priority | Workstream | Current state | Next checkpoint | Commit close rule |
|---|---|---|---|---|
| 0 | P3.27 Notion integration destroy parity | Implemented for reused dashboard Notion integration disconnect flow: no-trailing and trailing destroy routes are registered, the account route is tracked from routes.rb:379, OAuth callback-created hooks are found by app_id: notion with legacy hook-type fallback, and delete returns empty 200 OK instead of a local success/message envelope. |
Keep in Review; reopen only if live Notion OAuth/disconnect smoke exposes feature-gate drift, callback-created hook shape drift, or frontend delete error handling beyond the inspected controller/spec/frontend contract. | Focused Notion handler/service tests, route dump/parity regeneration (941 routes; 391 exact, 13 parameter-compatible, 0 missing out of 404), full go test ./..., and git diff --check must pass before commit. |
| 0 | P3.26 Shopify integration parity | Implemented for reused dashboard Shopify integration and customer-order panel: no-trailing and trailing destroy routes are registered, auth returns raw { redirect_url }, missing shop domains return Chatwoot's 422 { error: "Shop domain is required" }, orders returns raw { orders: [...] }, account-scoped contacts are required, contacts without email/phone return 422 { error: "Contact information missing" }, Shopify customers/search and orders REST calls are made through fakeable clients, order payloads include admin_url, callback-created hooks are found by app_id: shopify, and delete returns empty 200 OK. |
Keep in Review; reopen only if live Shopify OAuth/order smoke exposes token/session setup drift, REST API version drift, Shopify error-body wording, or frontend order payload drift beyond the inspected controller/helper/spec/frontend contract. | Focused Shopify handler/service tests, route dump/parity regeneration (940 routes; 390 exact, 13 parameter-compatible, 0 missing out of 403), sandbox package tests, escalated full go test ./..., and git diff --check passed. |
| 0 | P3.25 Linear integration parity | Implemented for reused dashboard Linear issue flow: no-trailing and trailing destroy routes are registered, teams/team_entities/search/linked/create/link/unlink return raw Chatwoot payloads, provider errors return 422 { error }, blank search returns Chatwoot's exact search-string error, requests proxy Linear GraphQL via fakeable clients, delete revokes Linear tokens and returns empty 200 OK, create/link/unlink resolve conversations by account display ID, conversation links use the reused frontend URL, and successful mutations create Chatwoot-style Linear activity messages. |
Keep in Review; reopen only if live Linear OAuth/GraphQL smoke exposes token refresh behavior, GraphQL query/mutation field drift, activity job queue semantics, or frontend payload drift beyond the inspected controller/processor/client/spec contract. | Focused Linear handler/service tests, route dump/parity regeneration (939 routes; 387 exact, 13 parameter-compatible, 0 missing out of 400), sandbox package tests, escalated full go test ./..., and git diff --check passed. |
| 0 | P3.24 Slack integration parity | Implemented for reused dashboard Slack settings flow: no-trailing and trailing singleton routes are registered for create/update/delete, PUT and PATCH update both work, create accepts frontend code and exchanges it for a Slack access token, hooks are persisted with app_id: slack and disabled status, update accepts frontend reference_id, fetches real private/public Slack channels with pagination, joins public channels, persists reference_id/settings.channel_name/status, create/update return raw Chatwoot app payloads with hooks, list-all returns raw channel arrays, invalid channels return Chatwoot's 422 { error }, and delete returns empty 200 OK. |
Keep in Review; reopen only if live Slack OAuth/channel smoke exposes OAuth redirect, provider error, app serializer, or Slack channel pagination drift beyond the inspected controller/builder/spec/frontend contract. | Focused Slack handler/service tests, route dump/parity regeneration, sandbox focused go test, escalated full go test ./..., and git diff --check passed. |
| 0 | P3.23 nested contact inbox creation API | Implemented for Chatwoot nested contact inbox creation: raw JSON/form/query params are accepted, contact and inbox are account-scoped, missing source IDs are generated through Chatwoot channel rules, duplicate contact+inbox+source rows are returned idempotently, hmac_verified is persisted on creation, and the response is raw { source_id, inbox } rather than the local model/envelope. |
Keep in Review; reopen only if live CRM/new-conversation smoke exposes inbox access-policy, unsupported channel, or serializer drift beyond the inspected controller/builder/Jbuilder contract. | Focused nested ContactInbox handler/service/repository tests, route parity check, full go test ./..., and git diff --check passed. |
| 1 | P3.2a invitation/confirmation mail parity | Implemented for current non-SSO reference behavior: profile resend is no longer a TODO-only log, new invited agents and unconfirmed invited profile resends generate reset-password invitation links, normal unconfirmed profile resends generate confirmation links, users.unconfirmed_email is modeled for email-update routing, and fakeable/environment SMTP mailers keep default tests offline. |
Keep in Review; reopen only if reused frontend smoke or fresh reference evidence exposes additional Devise confirmation states outside excluded SSO/SAML/LDAP/OIDC variants. | Focused profile and agent invitation tests, combined handler/service/repository/router/migrate/app tests, full go test ./..., and git diff --check passed. |
| 2 | Phase 2/3 drift | Tracked route parity is 0 missing for the current 404-route critical set; dashboard /app shell routes from routes.rb:19-20, .well-known app association and custom-domain challenge routes from routes.rb:657-660, Twilio callback routes from routes.rb:639-640, enterprise Twilio voice routes from routes.rb:643-646, root Linear/Shopify/Notion OAuth callback routes from routes.rb:630/634/654, root Twitter/Google/Microsoft/Instagram/TikTok callback routes from routes.rb:626/649-652, assignment policy routes from routes.rb:306-313, help-center portal/category/article routes from routes.rb:385-404, public help-center portal/sitemap/article/category/search/article-detail routes from routes.rb:590-601, enterprise contact outbound voice call from routes.rb:216, account agent-bot routes from routes.rb:94-97, account webhook routes from routes.rb:342, account integration app/hook routes from routes.rb:345-348, account Slack routes from routes.rb:350-352, account Dyte routes from routes.rb:357-358, account Shopify routes from routes.rb:361-364, account Linear routes from routes.rb:365-373, account Notion route from routes.rb:379, dashboard app routes from routes.rb:130, canned response routes from routes.rb:114, notification subscription routes from routes.rb:440, team/team-member routes from routes.rb:296-300, conversation participant routes from routes.rb:150, conversation direct upload route from routes.rb:151, conversation draft message routes from routes.rb:152, conversation inbox assistant route from routes.rb:165, conversation reporting events route from routes.rb:166, and account reporting events route from routes.rb:234 are now explicitly tracked. Notification list/action serializers, user notification-settings raw payloads, campaigns raw payload/display-id routes, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, agent index full-list behavior, agent create/update/delete defaults/errors/scope, account agent-bot route/payload/mutation behavior, account webhook payload/mutation behavior, integration app/hook payload behavior, account Slack OAuth/channel payload behavior, account Dyte create/join payload behavior, account Shopify customer-order payload behavior, account Linear GraphQL issue payload behavior, account Notion destroy behavior, dashboard app raw payload/serializer behavior, canned response raw payload/search/delete behavior, notification subscription payload behavior, team update/frontend route behavior, conversation participant route/payload/final-set update behavior, conversation direct upload ActiveStorage behavior, conversation draft message Redis-key-equivalent behavior, conversation inbox assistant Copilot payload behavior, conversation reporting-event raw array behavior, account reporting-events payload/filter/pagination behavior, label CRUD payloads, custom filters, custom attribute definitions, contact outbound voice calls, assignment policy CRUD and inbox binding payloads, Twilio inbound/status callbacks, enterprise Twilio voice callbacks, Linear/Shopify/Notion root integration callbacks, Shopify OAuth auth redirects, root channel OAuth callbacks, help-center portal/category/article payloads, dashboard app shell route behavior, app association JSON payloads, Cloudflare custom hostname verification, public widget popular-article lists, public help-center category list/show payloads, public portal show/default-locale payloads, public portal search payloads, public article show/markdown/tracking routes, and public sitemap XML now match the inspected Chatwoot contract. |
Continue the next evidence-backed route/controller/serializer drift after Notion parity or from B12 findings. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. |
| 3 | Phase 6 placeholder audit | Widget/public/webhook critical placeholders are burned down; inbox WhatsApp health/register-webhook and sync-template drift are closed; refreshed docs/parity/placeholder_audit.md shows only webhook nil-handler fallbacks still call chatwootParityStub; dashboard conversation transcript/custom-attribute response drift and message retry status drift are closed. |
Keep in Review; reopen only if fresh rg, route smoke, or B12 finds a frontend-reachable placeholder/stub in account/contact/conversation/message/inbox/widget/public paths. |
rg placeholder audit and scripts/parity_frontend_smoke.sh --check are recorded; no reused-frontend blocker is ownerless. |
| 4 | P3.9 account agent-bot API | Implemented for the reused dashboard AgentBots settings route with no-trailing-slash routes, PATCH update, raw Jbuilder-style payloads, account mutation scope, system-bot show/list visibility, empty 200 OK delete, and full reset/avatar action payloads. |
Keep in Review; reopen only if live settings smoke exposes avatar upload storage or administrator-secret gating drift. | Focused AgentBot handler tests, service/router focused tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 5 | P3.10 account webhooks API | Implemented for the reused dashboard Webhooks settings route with PATCH update, Chatwoot { payload } list/mutation serializers, nested { webhook: ... } bodies, generated secret, account-scoped mutations, URL/subscription validation, optional inbox serialization, and empty 200 OK delete. |
Keep in Review; reopen only if live settings smoke exposes audit writer or delivery-signature drift beyond the existing delivery service boundary. | Focused webhook handler/service/router tests, migration test, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 6 | P3.11 account integrations apps/hooks API | Implemented for reused dashboard integrations settings with { payload: [...] } app index, raw app/show and hook mutation payloads, raw or nested create/update bodies, account-scoped hook show/update/delete, persisted app_id/reference_id, reference app catalog seed additions, app hook/settings schema serializers, hook boolean status, account/inbox hook_type, and empty 200 OK delete. |
Keep in Review; reopen if live settings smoke exposes app feature-flag activation, app-specific credential validation, or provider setup side effects beyond this generic hooks boundary. | Focused integration hook handler/service tests, migration/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 7 | P3.12 account Dyte integration API | Implemented for the reused dashboard VideoCallButton/Dyte flow: account-scoped create meeting and add-participant routes are registered, route parity tracks routes.rb:357-358, Dyte credentials are loaded from account integration hooks, create_a_meeting creates a Chatwoot integrations outgoing message with meeting content attributes, and add-participant returns Dyte token payloads while rejecting non-integration messages with the reference error. |
Keep in Review; reopen if live smoke exposes provider payload/status-code drift or frontend message serializer gaps beyond the fakeable Dyte boundary. | Focused Dyte service and handler tests, combined handler/service/router/app/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 8 | P3.13 dashboard apps API | Implemented for the reused dashboard integration settings route: dashboard app CRUD routes are tracked, raw frontend bodies and Rails-style wrappers are accepted, list/show/create/update return the Chatwoot dashboard-app partial fields only, show/update/delete are account-scoped, content validation requires at least one { type: "frame", url: "http(s)://..." }, and delete returns 204. |
Keep in Review; reopen if live settings smoke exposes error-body wording or iframe content validation drift beyond the current reference schema. | Focused dashboard app handler/service tests, combined handler/service/repository/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 9 | P3.14 canned responses API | Implemented for the reused dashboard canned-response settings flow: raw frontend bodies and Rails-style wrappers are accepted, no-trailing-slash frontend aliases and PATCH update are registered, list/search return raw arrays, create/update/show return raw Chatwoot fields, search uses search= and Chatwoot ranking, update/delete are account-scoped, and delete returns empty 200 OK. |
Keep in Review; reopen only if live settings smoke exposes validation/error wording or non-frontend compatibility alias drift beyond the current reference contract. | Focused canned handler/service tests, combined handler/canned/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 10 | P3.15 notification subscriptions API | Implemented for the reused dashboard push helper: user-scoped singular POST/DELETE /api/v1/notification_subscriptions is tracked, raw frontend bodies plus { notification_subscription: ... } wrappers are accepted, browser push identifiers derive from subscription_attributes.endpoint, FCM identifiers derive from device_id/push_token, existing identifiers move/update to the current user, create returns raw Chatwoot subscription JSON with string subscription_type, and destroy returns empty 200 OK whether or not a matching push_token exists. |
Keep in Review; reopen only if live browser push smoke exposes service-worker registration or mobile FCM attribute drift beyond the current reference builder/controller contract. | Focused notification subscription handler tests, service/repository/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 11 | P3.16 teams frontend routes | Implemented for reused dashboard team settings: no-trailing-slash GET/POST /teams and GET/POST/PATCH/DELETE /teams/:team_id/team_members aliases are registered, and frontend PATCH /teams/:team_id update now reaches the Chatwoot team serializer response. |
Keep in Review; reopen only if live team settings smoke exposes team show/store payload drift or team-member authorization/status-code differences beyond the inspected controller/Jbuilder contract. | Focused TeamHandler PATCH test, router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 12 | P3.17 conversation participants API | Implemented for reused dashboard conversation sidebar calls: singleton GET/POST/PATCH/PUT/DELETE /conversations/:conversation_id/participants routes are tracked and registered without the trailing slash, list/create/update return raw Chatwoot agent arrays, create accepts user_ids, update treats user_ids as the final participant set, and destroy returns empty 200 OK. |
Keep in Review; reopen only if live conversation sidebar smoke exposes participant authorization or agent serializer drift beyond the inspected controller/Jbuilder contract. | Focused ConversationParticipant handler/service tests, router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 13 | P3.18 conversation direct uploads API | Implemented for reused dashboard composer uploads: POST /conversations/:conversation_id/direct_uploads is tracked and registered, ActiveStorage blob metadata returns raw signed_id/direct_upload JSON, the returned local PUT URL stores bytes under account-scoped direct uploads, and conversation lookup uses display ID with legacy ID fallback. |
Keep in Review; reopen only if live composer upload smoke exposes ActiveStorage header/status/storage drift beyond the inspected controller/frontend contract. | Focused upload handler/service/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 14 | P3.19 conversation inbox assistant API | Implemented for reused dashboard Copilot state: GET /conversations/:conversation_id/inbox_assistant is tracked and registered, conversation lookup uses display ID with legacy ID fallback, account-scoped captain_inboxes binds the inbox to a Captain assistant, and responses are exactly { assistant: { id, name } } or { assistant: null }. |
Keep in Review; reopen only if live conversation/Copilot smoke exposes feature-flag, authorization, or serializer drift beyond the inspected enterprise controller/store contract. | Focused ConversationCrud handler tests, service/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 15 | P3.20 conversation draft messages API | Implemented for Chatwoot's singular draft-message route: GET/PATCH/PUT/DELETE /conversations/:conversation_id/draft_messages are tracked and registered without a draft ID, conversation lookup uses display ID with legacy ID fallback, show returns raw { has_draft, message }, update upserts the nested draft_message.message, and delete returns empty 200 OK. |
Keep in Review; reopen only if live conversation composer smoke exposes Redis-backed multi-agent or authorization drift beyond the inspected controller/spec contract. | Focused DraftMessage handler/service/repository/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 16 | P3.21 conversation reporting events API | Implemented for Chatwoot enterprise conversation reporting events: GET /conversations/:conversation_id/reporting_events is tracked and registered, conversation lookup uses display ID with legacy ID fallback, events are account/conversation scoped and ordered by created_at ASC, and the response is the raw _reporting_event array with id/name/value/value_in_business_hours/event_start_time/event_end_time/account_id/inbox_id/user_id/conversation_id/created_at/updated_at. |
Keep in Review; reopen only if live enterprise conversation analytics smoke exposes pagination, authorization, or serializer drift beyond the inspected enterprise controller/Jbuilder contract. | Focused Conversation handler tests, combined handler/service/repository/router/route-parity tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 17 | P3.22 account reporting events API | Implemented for Chatwoot enterprise account reporting events: GET /reporting_events is tracked and returns { payload, meta }, accepts optional Unix since/until, inbox_id, user_id, and name filters, orders newest first, paginates at 25 rows per page, and serializes each row through the same _reporting_event field shape. |
Keep in Review; reopen only if live enterprise reports/audit smoke exposes admin-authorization, includes, or date-range drift beyond the inspected controller/spec contract. | Focused ReportingEvent handler tests, combined handler/service/repository/router/route-parity tests, route parity regeneration, full go test ./..., and git diff --check passed. |
| 16 | P6.8 contact outbound voice call | Implemented for the reused dashboard route with Twilio voice-enabled inbox validation, assigned-inbox check, open-conversation reuse, ContactInbox/conversation/call/message persistence, and Chatwoot response shape. | Keep in Review; reopen only if live smoke exposes provider initiation/status-update drift beyond the fakeable persisted boundary. | Focused contact call tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| 17 | B12 optional live smoke | API/browser/enterprise smoke commands are checked in; live runs need PostgreSQL, Redis, Meilisearch, Vite, and Chrome. | Run full live smoke when environment is available and map failures to the board. | docs/parity/frontend_smoke_report.md records pass/fail and linked owners. |
| 18 | B9.3 delayed automation actions | Current reference exposes no delayed automation action params; scheduled-item work is already P5.12; send_email_to_team is durable and add_sla mutates conversation/applied SLA state. |
Keep automation drift closed if future reference/smoke exposes delayed params or unsupported action shapes. | Automation worker/action fixtures verify queued team email replay, retry visibility through worker jobs, and SLA action idempotency. |
| 19 | P5.13 reports/analytics | P5.13a derives visible report aggregates from persisted rows; P5.13b adds lazy rollup freshness, durable day rollup jobs, and /reports metric timeseries. |
Keep report drift closed as frontend smoke or reference inspection exposes additional metrics. | Report fixtures verify timeseries values, cache/freshness behavior, and no hidden placeholder JSON. |
Open Checkpoint Contracts
These rows are the executable development plan from this point forward. A checkpoint is not closed until its row is updated with the commit, reference files inspected, and verification output.
| ID | Owner files | Reference files | Work to land | Exit gate |
|---|---|---|---|---|
| P3.24 Slack integration parity | internal/handler/api/v1/slack_integration_handler.go, internal/service/slack_integration_service.go, internal/repository/integration_hook_repo.go, internal/handler/api/v1/integration_hook_handler.go, cmd/route_parity/main.go, Slack handler/service tests |
reference/chatwoot/config/routes.rb:350-352, reference/chatwoot/app/controllers/api/v1/accounts/integrations/slack_controller.rb, reference/chatwoot/lib/integrations/slack/hook_builder.rb, reference/chatwoot/lib/integrations/slack/channel_builder.rb, reference/chatwoot/app/views/api/v1/accounts/integrations/slack/create.json.jbuilder, reference/chatwoot/app/views/api/v1/accounts/integrations/slack/update.json.jbuilder, reference/chatwoot/app/views/api/v1/models/_app.json.jbuilder, reference/chatwoot/app/views/api/v1/models/_hook.json.jbuilder, reference/chatwoot/spec/requests/api/v1/accounts/integrations/slack_request_spec.rb, reference/chatwoot/app/javascript/dashboard/api/integrations.js, reference/chatwoot/app/javascript/dashboard/store/modules/integrations.js |
Done. Chatwoot Slack singleton behavior is implemented for the reused dashboard flow: frontend no-trailing POST/PATCH/DELETE routes and Rails PUT update are registered alongside trailing aliases; create accepts code/inbox_id, exchanges OAuth against Slack, persists disabled app_id: slack hooks with access tokens; update accepts reference_id, fetches private/public Slack channels with cursor pagination, joins public channels, persists reference_id, settings.channel_name, and enabled status only when the channel exists; create/update return the raw app partial with hooks; list-all returns raw Slack channel arrays; invalid channel returns 422 { error: "Invalid slack channel. Please try again" }; delete returns empty 200 OK. |
Review by feat(integrations): align slack parity; focused handler tests cover no-trailing create raw app payload, empty delete, and PUT route availability; service tests cover OAuth exchange redirect/body, disabled hook creation, channel fetch/update/join, invalid/not-found, and raw channel lists through a fake Slack client; route parity is 379 exact, 13 parameter-compatible, 0 missing out of 392; escalated full go test ./... and git diff --check passed. |
| P3.23 nested contact inbox creation parity | internal/handler/api/v1/contact_handler.go, internal/service/contact_inbox_service.go, internal/repository/contact_inbox_repo.go, internal/handler/api/v1/crm_serializer.go, internal/router/router.go, cmd/route_parity/main.go, nested contact inbox handler/service/repository tests |
reference/chatwoot/config/routes.rb:212, reference/chatwoot/app/controllers/api/v1/accounts/contacts/contact_inboxes_controller.rb, reference/chatwoot/app/controllers/concerns/hmac_concern.rb, reference/chatwoot/app/builders/contact_inbox_builder.rb, reference/chatwoot/app/views/api/v1/accounts/contacts/contact_inboxes/create.json.jbuilder, reference/chatwoot/app/views/api/v1/models/_contact_inbox.json.jbuilder, reference/chatwoot/spec/controllers/api/v1/accounts/contacts/contact_inboxes_controller_spec.rb |
Done. Chatwoot ContactInboxBuilder behavior is implemented for POST /api/v1/accounts/:account_id/contacts/:contact_id/contact_inboxes: raw JSON/form/query params provide inbox_id, optional source_id, and hmac_verified; contact and inbox resolution is account-scoped; missing source IDs are generated by supported channel (api/web_widget UUID, email, sms phone, whatsapp phone without +, twilio sms/whatsapp medium); existing contact+inbox+source rows are returned idempotently; hmac_verified is set on creation; tokens are generated; inbox is preloaded; and the response is only { source_id, inbox: inbox_slim }. |
Review by feat(contacts): align contact inbox creation; focused handler tests cover raw payload shape, HMAC creation, generated source IDs, email idempotency, cross-account inbox rejection, and missing-phone Twilio failure; service tests cover WhatsApp/Twilio generation and idempotency; repository tests cover contact+inbox+source lookup; route parity, full go test ./..., and git diff --check passed. |
| P3.2a invitation/confirmation mail parity | internal/service/profile_service.go, internal/service/profile_confirmation_mailer.go, internal/handler/api/v1/profile_handler.go, internal/service/agent_service.go, internal/repository/agent_repo.go, internal/model/user.go, migrations/000032_add_users_unconfirmed_email.*, profile/agent handler tests |
reference/chatwoot/app/controllers/api/v1/accounts/agents_controller.rb, reference/chatwoot/app/builders/agent_builder.rb, reference/chatwoot/app/views/devise/mailer/confirmation_instructions.html.erb, reference/chatwoot/spec/mailers/confirmation_instructions_spec.rb, reference/chatwoot/spec/enterprise/mailers/devise_mailer_spec.rb for non-SAML invitation wording only |
Done. A shared fakeable confirmation mailer builds Chatwoot-shaped confirmation/invitation payloads; profile resend persists confirmation/reset tokens and delivers no-op/confirmation/invitation states; newly created invited agents get workspace invitation mail; unconfirmed_email is modeled for email-update branch routing; environment SMTP remains a no-op when not configured. SSO/SAML/LDAP/OIDC mail variants stay excluded. |
Review by feat(profile): send confirmation invitations; focused tests cover confirmed no-op, normal confirmation mail, invited workspace invitation mail, agent creation/inviter context, hashed reset-token persistence, and no network in default tests; full go test ./... and git diff --check passed. |
| P5.11a Captain document crawl/schedule | internal/service/captain_document_service.go, internal/service/captain_document_worker.go, internal/app/bootstrap.go |
reference/chatwoot/enterprise/app/jobs/captain/documents/crawl_job.rb, schedule_syncs_job.rb, perform_sync_job.rb, Firecrawl/simple parser jobs |
Durable schedule/crawl producers and handlers with fakeable crawl/parser boundaries. Missing provider config is a failed crawl_disabled state, not placeholder success. |
Review by feat(captain): queue document crawl jobs; focused worker tests prove enqueue, replay, account scope, idempotent scheduler, and disabled/failure states. |
| P5.11b Captain response/embedding fan-out | Captain document/assistant-response services and repositories, Meilisearch/embedding boundaries | response_builder_job.rb, enterprise/app/jobs/captain/llm/update_embedding_job.rb, FAQ generator/embedding services |
Queue FAQ response generation after successful document content changes, reset unedited responses, create/update assistant responses, and fan out embedding update work behind fakeable LLM gates. | Review by feat(captain): queue response embedding jobs; tests cover response reset/create, embedding-disabled retry, fake embedding success, account scope, and no external network in default tests. |
| P5.11c Copilot and conversation response jobs | internal/service/copilot_service.go, internal/service/copilot_response_worker.go, internal/service/captain_conversation_service.go, internal/service/message_service.go, internal/app/bootstrap.go |
enterprise/app/jobs/captain/copilot/response_job.rb, enterprise/app/jobs/captain/conversation/response_builder_job.rb, enterprise/app/services/captain/copilot/chat_service.rb, enterprise/app/services/enterprise/message_templates/hook_execution_service.rb, enterprise/app/models/copilot_message.rb |
Queue assistant replies after Copilot user messages and Captain pending-conversation triggers. Persist assistant messages, enqueue Captain conversation replies/handoff messages, open handoff conversations, and keep fakeable provider disabled/failure states observable through durable retry. | Review by feat(captain): queue copilot response jobs; focused tests cover Copilot enqueue/persist/fallback/retry, Captain conversation enqueue/handoff/retry/non-pending skip, and service/worker/app package replay. |
| P5.13a analytics placeholder burn-down | internal/service/analytics_service.go, internal/service/analytics_query_helpers.go, live/report handlers |
Chatwoot live_reports_controller.rb, reports_controller.rb, BotMetricsBuilder, InboxLabelMatrixBuilder, FirstResponseTimeDistributionBuilder, OutgoingMessagesCountBuilder |
Replace frontend-visible zero/empty placeholder responses for live conversations, grouped live conversations, bot summary/metrics, conversation summary, inbox-label matrix, first-response distribution, and outgoing-message counts with persisted conversation/message/reporting-event queries. | Review by feat(reports): derive analytics aggregates; focused service/handler tests prove non-zero values from persisted rows and rg finds no placeholder TODOs in these methods. |
| P5.13b scheduled/cached analytics | internal/service/analytics_service.go, internal/service/analytics_query_helpers.go, internal/service/reporting_rollup_worker.go, internal/service/reporting_rollup_service.go, report handlers/services, worker bootstrap |
Chatwoot report controllers/services used by dashboard analytics, Reports::DataSource, reporting rollup/backfill jobs |
Wire scheduled or lazy cached rollup freshness/idempotency and route GET /reports to metric timeseries instead of the summary handler. Define freshness rules for expensive rollups. |
Review by feat(reports): add analytics timeseries rollups; report fixtures prove timeseries values are derived from persisted conversations/messages/reporting events, rollups refresh idempotently, durable reporting:rollup_day jobs replay, and hidden placeholder report JSON does not reappear. |
| P3.6 label CRUD payload parity | internal/handler/api/v1/label_handler.go, internal/service/tag_service.go, internal/model/tag.go, internal/repository/tag_repo.go, migrations, label handler/service tests |
reference/chatwoot/app/controllers/api/v1/accounts/labels_controller.rb, app/views/api/v1/accounts/labels/*.json.jbuilder, app/models/label.rb, db/schema.rb, dashboard api/labels.js, store/modules/labels.js |
Account /labels CRUD now returns Chatwoot { payload: [...] } lists, raw mutation/show payloads, empty 200 OK delete, title/description/color/show_on_sidebar persistence, title lowercasing/uniqueness, account scope, nested { label: ... } request bodies, legacy name compatibility, default color, and local association cleanup/rename side effects. |
Review by feat(labels): align chatwoot label payloads; focused handler/service/repository tests, combined handler/service/router tests, go test ./cmd/migrate -count=1, full go test ./..., and git diff --check passed. |
| P3.6 custom filters/custom attribute definitions payload parity | internal/handler/api/v1/custom_filter_handler.go, internal/service/custom_filter_service.go, internal/repository/custom_filter_repo.go, internal/handler/api/v1/custom_attribute_definition_handler.go, internal/service/custom_attribute_definition_service.go, internal/service/custom_attribute_value_service.go, handler/service/repository tests |
reference/chatwoot/app/controllers/api/v1/accounts/custom_filters_controller.rb, app/views/api/v1/accounts/custom_filters/*.json.jbuilder, app/views/api/v1/models/_custom_filter.json.jbuilder, dashboard api/customViews.js, store/modules/customViews.js, custom_attribute_definitions_controller.rb, _custom_attribute_definition.json.jbuilder, dashboard api/attributes.js, store/modules/attributes.js, settings attributes screens/constants |
/custom_filters and /custom_attribute_definitions now return raw Chatwoot arrays/objects instead of local envelopes, accept raw reused-frontend bodies plus Rails-style wrappers, use 200 OK mutation responses, preserve 204 deletes, scope saved views by current user and default filter_type=conversation, normalize custom attribute enum integers to Chatwoot string values, and keep custom attribute value validation compatible with normalized *_attribute definitions plus legacy rows. |
Review by feat(custom-fields): align chatwoot saved view payloads; focused handler/service/repository tests, combined handler/service/router tests, full go test ./..., and git diff --check must pass. |
| P3.7a help center portal route/payload parity | internal/router/router.go, internal/handler/api/v1/portal_handler.go, internal/service/portal_service.go, internal/repository/portal_repo.go, portal handler/service/repository tests |
reference/chatwoot/app/controllers/api/v1/accounts/portals_controller.rb, app/views/api/v1/accounts/portals/*.json.jbuilder, dashboard api/helpCenter/portals.js, help-center portal store/actions |
Frontend no-trailing-slash GET/POST /portals, PATCH /portals/:portal_id, and PATCH archive routes are registered; :portal_id resolves as Chatwoot portal slug with legacy numeric fallback; raw portal bodies plus { portal: ... } wrappers are accepted; portal index/show/create/update return raw Jbuilder-style payloads; delete/logo/archive return empty 200 OK; send_instructions returns { message }; and ssl_status returns enterprise { status, verification_errors }/422 semantics. |
Review by feat(help-center): align portal payloads; focused portal handler/service/repository tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| P3.7b help center category route/payload parity | internal/router/router.go, internal/handler/api/v1/category_handler.go, internal/service/category_service.go, internal/repository/category_repo.go, category tests |
reference/chatwoot/app/controllers/api/v1/accounts/categories_controller.rb, app/views/api/v1/accounts/categories/*.json.jbuilder, dashboard api/helpCenter/categories.js |
Category APIs now resolve portal slugs account-scoped for /portals/:portal_id/categories, accept raw category bodies plus wrappers, expose frontend no-trailing-slash and PATCH routes, return Chatwoot { payload, meta } and { payload: category } serializers with locale filtering and article counts, make delete/reorder empty 200 OK, accept Chatwoot positions_hash, and keep category mutations scoped to the resolved portal. |
Review by feat(help-center): align category payloads; focused category handler/service/repository tests, route dump/parity regeneration, full go test ./..., and git diff --check passed. |
| P3.7c help center article and bulk-action parity | internal/router/router.go, internal/handler/api/v1/article_handler.go, internal/service/article_service.go, internal/repository/article_repo.go, article tests |
reference/chatwoot/app/controllers/api/v1/accounts/articles_controller.rb, app/controllers/api/v1/accounts/articles/bulk_actions_controller.rb, app/views/api/v1/accounts/articles/*.json.jbuilder, dashboard api/helpCenter/articles.js |
Article APIs now resolve portal slugs account-scoped with numeric fallback, accept raw article bodies plus wrappers, expose no-trailing-slash index/create plus frontend PATCH, return Chatwoot { payload, meta } list/search payloads and { payload: article } show/create/update/edit payloads, serialize category/author/views/Unix updated-at/meta/associated articles, make delete/reorder/bulk actions empty 200 OK, accept Chatwoot positions_hash, scope mutations to the resolved portal, and implement frontend bulk update-status/update-category/delete routes. |
Review by feat(help-center): align article payloads; focused article handler tests, service/repository article tests, router test, route dump/parity regeneration, escalated full go test ./..., and git diff --check passed. |
| P3.7d enterprise article bulk translation | internal/handler/api/v1/article_handler.go, internal/service/article_service.go, internal/repository/article_repo.go, internal/app/bootstrap.go, article handler/service tests |
reference/chatwoot/enterprise/app/controllers/enterprise/api/v1/accounts/articles/bulk_actions_controller.rb, enterprise/app/jobs/captain/articles/translate_job.rb, enterprise/app/services/captain/llm/article_translation_service.rb, dashboard api/helpCenter/articles.js, BulkTranslateDialog.vue |
Article bulk translate now validates captain_tasks, portal allowed locales, optional target-locale category, and selected articles; detects existing root-article translations and returns 409 { duplicate_articles } unless force is true; queues durable low-priority captain:article_translate jobs; maps locale codes to English language names; and creates or updates draft translated articles through the wired LLM translation backend using Chatwoot's title/content prompts. |
Review by feat(help-center): queue article translations; focused service/handler tests, combined service/handler/repository/app tests, full go test ./..., and git diff --check passed. No route changes. |
| P3.9 account agent-bot API parity | internal/router/router.go, internal/handler/api/v1/agent_bot_handler.go, internal/service/agent_bot_service.go, internal/repository/agent_bot_repo.go, agent bot handler tests |
reference/chatwoot/config/routes.rb:94-97, reference/chatwoot/app/controllers/api/v1/accounts/agent_bots_controller.rb, app/views/api/v1/accounts/agent_bots/*.json.jbuilder, app/views/api/v1/models/_agent_bot.json.jbuilder, app/models/agent_bot.rb, app/presenters/agent_bot_presenter.rb, dashboard api/agentBots.js, store/modules/agentBots.js, settings agentBots route/components |
Account agent-bot APIs now expose frontend no-trailing-slash GET/POST and PATCH update routes, track the full Chatwoot account agent-bot route set, return raw Jbuilder-style arrays/objects with thumbnail, bot_config, system_bot, account-scoped access_token/secret, accept JSON or multipart form bodies, preserve explicit empty update fields, scope create/update/delete/reset/avatar mutations to current-account bots, allow show/index of system bots through accessible_to, return empty 200 OK delete, and return full bot payloads after reset-access-token/reset-secret/avatar deletion. |
Review by feat(agent-bots): align account payloads; focused AgentBot handler tests, go test ./internal/handler/api/v1 ./internal/service ./internal/router -run 'AgentBot|Router' -count=1, route dump/parity regenerated to TOTAL: 864 and 308 exact, 7 parameter-compatible, 0 missing out of 315, full go test ./..., and git diff --check passed. |
| P3.10 account webhook API parity | internal/router/router.go, internal/handler/api/v1/webhook_subscription_handler.go, internal/service/webhook_subscription_service.go, internal/repository/webhook_subscription_repo.go, internal/model/webhook_subscription.go, webhook handler/service tests, migrations |
reference/chatwoot/config/routes.rb:342, reference/chatwoot/app/controllers/api/v1/accounts/webhooks_controller.rb, app/views/api/v1/accounts/webhooks/*.json.jbuilder, app/models/webhook.rb, dashboard api/webhooks.js, store/modules/webhooks.js, settings integrations/Webhooks/* components |
Account webhook APIs now match the reused dashboard settings flow: index returns { payload: { webhooks } }, create/update return { payload: { webhook } }, delete returns empty 200 OK, frontend PATCH update is registered, nested { webhook: ... } request bodies are accepted, name/url/subscriptions/inbox_id/secret serialize like Jbuilder, mutations are scoped through current account, URL/subscription validation follows the reference allowed event set, and outgoing delivery filtering keeps using the same persisted subscription rows. |
Review by feat(webhooks): align account payloads; focused webhook handler/service tests, router focused test, go test ./cmd/migrate -count=1, route dump/parity regenerated to TOTAL: 865 and 313 exact, 7 parameter-compatible, 0 missing out of 320, full go test ./..., and git diff --check passed. |
| P3.11 account integration apps/hooks API parity | internal/router/router.go, internal/handler/api/v1/integration_hook_handler.go, internal/service/integration_hook_service.go, internal/repository/integration_hook_repo.go, internal/model/integration_hook.go, integration hook handler/service tests, migrations, cmd/route_parity |
reference/chatwoot/config/routes.rb:345-348, reference/chatwoot/app/controllers/api/v1/accounts/integrations/apps_controller.rb, app/controllers/api/v1/accounts/integrations/hooks_controller.rb, app/views/api/v1/accounts/integrations/apps/*.jbuilder, app/views/api/v1/accounts/integrations/hooks/*.jbuilder, app/views/api/v1/models/_app.json.jbuilder, app/views/api/v1/models/_hook.json.jbuilder, app/models/integrations/app.rb, app/models/integrations/hook.rb, config/integration/apps.yml, dashboard api/integrations.js, store/modules/integrations.js, settings integrations components |
Account integration apps/hooks now match the reused dashboard integration settings contract: app index returns { payload: [...] }; app show returns a raw app object; hooks create/show/update return raw hook objects; delete returns empty 200 OK; frontend no-trailing-slash app/hook routes and hook PATCH update are registered and tracked; raw frontend hook bodies and nested { hook: ... } are accepted; hooks persist app_id/reference_id; show/update/delete are account-scoped; hook serializers expose app_id, boolean status, account_id, account/inbox hook_type, settings, reference_id, and optional inbox; app serializers expose hooks, action, button, allow_multiple_hooks, settings_form_schema, and visible_properties; migration 000030 seeds the additional reference app catalog rows. |
Review by feat(integrations): align app hook payloads; focused integration hook handler/service tests, go test ./cmd/migrate -count=1, go test ./cmd/route_parity -count=1, router focused test, route dump/parity regenerated to TOTAL: 869 and 321 exact, 7 parameter-compatible, 0 missing out of 328, full go test ./..., and git diff --check passed. |
| P3.12 account Dyte integration API parity | internal/router/router.go, internal/handler/api/v1/dyte_integration_handler.go, internal/service/dyte_integration_service.go, internal/service/message_service.go, Dyte handler/service tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:357-358, reference/chatwoot/app/controllers/api/v1/accounts/integrations/dyte_controller.rb, reference/chatwoot/lib/integrations/dyte/processor_service.rb, reference/chatwoot/lib/dyte.rb, reference/chatwoot/app/javascript/dashboard/api/integrations/dyte.js, reference/chatwoot/app/javascript/dashboard/components/widgets/VideoCallButton.vue, reference/chatwoot/app/javascript/dashboard/components-next/message/bubbles/Dyte.vue |
Account Dyte APIs now match the reused dashboard video-call boundary: POST /integrations/dyte/create_a_meeting and POST /integrations/dyte/add_participant_to_meeting are registered under account integrations and tracked in route parity; create resolves conversations by account display ID, enforces inbox access, loads account Dyte hook credentials, calls a fakeable Dyte backend, persists an outgoing integrations message with content_attributes.type=dyte and data.meeting_id, and returns the normal Chatwoot message serializer; add-participant resolves account-scoped messages, rejects non-integrations messages with 422 { error: { error: "Invalid message type. Action not permitted" } }, and returns the Dyte participant token payload. |
Review by feat(integrations): align dyte meeting routes; focused Dyte service tests, focused Dyte handler tests, combined handler/service/router/app/route-parity tests, route dump/parity regenerated to TOTAL: 907 and 355 exact, 9 parameter-compatible, 0 missing out of 364, full go test ./..., and git diff --check passed. |
| P3.13 dashboard apps API parity | internal/router/router.go, internal/handler/api/v1/dashboard_app_handler.go, internal/service/dashboard_app_service.go, internal/model/dashboard_app.go, internal/repository/dashboard_app_repo.go, dashboard app handler/service tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:130, reference/chatwoot/app/controllers/api/v1/accounts/dashboard_apps_controller.rb, reference/chatwoot/app/views/api/v1/accounts/dashboard_apps/*.json.jbuilder, reference/chatwoot/app/views/api/v1/models/_dashboard_app.json.jbuilder, reference/chatwoot/app/models/dashboard_app.rb, reference/chatwoot/app/javascript/dashboard/api/dashboardApps.js, reference/chatwoot/app/javascript/dashboard/store/modules/dashboardApps.js, dashboard integrations DashboardApps modal/index components |
Dashboard app APIs now match the reused settings flow: the full Chatwoot resource route set is tracked; create/update accept raw frontend bodies and Rails-style { dashboard_app: ... } wrappers; create returns 200 OK; list returns a raw array; show/create/update return raw dashboard app objects containing only id, title, content, and created_at; show/update/delete are scoped by current account; delete returns 204; and content validation requires at least one frame widget with an HTTP(S) URL. |
Review by feat(dashboard-apps): align chatwoot payloads; focused dashboard app handler/service tests, combined handler/service/repository/router/route-parity tests, route dump/parity regenerated to TOTAL: 907 and 357 exact, 13 parameter-compatible, 0 missing out of 370, full go test ./..., and git diff --check passed. |
| P3.14 canned responses API parity | internal/router/router.go, internal/handler/api/v1/canned_response_handler.go, internal/canned/service.go, internal/canned/model.go, canned handler/service tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:114, reference/chatwoot/app/controllers/api/v1/accounts/canned_responses_controller.rb, reference/chatwoot/app/models/canned_response.rb, reference/chatwoot/db/schema.rb, reference/chatwoot/app/javascript/dashboard/api/cannedResponse.js, reference/chatwoot/app/javascript/dashboard/store/modules/cannedResponse.js |
Canned responses now match the reused dashboard API: frontend PATCH /api/v1/accounts/:account_id/canned_responses/:id and no-trailing-slash index/create aliases are registered; the tracked critical route set includes Rails PATCH; create/update accept raw frontend bodies plus Rails-style { canned_response: ... }; list/search return raw arrays; search= filters and ranks by short-code prefix, short-code contains, then content contains; create/update/show return raw objects with id, account_id, short_code, content, created_at, and updated_at; update/delete are account-scoped; delete returns empty 200 OK; legacy q and /search remain only compatibility aliases. |
Review by feat(canned-responses): align chatwoot payloads; focused canned handler tests cover list/search/raw create/PATCH update/delete/cross-account 404, service tests cover account-scoped get/update/delete and ranking, route dump/parity regenerated to TOTAL: 910 and 358 exact, 13 parameter-compatible, 0 missing out of 371, full go test ./..., and git diff --check passed. |
| P3.15 notification subscriptions API parity | internal/router/router.go, internal/handler/api/v1/notification_subscription_handler.go, internal/service/notification_subscription_service.go, internal/repository/notification_subscription_repo.go, notification subscription handler tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:440, reference/chatwoot/app/controllers/api/v1/notification_subscriptions_controller.rb, reference/chatwoot/app/builders/notification_subscription_builder.rb, reference/chatwoot/app/models/notification_subscription.rb, reference/chatwoot/db/schema.rb, dashboard api/notificationSubscription.js, helper/pushHelper.js |
Notification subscriptions now match the reused dashboard push registration boundary: singular user-scoped POST/DELETE /api/v1/notification_subscriptions routes are tracked; create accepts raw frontend bodies plus Rails-style wrappers; browser push identifiers derive from endpoint; FCM identifiers derive from device_id or push_token; duplicate identifiers update/move to the current user; create serializes raw Chatwoot subscription fields with string enum values; and destroy deletes by push_token/endpoint while returning empty 200 OK for missing matches. |
Review by feat(notifications): align subscription payloads; focused notification subscription handler tests, combined handler/service/repository/router/route-parity tests, route dump/parity regeneration to TOTAL: 912 and 360 exact, 13 parameter-compatible, 0 missing out of 373, full go test ./..., and git diff --check passed. |
| P3.16 teams frontend route parity | internal/router/router.go, internal/handler/api/v1/team_handler.go, team handler/router tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:296-300, reference/chatwoot/app/controllers/api/v1/accounts/teams_controller.rb, reference/chatwoot/app/controllers/api/v1/accounts/team_members_controller.rb, reference/chatwoot/app/views/api/v1/accounts/teams/*.json.jbuilder, reference/chatwoot/app/views/api/v1/accounts/team_members/*.json.jbuilder, dashboard api/teams.js, store/modules/teams/actions.js, store/modules/teamMembers.js |
Team routes now match reused dashboard call sites: no-trailing-slash GET/POST /teams aliases are registered for CacheEnabledApiClient, frontend PATCH /teams/:team_id update is registered alongside Rails PUT, and no-trailing-slash team-member list/create/update/delete aliases are registered for TeamsAPI.getAgents/addAgents/updateAgents. Team update continues to accept raw frontend bodies and { team: ... } wrappers and returns raw Chatwoot team fields. |
Review by feat(teams): align frontend update routes; focused TeamHandler PATCH test, router/route-parity tests, route dump/parity regeneration to TOTAL: 919 and 361 exact, 13 parameter-compatible, 0 missing out of 374, full go test ./..., and git diff --check passed. |
| P3.17 conversation participants API parity | internal/router/router.go, internal/handler/api/v1/conversation_participant_handler.go, internal/service/conversation_participant_service.go, internal/repository/conversation_participant_repo.go, conversation participant handler/service tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:150, reference/chatwoot/app/controllers/api/v1/accounts/conversations/participants_controller.rb, reference/chatwoot/app/views/api/v1/accounts/conversations/participants/*.json.jbuilder, reference/chatwoot/app/views/api/v1/models/_agent.json.jbuilder, dashboard api/inbox/conversation.js |
Conversation participants now match the reused dashboard sidebar contract: singleton participant routes are tracked and registered without trailing slashes; trailing slash and legacy per-user routes remain compatibility aliases; list/create/update return raw agent arrays rendered through the Chatwoot agent serializer shape; create accepts frontend user_ids and legacy user_id; update treats user_ids as the final participant set, adding missing users and removing absent users; destroy accepts { user_ids: [...] } and returns empty 200 OK. |
Review by feat(conversations): align participant payloads; focused ConversationParticipant handler/service tests, router/route-parity tests, route dump/parity regeneration to TOTAL: 925 and 366 exact, 13 parameter-compatible, 0 missing out of 379, full go test ./..., and git diff --check passed. |
| P3.18 conversation direct uploads API parity | internal/router/router.go, internal/handler/api/v1/upload_handler.go, internal/service/upload_service.go, internal/app/bootstrap.go, upload handler tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:151, reference/chatwoot/app/controllers/api/v1/accounts/conversations/direct_uploads_controller.rb, reference/chatwoot/app/javascript/dashboard/composables/useFileUpload.js, reference/chatwoot/app/javascript/dashboard/mixins/fileUploadMixin.js |
Conversation direct uploads now match the reused dashboard composer boundary: Chatwoot's nested POST /api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads route is tracked and registered, JSON ActiveStorage blob metadata returns raw signed_id, direct_upload.url, and headers, the returned local PUT target writes bytes to account-scoped storage, direct-upload rows keep Source=account and current account scope, and conversation resolution uses display ID with legacy ID fallback. |
Review by feat(conversations): align direct upload routes; focused upload handler/service/router/route-parity tests, route dump/parity regeneration to TOTAL: 927 and 367 exact, 13 parameter-compatible, 0 missing out of 380, full go test ./..., and git diff --check passed. |
| P3.19 conversation inbox assistant API parity | internal/router/router.go, internal/handler/api/v1/conversation_handler.go, internal/service/conversation_service.go, conversation CRUD/service tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:165, reference/chatwoot/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb, reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js, reference/chatwoot/app/javascript/dashboard/store/modules/conversations/actions.js, conversation store mutation that writes copilotAssistant |
Conversation inbox assistant now matches the reused dashboard Copilot sidebar contract: GET /api/v1/accounts/:account_id/conversations/:conversation_id/inbox_assistant is tracked and registered, resolves conversations by account display ID with legacy ID fallback, joins captain_inboxes to captain_assistants with account and inbox scope, serializes only assistant id/name, and returns { assistant: null } when no assistant is bound. |
Review by feat(conversations): expose inbox assistant; focused ConversationCrud handler tests cover bound, unbound, and display-ID lookup paths; service/router/route-parity tests pass; route dump/parity regenerated to TOTAL: 928 and 368 exact, 13 parameter-compatible, 0 missing out of 381; full go test ./... and git diff --check passed. |
| P3.20 conversation draft messages API parity | internal/router/router.go, internal/handler/api/v1/draft_message_handler.go, internal/service/draft_message_service.go, internal/repository/draft_message_repo.go, draft message handler tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:152, reference/chatwoot/app/controllers/api/v1/accounts/conversations/draft_messages_controller.rb, reference/chatwoot/spec/controllers/api/v1/accounts/conversations/draft_messages_controller_spec.rb, reference/chatwoot/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue local draft call sites for composer behavior context |
Conversation draft messages now match Chatwoot's singular route contract: GET/PATCH/PUT/DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/draft_messages are tracked and registered without :draft_id, show returns raw { has_draft: false } or { has_draft: true, message }, update accepts nested { draft_message: { message } } and returns empty 200 OK, delete clears the conversation draft and returns empty 200 OK, and conversation resolution uses display ID with legacy ID fallback. Legacy local collection/id draft routes remain only compatibility aliases. |
Review by feat(conversations): align draft message route; focused DraftMessage handler tests cover raw show/no-draft, nested update, empty delete, display-ID lookup, and legacy route compatibility; service/repository/router/route-parity tests pass; route dump/parity regenerated to TOTAL: 932 and 372 exact, 13 parameter-compatible, 0 missing out of 385; full go test ./... and git diff --check passed. |
| P3.21 conversation reporting events API parity | internal/router/router.go, internal/handler/api/v1/conversation_handler.go, internal/service/conversation_service.go, internal/repository/reporting_event_repo.go, conversation handler tests, cmd/route_parity |
reference/chatwoot/config/routes.rb:166, reference/chatwoot/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb, reference/chatwoot/enterprise/app/views/api/v1/accounts/conversations/reporting_events.json.jbuilder, reference/chatwoot/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder, reference/chatwoot/app/models/reporting_event.rb |
Conversation reporting events now match Chatwoot's enterprise member route boundary: GET /api/v1/accounts/:account_id/conversations/:conversation_id/reporting_events is tracked and registered, resolves conversations by account display ID with legacy ID fallback, scopes event rows by account and conversation, orders by created_at ASC, and serializes a raw array with nullable inbox_id, user_id, and conversation_id keys preserved. |
Review by feat(conversations): expose reporting events; focused Conversation handler tests cover raw array shape, ascending order, display-ID lookup, invalid params, and cross-account non-leakage; route dump/parity regenerated to TOTAL: 933 and 373 exact, 13 parameter-compatible, 0 missing out of 386; full go test ./... and git diff --check passed. |
| P3.22 account reporting events API parity | cmd/route_parity, internal/handler/api/v1/reporting_event_handler.go, internal/service/reporting_event_service.go, internal/repository/reporting_event_repo.go, reporting event handler tests |
reference/chatwoot/config/routes.rb:234, reference/chatwoot/enterprise/app/controllers/api/v1/accounts/reporting_events_controller.rb, reference/chatwoot/enterprise/app/views/api/v1/accounts/reporting_events/index.json.jbuilder, reference/chatwoot/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder, reference/chatwoot/spec/enterprise/controllers/api/v1/accounts/reporting_events_controller_spec.rb, reference/chatwoot/app/helpers/date_range_helper.rb |
Account reporting events now match Chatwoot's enterprise index boundary: GET /api/v1/accounts/:account_id/reporting_events is tracked, returns raw { payload, meta: { count, current_page, total_pages } }, filters by optional Unix timestamp since/until, inbox_id, user_id, and name, orders by created_at DESC, uses a fixed 25-row page size, and serializes event rows with nullable relation keys preserved. |
Review by feat(reporting): align account events; focused ReportingEvent handler tests cover payload shape, ordering, combined filters, pagination, and invalid filters; route parity regenerated to 374 exact, 13 parameter-compatible, 0 missing out of 387; full go test ./... and git diff --check passed. |
| P6.8 contact outbound voice call parity | internal/router/router.go, internal/handler/api/v1/contact_handler.go, internal/service/contact_service.go, internal/model/call.go, contact handler tests |
reference/chatwoot/config/routes.rb:216, reference/chatwoot/enterprise/app/controllers/api/v1/accounts/contacts/calls_controller.rb, reference/chatwoot/enterprise/app/services/voice/outbound_call_builder.rb, reference/chatwoot/enterprise/app/services/voice/call_message_builder.rb, reference/chatwoot/enterprise/app/models/call.rb, dashboard api/contacts.js, store/modules/contacts/actions.js, api/channel/voice/voiceAPIClient.js |
Contact outbound calls now match the Chatwoot enterprise route boundary: account contact lookup, current user's assigned Channel::TwilioSms inbox lookup, voice_enabled guard, phone-number guard, open display-ID conversation reuse only for same inbox/contact, new ContactInbox/open conversation creation when needed, persisted outgoing Twilio call metadata, linked voice_call message content attributes, and raw { conversation_id, inbox_id, call_sid, conference_sid } response. |
Review by feat(contacts): initiate voice calls; focused contact call tests cover success/reuse/resolved-hint ignored/no-phone/non-voice/unassigned cases; route dump/parity regenerated to TOTAL: 861 and 299 exact, 7 parameter-compatible, 0 missing out of 306; full go test ./... and git diff --check passed. |
| P6 conversation transcript response parity | internal/handler/api/v1/conversation_handler.go, conversation handler tests |
reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb, dashboard api/inbox/conversation.js |
Account conversation transcript now follows Chatwoot's controller contract: missing email returns 422 { error: "email param missing" }, nonblank email schedules through the existing service boundary and returns empty 200 OK, and invalid-looking but nonblank email values are not rejected by local email format validation. |
Review by feat(conversations): align transcript responses; focused transcript handler/service tests, combined handler/service/router tests, full go test ./..., and git diff --check must pass. |
| P6 conversation custom attributes response parity | internal/handler/api/v1/conversation_handler.go, conversation handler tests |
reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb, app/views/api/v1/accounts/conversations/custom_attributes.json.jbuilder, dashboard api/inbox/conversation.js, conversation store action |
Account conversation custom attribute updates now return Chatwoot { custom_attributes: ... } only, matching the store action that reads response.data.custom_attributes, instead of returning the full conversation serializer with unrelated fields. Empty/null JSON serializes as {}. |
Review by feat(conversations): align custom attribute response; focused handler tests, combined handler/service/router tests, full go test ./..., and git diff --check passed. |
| P6 message retry status parity | internal/service/message_service.go, internal/handler/api/v1/message_handler.go, message service/handler tests |
reference/chatwoot/app/controllers/api/v1/accounts/conversations/messages_controller.rb, app/views/api/v1/accounts/conversations/messages/retry.json.jbuilder, dashboard api/inbox/message.js |
Account message retry now follows Chatwoot: status resets to sent, content_attributes is cleared to {}, response returns the message serializer, and the existing durable SendReplyJob path is queued when a worker pool is configured. |
Review by feat(messages): align retry status parity; focused retry service/handler tests, combined handler/service tests, full go test ./..., and git diff --check must pass. |
| P4.9 assignment policies payload/binding parity | internal/router/router.go, internal/handler/api/v1/assignment_policy_handler.go, internal/service/assignment_policy_service.go, internal/repository/assignment_policy_repo.go, internal/model/assignment_policy.go, assignment policy handler/service/repository/router tests |
reference/chatwoot/config/routes.rb:306-313, reference/chatwoot/app/controllers/api/v1/accounts/assignment_policies_controller.rb, reference/chatwoot/app/controllers/api/v1/accounts/assignment_policies/inboxes_controller.rb, reference/chatwoot/app/controllers/api/v1/accounts/inboxes/assignment_policies_controller.rb, assignment policy Jbuilder views, dashboard api/assignmentPolicies.js, store/modules/assignmentPolicies.js |
Account assignment policies now use the Chatwoot model fields and response contracts: list returns a raw array, show/create/update return raw policy objects, delete returns empty 200 OK, raw and { assignment_policy: ... } request bodies are accepted, inbox assignment policy GET/POST/DELETE returns raw policy/empty 200 OK, and policy inbox lists return { inboxes: [...] }. |
Review by feat(assignment-policies): align chatwoot payloads; focused AssignmentPolicy handler tests, service/repository/router/app/migrate/route-parity packages, escalated full go test ./..., route dump TOTAL: 906, tracked route parity 353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362, and git diff --check passed. |
| Phase 2/3 drift audit | cmd/route_parity, docs/parity/*, serializer tests |
reference/chatwoot/config/routes.rb, controller Jbuilder views, reused frontend API clients |
Convert any smoke/reference mismatch into a named route, controller, or serializer slice. Static route extraction remains acceptable until Ruby/Bundler is available. | Regenerated route parity shows 0 missing tracked frontend routes; new serializer fixtures cover the drift. |
| Phase 6 placeholder burn-down | Account/contact/conversation/message/inbox handlers and services | Matching reference controllers/Jbuilder views plus reused frontend screens | Re-run placeholder audit and assign every frontend-reachable stub to a specific owner. Burn down the highest-impact stubs before broad feature expansion. | rg placeholder audit is recorded here; no reused-frontend critical path is ownerless. |
| B12 live smoke | scripts/parity_frontend_smoke.sh, docs/parity/frontend_smoke_report.md, cmd/gochat |
Reused reference/chatwoot Vite frontend, dashboard route/API clients |
Run optional live API/browser/enterprise smoke with PostgreSQL, Redis, Meilisearch, GoChat, Vite, and Chrome. Convert failures into named rows above. | Smoke report records command, environment, pass/fail, artifacts, and linked follow-up owners. |
Checkpoint sequencing:
- Run Phase 2/3 and Phase 6 audits after each smoke failure or route/serializer change, not as a one-time cleanup.
- Keep B12 live smoke optional until the full external stack is available, but every failed live smoke must become a named row in this table.
- Open future Phase 5 work only from concrete reference or smoke evidence now that the named durable worker backlog is in Review.
Commit discipline:
- Each implementation checkpoint updates this section, the Commit Ledger, and the matching lower phase section in the same commit.
- Documentation-only commits may update this table without code, but must still run
git diff --check. - No checkpoint can move to Review while its fakeable external-provider boundary, retry behavior, and frontend payload shape are untested.
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. |
docs/parity/frontend_smoke_report.md |
Checked B12 reused frontend smoke readiness/report output. | Update after every smoke harness, browser smoke, or frontend-exposed gap change. |
docs/parity/placeholder_audit.md |
Phase 6 chatwootParityStub and placeholder classification evidence. |
Update after every placeholder audit or burn-down checkpoint. |
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 |
|---|---|---|---|
feat(integrations): align notion parity |
Advances P3.27 Notion integration destroy parity by matching Chatwoot Api::V1::Accounts::Integrations::NotionController#destroy, route 379, Notion callback hook shape, and reused dashboard integrations delete flow. GoChat now exposes no-trailing and trailing DELETE /api/v1/accounts/:account_id/integrations/notion, finds OAuth-created hooks by app_id: notion with legacy hook-type fallback, and returns empty 200 OK instead of the local { message } envelope. |
go test ./internal/service ./internal/handler/api/v1 ./cmd/route_parity -run 'NotionIntegration|RouteParity' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/router ./cmd/route_parity -run 'NotionIntegration|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 941; tracked route parity is 391 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 404. |
P3.27 moves to Review for current Notion destroy evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(contacts): align contact inbox creation |
Advances P3.23 nested contact inbox creation parity by matching Chatwoot Api::V1::Accounts::Contacts::ContactInboxesController#create, ContactInboxBuilder, HmacConcern, route 212, request specs, and the contact inbox Jbuilder partial. GoChat now accepts raw JSON/form/query params, account-scopes contact and inbox lookup, generates source IDs for API/WebWidget/Email/Sms/Whatsapp/Twilio channels, returns existing contact+inbox+source rows idempotently, persists hmac_verified on creation, and returns raw { source_id, inbox } instead of the local model. |
go test ./internal/handler/api/v1 ./internal/service ./internal/repository -run 'ContactInbox|ContactHandlerCRUD' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/route_parity -run 'ContactInbox|ContactHandlerCRUD|Router|RouteParity' -count=1; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump remains TOTAL: 933; tracked route parity remains 374 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 387. |
P3.23 moves to Review for current nested contact inbox creation evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
docs: land contact inbox parity plan |
Documentation-only checkpoint requested before continuing implementation. Confirms the clean committed baseline at 2072396 feat(reporting): align account events, lands P3.23 nested contact inbox creation as the next executable slice, and records the exact Chatwoot route/controller/builder/HMAC/Jbuilder references plus Go owner files, behavior gaps, and exit gates. |
git diff --check. No Go code or route artifacts changed. |
Start feat(contacts): align contact inbox creation, then continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from concrete reference/smoke evidence. |
feat(reporting): align account events |
Advances P3.22 account reporting-events parity by matching Chatwoot enterprise Api::V1::Accounts::ReportingEventsController#index, route 234, DateRangeHelper, request specs, and the _reporting_event Jbuilder partial. GoChat now returns { payload, meta } instead of the local success envelope, treats date filtering as optional Unix since/until, supports inbox_id, user_id, and name filters, orders by created_at DESC, paginates at Chatwoot's fixed 25 rows per page, and tracks the account route in route parity. |
go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/route_parity -run 'ReportingEvent|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 933; tracked route parity is 374 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 387. |
P3.22 moves to Review for current enterprise account reporting-events evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(conversations): expose reporting events |
Advances P3.21 conversation reporting-events parity by matching Chatwoot enterprise Api::V1::Accounts::ConversationsController#reporting_events, route 166, and the _reporting_event Jbuilder partial. GoChat now registers and tracks GET /api/v1/accounts/:account_id/conversations/:conversation_id/reporting_events, resolves conversations by display ID with legacy ID fallback, scopes reporting events by account and conversation, orders by created_at ASC, and returns the raw Chatwoot array shape with nullable relation keys preserved. |
go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/route_parity -run 'ReportingEvent|Conversation|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 933; tracked route parity is 373 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 386. |
P3.21 moves to Review for current enterprise conversation reporting-event evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(conversations): align draft message route |
Advances P3.20 conversation draft-message parity by matching Chatwoot Api::V1::Accounts::Conversations::DraftMessagesController, route 152, and its request specs. GoChat now registers and tracks the singular GET/PATCH/PUT/DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/draft_messages routes without a draft ID, resolves conversations by display ID with legacy ID fallback, models the Redis key behavior as one effective persisted draft per conversation, returns raw { has_draft, message } show payloads, accepts nested { draft_message: { message } } updates, and returns empty 200 OK for update/delete. |
go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/route_parity -run 'DraftMessage|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 932; tracked route parity is 372 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 385. |
P3.20 moves to Review for current conversation draft-message evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(conversations): expose inbox assistant |
Advances P3.19 conversation inbox assistant parity by matching Chatwoot enterprise Api::V1::Accounts::ConversationsController#inbox_assistant, route 165, and reused dashboard api/inbox/conversation.js plus conversation store Copilot assistant state. GoChat now registers and tracks GET /api/v1/accounts/:account_id/conversations/:conversation_id/inbox_assistant, resolves conversations by display ID with legacy ID fallback, account-scopes the inbox-to-Captain-assistant join through captain_inboxes and captain_assistants, returns only assistant id/name, and returns { assistant: null } for unbound inboxes. |
go test ./internal/handler/api/v1 ./internal/service ./internal/router ./cmd/route_parity -run 'ConversationCrud|Conversation|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 928; tracked route parity is 368 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 381. |
P3.19 moves to Review for current conversation Copilot assistant evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(conversations): align direct upload routes |
Advances P3.18 conversation direct upload parity by matching Chatwoot nested Conversations::DirectUploadsController, route 151, and the reused dashboard ActiveStorage upload callers. GoChat now registers and tracks POST /api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads, returns raw ActiveStorage signed_id/direct_upload metadata, validates the account-scoped conversation by display ID with legacy ID fallback, stores upload rows as account direct uploads, and exposes the local PUT URL used by the metadata response to persist bytes. |
go test ./internal/handler/api/v1 ./internal/service ./internal/router ./cmd/route_parity -run 'Upload|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 927; tracked route parity is 367 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 380. |
P3.18 moves to Review for current composer upload evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(conversations): align participant payloads |
Advances P3.17 conversation participant parity by matching Chatwoot Conversations::ParticipantsController, participant Jbuilder views, the _agent serializer, routes 150, and reused dashboard api/inbox/conversation.js calls. GoChat now registers and tracks singleton GET/POST/PATCH/PUT/DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/participants routes without trailing slashes, keeps trailing slash and per-user compatibility aliases, returns raw agent arrays instead of local { success, data } envelopes, accepts frontend user_ids, treats update user_ids as the final participant set, and returns empty 200 OK for participant destroy. |
go test ./internal/handler/api/v1 ./internal/service ./internal/router ./cmd/route_parity -run 'ConversationParticipant|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 925; tracked route parity is 366 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 379. |
P3.17 moves to Review for current conversation sidebar participant evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(teams): align frontend update routes |
Advances P3.16 team route parity by matching Chatwoot TeamsController, TeamMembersController, their Jbuilder serializers, routes 296-300, and reused dashboard api/teams.js/team store callers. GoChat now registers frontend no-trailing-slash aliases for team index/create and team-member list/create/update/delete, tracks the Rails PATCH /api/v1/accounts/:account_id/teams/:team_id route, routes frontend team updates through the existing Chatwoot-shaped update serializer, and keeps raw frontend bodies plus { team: ... } wrappers accepted. |
go test ./internal/handler/api/v1 -run TeamHandler -count=1; go test ./internal/handler/api/v1 ./internal/router ./cmd/route_parity -run 'TeamHandler|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 919; tracked route parity is 361 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 374. |
P3.16 moves to Review for current team settings route evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(notifications): align subscription payloads |
Advances P3.15 notification subscription parity by matching Chatwoot NotificationSubscriptionsController, NotificationSubscriptionBuilder, the NotificationSubscription enum model, schema fields, and reused dashboard pushHelper/notificationSubscription API. GoChat now registers the singular user-scoped DELETE /api/v1/notification_subscriptions, tracks POST/DELETE route parity, accepts raw frontend bodies and Rails-style wrappers, derives browser push identifiers from subscription_attributes.endpoint, derives FCM identifiers from device_id or push_token, updates/moves duplicate identifiers to the current user, returns raw subscription objects with string subscription_type, and makes destroy an empty 200 OK no-op for missing matches like the reference controller. |
go test ./internal/handler/api/v1 -run NotificationSubscription -count=1; go test ./internal/service ./internal/repository -run NotificationSubscription -count=1; go test ./internal/router ./cmd/route_parity -run 'Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 912; tracked route parity is 360 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 373. |
P3.15 moves to Review for current browser push registration evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
4e0113a feat(canned-responses): align chatwoot payloads |
Advances P3.14 canned response parity by matching Chatwoot CannedResponsesController, CannedResponse#order_by_search, schema fields, and reused dashboard cannedResponse API/store. GoChat now registers frontend no-trailing-slash index/create aliases plus PATCH update, accepts raw frontend bodies and Rails-style wrappers, returns raw arrays/objects with Chatwoot fields instead of local { success, data } envelopes, supports search= ranking by short-code prefix/contains/content, scopes show/update/delete by account, and returns empty 200 OK deletes while keeping legacy q and /search as compatibility aliases only. |
go test ./internal/handler/api/v1 -run CannedResponse -count=1; go test ./internal/canned -count=1; go test ./internal/handler/api/v1 ./internal/canned ./internal/router ./cmd/route_parity -run 'CannedResponse|Router|RouteParity' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 910; tracked route parity is 358 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 371. |
P3.14 moves to Review for current canned response settings evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
docs: track canned response parity |
Documentation-only checkpoint requested before continuing implementation. It records the already-clean 75f0e80 feat(dashboard-apps): align chatwoot payloads baseline and lands fresh canned-response reference/frontend drift as P3.14, with owner files, reference files, required request/response/status contracts, route artifact rules, and test gates. |
git diff --check. No Go code changed and no route artifacts changed. |
Start feat(canned-responses): align chatwoot payloads, then continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from concrete reference/smoke evidence. |
feat(dashboard-apps): align chatwoot payloads |
Advances P3.13 dashboard app parity by matching Chatwoot DashboardAppsController, dashboard app Jbuilder partials, model content schema, and reused dashboard integrations settings store/API. GoChat now tracks the dashboard app resource routes, accepts the raw frontend create/update bodies plus Rails-style wrappers, scopes show/update/delete by account, returns raw dashboard app partial payloads instead of local success/data envelopes or extended model fields, returns 200 OK for create and 204 for delete, and validates content as at least one frame widget with an HTTP(S) URL. |
go test ./internal/handler/api/v1 -run DashboardApp -count=1; go test ./internal/service -run DashboardApp -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/route_parity -run 'DashboardApp|Router|Route' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 907; tracked route parity is 357 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 370. |
P3.13 moves to Review for current dashboard integrations settings evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(integrations): align dyte meeting routes |
Advances P3.12 account Dyte integration parity by matching Chatwoot Api::V1::Accounts::Integrations::DyteController, Integrations::Dyte::ProcessorService, Dyte API wrapper behavior, and reused dashboard api/integrations/dyte.js/video-call components. GoChat now registers the account-scoped create-meeting and add-participant routes, tracks them in route parity, loads Dyte credentials from account integration hooks, creates outgoing integrations messages with Dyte meeting attributes, accepts integrations as a valid message content type, and returns participant token payloads through a fakeable Dyte backend. |
go test ./internal/service -run Dyte -count=1; go test ./internal/handler/api/v1 -run Dyte -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router ./cmd/route_parity ./internal/app -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 907; tracked route parity is 355 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 364. |
P3.12 moves to Review for current dashboard Dyte evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
feat(profile): send confirmation invitations |
Advances P3.2a invitation/confirmation mail parity by matching Chatwoot AgentBuilder, Devise confirmation instructions view/specs, and profile resend controller behavior for non-SSO flows. GoChat now has a fakeable/environment SMTP confirmation mail boundary; profile resend persists confirmation or reset-password tokens and sends normal confirmation or workspace invitation payloads; confirmed resends remain no-op; newly created invited agents send invitation mail; users.unconfirmed_email is modeled for email-update branch routing; and bootstrap wires the account agent handler plus confirmation mailers. |
go test ./internal/handler/api/v1 -run 'ProfileHandlerSuite/TestResendConfirmation|AgentHandlerSuite/TestCreateAgent' -count=1; go test ./internal/service ./internal/repository ./cmd/migrate ./internal/app -run 'Profile|Agent|App|Migrate' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router ./cmd/migrate ./internal/app -count=1; full go test ./...; git diff --check. No route artifacts changed. |
P3.2a moves to Review for current non-SSO reference evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, B12 live smoke, or fresh reference/smoke drift. |
docs: land active parity follow-up |
Documentation/tooling checkpoint requested before continuing implementation. It records the committed 682d0ab feat(profile): send confirmation invitations baseline, refreshes the Phase 6 placeholder and B12 readiness evidence, and keeps the next executable queue limited to Phase 2/3 drift audit, optional B12 live smoke, or fresh reference/smoke findings. |
bash -n scripts/parity_frontend_smoke.sh; scripts/parity_frontend_smoke.sh --check; rg -n "TODO|placeholder|chatwootParityStub|stub|not implemented|unsupported" internal/handler internal/service internal/router -g'*.go'; go run ./cmd/dump_routes; go run ./cmd/route_parity; git diff --check. No route artifacts changed; route dump remains TOTAL: 905. |
Continue Phase 2/3 drift audit first unless the full B12 live stack is available. Keep Phase 6 in Review and reopen only from fresh frontend-reachable placeholder evidence. |
docs: land invitation parity tracker |
Documentation-only checkpoint requested before continuing implementation. It records the clean 2acd938 feat(account-users): align inviter id baseline, promotes P3.2a invitation/confirmation mail parity to the top active slice, maps the inspected Chatwoot Devise/AgentBuilder references into an executable row, and keeps SSO/SAML/LDAP/OIDC mail variants excluded. |
git diff --check. No Go code changed and no route artifacts changed. |
Start feat(profile): send confirmation invitations, then continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from concrete reference/smoke evidence. |
feat(account-users): align inviter id |
Advances P3.2 invitation/account-user parity by matching Chatwoot account_users.inviter_id, AgentBuilder#create_account_user, app/views/api/v1/models/_user.json.jbuilder, and enterprise Audit::AccountUser. GoChat now maps model.AccountUser.InvitedBy to the Chatwoot inviter_id column and JSON name, emits account-user lifecycle payloads with inviter_id, adds migration 000031 to copy existing invited_by values forward, and keeps account agent serializers free of inviter fields. |
go test ./internal/model -run AccountUserDefaultValues -count=1; go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestCreateAgent' -count=1; go test ./internal/service ./internal/repository -run 'AccountUser|Agent' -count=1; go test ./cmd/migrate ./internal/app -count=1; full go test ./...; git diff --check. No route artifacts changed. |
AccountUser inviter persistence moves to Review for current reference evidence; continue remaining P3.2 invitation mail/confirmation drift, B12 live smoke, or the next evidence-backed route/controller/serializer drift. |
feat(profile): expose hmac identifier |
Advances P3.1 profile _user serializer parity by matching Chatwoot app/views/api/v1/models/_user.json.jbuilder, UserAttributeHelpers#hmac_identifier, spec/models/user_spec.rb, and reused frontend scriptHelpers.js. GoChat profile responses now omit hmac_identifier when CHATWOOT_INBOX_HMAC_KEY is not configured and include the SHA-256 HMAC of the user email keyed by that installation config when present. |
go test ./internal/handler/api/v1 -run 'ProfileHandlerSuite/TestGet' -count=1; go test ./internal/service -run Profile -count=1; full go test ./...; git diff --check. No route artifacts changed. |
Profile _user HMAC field moves to Review for current reference evidence; continue remaining P3.2 invitations drift, B12 live smoke, or the next evidence-backed route/controller/serializer drift. |
feat(platform): align user payloads |
Advances P3.2 platform user parity by matching Chatwoot Platform::Api::V1::UsersController, app/views/platform/api/v1/models/_user.json.jbuilder, users/token.json.jbuilder, and platform routes. GoChat now returns raw user/token/login payloads instead of local envelopes, creates or reuses users by email with confirmed active status, idempotently grants platform-app permission, generates/reuses personal access tokens, merges custom_attributes on update, returns empty 200 OK deletes, registers GET /platform/api/v1/users/:id/login, and removes the non-reference GET /platform/api/v1/users/:id/token route from the dump. |
go test ./internal/handler/api/v1 -run PlatformUser -count=1; go test ./internal/service -run PlatformUser -count=1; go test ./internal/router -run RegisterRoutes -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 905; tracked route parity remains 353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362. |
Platform users move to Review for current reference evidence; continue remaining P3.2 invitations drift, B12 live smoke, or the next evidence-backed route/controller/serializer drift. |
feat(agents): align serializer fields |
Advances P3.2 agents serializer parity by matching Chatwoot app/views/api/v1/models/_agent.json.jbuilder. GoChat account agent list/create/show/update payloads now return only id, account_id, availability_status, auto_offline, confirmed, email, provider, available_name, optional custom_attributes, name, role, thumbnail, and enterprise custom_role_id; local invited_by and account_user_id fields are no longer emitted. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestCreateAgent|TestAgentHandlerSuite/TestListAfterCreate|TestAgentHandlerSuite/TestGetAgent' -count=1; go test ./internal/handler/api/v1 -run AgentHandler -count=1; go test ./internal/service ./internal/repository ./internal/router -run Agent -count=1; sandboxed full go test ./... failed only on local socket/miniredis/httptest restrictions; escalated full go test ./... passed; git diff --check. |
Agent serializer field shape moves to Review for current reference evidence; continue remaining P3.2 account users/invitations drift or B12 live smoke. |
docs: record placeholder smoke audit |
Documentation/tooling checkpoint for Phase 6 and B12 readiness. Adds docs/parity/placeholder_audit.md, classifies remaining chatwootParityStub call sites as public webhook nil-handler guards only, refreshes docs/parity/frontend_smoke_report.md, and moves Phase 6 placeholder burn-down to Review for the inspected account/contact/conversation/message/inbox/widget/public critical paths. |
rg -n "chatwootParityStub" internal/handler internal/service internal/router internal/channel; `rg -n "chatwootParityStub" internal/router/router.go |
wc -l; bash -n scripts/parity_frontend_smoke.sh; scripts/parity_frontend_smoke.sh --check; scripts/parity_frontend_smoke.sh --print; git diff --check`. |
feat(assignment-policies): align chatwoot payloads |
Advances P4.9/Phase 2/3 assignment policy parity by matching Chatwoot AssignmentPoliciesController, AssignmentPolicies::InboxesController, Inboxes::AssignmentPoliciesController, Jbuilder views, frontend assignmentPolicies API/store usage, and routes.rb:306-313. GoChat now registers no-trailing-slash Chatwoot assignment policy routes, persists Chatwoot assignment policy fields, returns raw array/object payloads, accepts raw and nested request bodies, exposes assigned inbox counts, replaces inbox policy associations, lists policy inboxes as { inboxes: [...] }, and returns empty 200 OK deletes. |
go test ./internal/handler/api/v1 -run AssignmentPolicy -count=1; go test ./internal/service ./internal/repository ./internal/router ./internal/app ./cmd/migrate ./cmd/route_parity -count=1; sandboxed full go test ./... failed only on local socket restrictions, escalated full go test ./... passed; git diff --check; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity. Route dump is TOTAL: 906; tracked route parity remains 353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362. |
Assignment policies move to Review for current reference evidence; continue Phase 6 placeholder audit, B12 live smoke, or the next evidence-backed route/controller/serializer drift. |
feat(channels): expose oauth callbacks |
Advances Phase 2/3 root channel callback parity by matching Chatwoot Twitter::CallbacksController#show, Google::CallbacksController#show, Microsoft::CallbacksController#show, Instagram::CallbacksController#show, Tiktok::CallbacksController#show, and routes.rb:626/649-652. GoChat now registers /twitter/callback, /google/callback, /microsoft/callback, /instagram/callback, and /tiktok/callback; Google/Microsoft callbacks exchange OAuth codes and create/update email inboxes from ID-token mailbox identity; Instagram/TikTok/Twitter callbacks create/update channel inboxes and preserve Chatwoot success/error redirect paths for reused frontend onboarding/settings flows. |
go test ./internal/router -run 'ChannelCallbacks|IntegrationCallbacks|RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity. Route dump is TOTAL: 900; tracked route parity is 353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362. |
Root channel callbacks move to Review for current reference evidence; continue Phase 6 placeholder audit, B12 live smoke, or the next evidence-backed route/controller/serializer drift. |
feat(integrations): expose oauth callbacks |
Advances Phase 2/3 root integration callback parity by matching Chatwoot Linear::CallbacksController#show, Shopify::CallbacksController#show, Notion::CallbacksController#show, account Shopify auth behavior, and routes.rb:630/634/654. GoChat now registers /linear/callback, /shopify/callback, and /notion/callback; verifies account state, exchanges OAuth codes through configurable provider token endpoints, persists account integration hooks with Chatwoot app IDs/access tokens/settings/reference IDs, redirects to reused frontend integration settings, and returns Shopify auth redirect_url authorization payloads. |
go test ./internal/router -run 'IntegrationCallbacks|RegisterRoutes' -count=1; go test ./internal/service -run ShopifyIntegrationService -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity. Route dump is TOTAL: 895; tracked route parity is 348 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 357. |
Linear/Shopify/Notion root callbacks move to Review for current reference evidence; continue the remaining root channel callback audit for Twitter/Google/Microsoft/Instagram/TikTok, Phase 6 placeholder audit, or B12 live smoke. |
feat(twilio): expose root callback |
Advances Phase 2/3 Twilio ingress parity by matching Chatwoot Twilio::CallbackController#create, Twilio::DeliveryStatusController#create, and routes.rb:639-640. GoChat now registers the exact /twilio/callback route, tracks /twilio/delivery_status, acknowledges root callbacks with 204 No Content, resolves inbound callbacks by To or MessagingServiceSid, normalizes phone numbers to stored E.164 channel rows, and persists inbound SMS/MMS through the existing Twilio incoming-message pipeline and durable persister boundary. |
go test ./internal/handler/webhook -run 'TwilioCallback|TwilioWebhook|TwilioDeliveryStatus' -count=1; go test ./internal/router -run 'RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 892; tracked route parity is 345 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 354. |
Twilio root callback ingress moves to Review for current reference evidence; continue Phase 2/3 callback route audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(twilio): expose voice callbacks |
Advances Phase 2/3 enterprise voice parity by matching Chatwoot enterprise Twilio::VoiceController#call_twiml/#status/#conference_status/#recording_status and routes.rb:643-646. GoChat now registers /twilio/voice/call/:phone, /twilio/voice/status/:phone, /twilio/voice/conference_status/:phone, and /twilio/voice/recording_status/:phone; voice callbacks resolve the Twilio phone to a voice-enabled inbox, return conference TwiML for existing call rows, map Twilio call/conference events to persisted call state, and retain status/conference/recording callback payloads in call attributes. |
go test ./internal/router -run 'TwilioVoice|RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 891; tracked route parity is 343 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 352. |
Enterprise Twilio voice callbacks move to Review for current reference evidence; continue Phase 2/3 drift audit, callback route audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(custom-domains): expose cloudflare challenge |
Advances Phase 2/3 public custom-domain parity by matching Chatwoot enterprise CustomDomainsController#verify, Cloudflare SSL setting keys from Cloudflare::BaseCloudflareZoneService, and routes.rb:660. GoChat now registers /.well-known/cf-custom-hostname-challenge/:id, resolves the request host against portals.custom_domain, returns Domain not found for unknown domains, returns Challenge ID not found for mismatched ssl_settings.cf_verification_id, and serves the stored cf_verification_body as plain text on success. |
go test ./internal/router -run 'CustomDomain|WellKnown|RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 887; tracked route parity is 339 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 348. |
Cloudflare custom hostname verification moves to Review for current reference evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(well-known): expose app association files |
Advances Phase 2/3 public route parity by matching Chatwoot AndroidAppController#assetlinks, AppleAppController#site_association, MicrosoftController#identity_association, assetlinks.json.erb, site_association.html.erb, identity_association.json.jbuilder, and routes.rb:657-659. GoChat now registers /.well-known/assetlinks.json, /.well-known/apple-app-site-association, and /.well-known/microsoft-identity-association.json; Android payloads use ANDROID_BUNDLE_ID and ANDROID_SHA256_CERT_FINGERPRINT, Apple payloads use IOS_APP_ID plus /app/accounts/*/conversations/*, and Microsoft payloads use AZURE_APP_ID. |
go test ./internal/router -run 'WellKnown|RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 886; tracked route parity is 338 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 347. |
.well-known association routes move to Review for current reference/spec evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(dashboard): expose app shell routes |
Advances Phase 2/3 dashboard route parity by matching Chatwoot DashboardController#index, routes.rb:19-20, layouts/vueapp.html.erb, and mailer/push helper paths that resolve to /app/accounts/:account_id/conversations/:display_id. GoChat now registers /app and /app/*params, returns a Chatwoot-style HTML dashboard shell with window.chatwootConfig, window.globalConfig, and the #app mount for browser requests, and returns Chatwoot's 406 { error: "Please use API routes instead of dashboard routes for JSON requests" } for JSON dashboard requests. |
go test ./internal/router -run 'DashboardIndex|RegisterRoutes' -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 883; tracked route parity is 335 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 344. |
Dashboard deep-link routes move to Review for current mailer/push evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
docs: land full parity tracking plan |
Documentation-only checkpoint requested before continuing implementation. It confirms the worktree was already clean at efceb22, makes the Hermes search and automation/macro/CSAT task families explicit in the master tracker, and records the current implementation baseline plus next executable checkpoints in one place. |
git diff --check. No Go code changed and no route artifacts changed. |
Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from this tracker; .hermes/plans/* are source notes only. |
feat(help-center): expose public sitemap |
Advances P3.8/P6.6 public help-center parity by matching Public::Api::V1::PortalsController#sitemap, sitemap.xml.erb, and routes.rb:591. GoChat now registers /hc/:slug/sitemap.xml, resolves only non-archived portals by slug, emits sitemap XML for published portal articles only, uses custom domain URLs with Chatwoot-style https:// prefixing when no protocol is present, and writes public article links plus ISO date lastmod values. |
go test ./internal/handler/api/v1 -run PortalHandler -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 881; tracked route parity is 333 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 342. |
P3.8/P6.6l moves to Review for current public sitemap evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(help-center): expose public article show |
Advances P3.8/P6.6 public help-center parity by matching Public::Api::V1::Portals::ArticlesController#show/#show_markdown/#tracking_pixel, the public article Jbuilder payload, and routes.rb:598-601. GoChat now registers the public article route, dispatches .md and .png suffixes through the same Gin-compatible article slug route, resolves only non-archived portals by slug, returns public article payloads by slug, serves raw markdown for published articles, rejects markdown for drafts, and increments published article views when serving the 1x1 tracking pixel. |
go test ./internal/handler/api/v1 -run ArticleHandler -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 880; tracked route parity is 332 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 341. |
P3.8/P6.6k moves to Review for current public article show/markdown/tracking evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(help-center): expose public portal search |
Advances P3.8/P6.6 public help-center parity by matching Public::Api::V1::Portals::SearchController#index, the public search-article Jbuilder partial, and routes.rb:593. GoChat now registers /hc/:slug/:locale/search, resolves only non-archived portals by slug, trims blank searches to empty results, restricts public search to published articles in the requested locale, uses the Chatwoot 10-result page size, and returns search article payloads with plain-text snippets plus /hc/:slug/articles/:article_slug links. |
go test ./internal/handler/api/v1 -run ArticleHandler -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 879; tracked route parity is 331 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 338. |
P3.8/P6.6j moves to Review for current public portal search evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(help-center): expose public portal show |
Advances P3.8/P6.6 public help-center parity by matching Public::Api::V1::PortalsController#show, the public HC portal Jbuilder payload, and routes.rb:590/592. GoChat now registers /hc/:slug and /hc/:slug/:locale, redirects slug-only requests to the portal default locale, resolves only non-archived portals by slug, preloads public categories/articles, and returns Chatwoot public portal fields with public category payloads, optional logo, published article count, category count, and default locale meta. |
go test ./internal/handler/api/v1 -run PortalHandler -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 878; tracked route parity is 330 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 337. |
P3.8/P6.6i moves to Review for current public portal show evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(help-center): expose public category lists |
Advances P3.8/P6.6 public help-center parity by matching Public::Api::V1::Portals::CategoriesController#index/#show, public category Jbuilder payloads, and routes.rb:595-596. GoChat now registers /hc/:slug/:locale/categories plus .json and /hc/:slug/:locale/categories/:category_slug, resolves only non-archived portals by slug, returns Chatwoot public category arrays or raw category objects without dashboard-only fields, orders lists by position, scopes show lookup by slug plus locale, and counts only published articles in meta.articles_count. |
go test ./internal/handler/api/v1 -run CategoryHandler -count=1; go test ./internal/service -run Category -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 876; tracked route parity is 328 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 335. |
P3.8/P6.6h moves to Review for current public help-center category evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(help-center): expose public article lists |
Advances P3.8/P6.6 widget/public parity by matching the reused widget getMostReadArticles API, Public::Api::V1::Portals::ArticlesController#index, public article Jbuilder payloads, and routes.rb:594/597. GoChat now registers /hc/:slug/:locale/articles plus .json and category article-list aliases, resolves only non-archived portals by slug, returns public { payload, meta: { articles_count } } article arrays, serializes public portal/category/author/link fields, restricts public lists to published articles in the requested locale, honors widget per_page=6, and maps sort=views to view-count ordering. |
go test ./internal/handler/api/v1 -run ArticleHandler -count=1; go test ./internal/service -run Article -count=1; go test ./internal/router -run Router -count=1; go test ./cmd/route_parity -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 873; tracked route parity is 325 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 332. |
P3.8/P6.6g moves to Review for current widget/public evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(companies): align avatar form payloads |
Advances P3.5 company settings/CRM parity by matching Chatwoot enterprise CompaniesController#create/#update/#index/#search, company Jbuilder payloads, and the reused dashboard companies Pinia store/API behavior. GoChat company create/update now accepts nested multipart company[...] form bodies used when the frontend sends avatar uploads, maps uploaded company[avatar] into the serialized avatar_url field, preserves nested multipart company[custom_attributes][...] values, and makes company list/search/contact list/search use the reference fixed RESULTS_PER_PAGE = 25 instead of local per_page overrides. |
go test ./internal/handler/api/v1 -run CompanyHandler -count=1; go test ./internal/service -run Company -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 869 and tracked route parity remains 321 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 328. |
P3.5 remains Doing for any future contact/company drift from live smoke or fresh reference inspection; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(integrations): align app hook payloads |
Advances P3.11 account integrations apps/hooks parity by matching Chatwoot Integrations::AppsController, Integrations::HooksController, app/hook Jbuilder views, Integrations::App, Integrations::Hook, config/integration/apps.yml, and the reused dashboard integrations API/store/settings components. GoChat now tracks routes.rb:345-348, registers frontend no-trailing-slash app/hook routes plus hook PATCH, returns Chatwoot { payload: [...] } app index and raw app/hook objects, accepts raw frontend hook bodies plus nested { hook: ... }, persists app_id and reference_id, scopes hook show/update/delete by account, serializes boolean hook status, account/inbox hook_type, settings, inbox, app hooks, settings schemas, visible properties, and seeds the additional reference app catalog rows. |
go test ./internal/handler/api/v1 -run IntegrationHook -count=1; go test ./internal/service -run IntegrationHook -count=1; go test ./cmd/migrate -count=1; go test ./cmd/route_parity -count=1; go test ./internal/router -run Router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 869; tracked route parity is 321 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 328. |
P3.11 moves to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(webhooks): align account payloads |
Advances P3.10 account webhook API parity by matching Chatwoot WebhooksController, account webhook Jbuilder views, Webhook model validation, and the reused dashboard webhooks API/store/settings components. GoChat now tracks routes.rb:342, registers frontend PATCH /api/v1/accounts/:account_id/webhooks/:webhook_id, returns Chatwoot { payload: { webhooks } } and { payload: { webhook } } payloads, accepts nested { webhook: ... } bodies, persists name, inbox_id, url, subscriptions, and generated secret, scopes show/update/delete by account, validates HTTP/HTTPS URLs plus the reference allowed event set, returns empty 200 OK delete, and keeps delivery filtering backed by persisted subscription rows. |
go test ./internal/handler/api/v1 -run WebhookSubscription -count=1; go test ./internal/service -run WebhookSubscription -count=1; go test ./internal/router -run Router -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router ./cmd/route_parity -run 'WebhookSubscription|Router|RouteParity' -count=1; go test ./cmd/migrate -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 865; tracked route parity is 313 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 320. |
P3.10 moves to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(agent-bots): align account payloads |
Advances P3.9 account agent-bot API parity by matching Chatwoot AgentBotsController, account agent-bot Jbuilder views, _agent_bot.json.jbuilder, AgentBotPresenter, and the reused dashboard AgentBotsAPI/agentBots store path. GoChat now registers frontend no-trailing-slash GET/POST /api/v1/accounts/:account_id/agent_bots plus PATCH /agent_bots/:id, tracks routes.rb:94-97, returns raw bot arrays/objects with thumbnail, bot_config, system_bot, account-scoped access_token/secret, accepts JSON and multipart form bodies, preserves explicit empty update values, scopes mutation/reset/avatar actions to current-account bots while allowing index/show of system bots, returns empty 200 OK delete, and returns full bot payloads for reset/avatar actions. |
go test ./internal/handler/api/v1 -run AgentBot -count=1; go test ./internal/service -run AgentBot -count=1; go test ./internal/router -run Router -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -run 'AgentBot|Router' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./cmd/route_parity -count=1; go test ./...; git diff --check. Route dump is TOTAL: 864; tracked route parity is 308 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 315. |
P3.9 moves to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
feat(contacts): initiate voice calls |
Advances P6.8 contact outbound voice call parity by matching Chatwoot enterprise Contacts::CallsController#create, Voice::OutboundCallBuilder, Voice::CallMessageBuilder, Call, and the reused dashboard ContactAPI.initiateCall/voice client path. GoChat now registers POST /api/v1/accounts/:account_id/contacts/:contact_id/call, requires an assigned Channel::TwilioSms inbox with voice_enabled, requires a contact phone number, reuses only matching open display-ID conversations, creates ContactInbox/open conversation records when needed, persists Twilio outgoing call metadata and a linked voice_call message payload, and returns { conversation_id, inbox_id, call_sid, conference_sid }. |
go test ./internal/handler/api/v1 -run 'ContactHandlerCRUDTestSuite/TestInitiateCall' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/router -run 'Contact|Router' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./cmd/route_parity -count=1; go test ./...; git diff --check. Route dump is TOTAL: 861; tracked route parity is 299 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 306. |
P6.8 moves to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
docs: land parity execution tracker |
Documentation-only checkpoint that turns the Hermes-era search and automation/macro/CSAT notes, user-confirmed enterprise scope, latest help-center baseline, and fresh contact voice-call drift into one executable queue. Adds lifecycle rules, ordered next slices, explicit P6.8 contact outbound voice-call contract, and updates the active board so future work can be tracked without reopening .hermes/plans/*. |
git diff --check. No Go code changed and no route artifacts changed. |
Start P6.8 contact outbound voice-call parity, or run the next Phase 2/3 plus Phase 6 evidence audit before implementation. |
feat(help-center): queue article translations |
Advances P3.7d enterprise help-center parity by matching Chatwoot enterprise Articles::BulkActionsController#translate, Captain::Articles::TranslateJob, Captain::Llm::ArticleTranslationService, and the reused dashboard bulkTranslate flow. Bulk translate now enforces captain_tasks, portal allowed locales, optional locale-matched categories, and article selection; returns 409 { duplicate_articles } for existing translations unless force is true; enqueues low-priority durable captain:article_translate jobs; registers replay in bootstrap; and uses a wired LLM-backed article translator with Chatwoot title/content prompts and locale-to-English target language names before creating or updating draft translated articles. |
go test ./internal/service -run 'ArticleService_.*Translate|LLMArticleTranslationBackend|ArticleService_BulkActions|ArticleService_BulkUpdateStatus|ArticleService_BulkDelete' -count=1; go test ./internal/handler/api/v1 -run 'ArticleHandlerSuite/TestBulkTranslate|ArticleHandlerSuite/TestBulkActions' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/repository ./internal/app -run 'Article|Bootstrap|App' -count=1; go test ./...; git diff --check. No route changes. |
P3.7d moves to Review; continue Phase 6 placeholder burn-down or the next evidence-backed Phase 2/3 drift slice. |
feat(inboxes): align agent bot member actions |
Advances P6.5 inbox API parity by aligning GET /inboxes/:id/agent_bot and POST /inboxes/:id/set_agent_bot with Chatwoot InboxesController#agent_bot/#set_agent_bot, inboxes/agent_bot.json.jbuilder, _agent_bot.json.jbuilder, and the reused dashboard agentBots store/API path. The setter accepts Chatwoot `{ agent_bot: id |
null }bodies, keeps legacy{ agent_bot_id }compatibility, treats omitted/null params as disconnect, returns empty200 OK, destroys disconnected bindings instead of leaving inactive local rows, and updates the inbox's single binding on reassignment. The getter now returns { agent_bot: {} }when disconnected and serializes assigned bots with Chatwoot fields such asthumbnail, bot_type, bot_config, and outgoing_url`. |
go test ./internal/service -run 'InboxService_SetAgentBot' -count=1; go test ./internal/handler/api/v1 -run 'InboxHandler_Set.*AgentBot|InboxHandler_GetAgentBot|InboxSetAgentBot' -count=1; go test ./internal/handler/api/v1 ./internal/service -run 'Inbox.*AgentBot|SetAgentBot' -count=1; go test ./...; git diff --check. No route changes. |
feat(messages): align retry status parity |
Advances P6 message API parity by matching Chatwoot MessagesController#retry, messages/retry.json.jbuilder, and the reused dashboard MessageApi.retry path. Message retry now resets failed messages to sent, clears content_attributes to {}, returns the Chatwoot message serializer, and queues the existing durable message:send_reply path when a WorkerPool is configured instead of leaving local retrying state/provider failure details visible. |
go test ./internal/service -run MessageService_Retry -count=1; go test ./internal/handler/api/v1 -run 'MessageHandlerTestSuite/TestRetry' -count=1; go test ./internal/handler/api/v1 ./internal/service -run 'Message.*Retry' -count=1; go test ./...; git diff --check. |
P6.4 message retry drift moves to Review; continue Phase 6 placeholder burn-down or fresh evidence-backed Phase 2/3 drift. |
feat(conversations): align custom attribute response |
Advances P6 conversation response parity by matching Chatwoot ConversationsController#custom_attributes, custom_attributes.json.jbuilder, and the reused dashboard ConversationApi.updateCustomAttributes store action. The account conversation custom-attributes route now returns only { custom_attributes: ... }, excludes local conversation/payload/meta wrappers, and converts empty/null JSON to {} for frontend merge stability. The placeholder audit was rerun and still shows only webhook handler-not-injected fallbacks calling chatwootParityStub. |
go test ./internal/handler/api/v1 -run 'ConversationHandler.*UpdateCustomAttributes|TestConversationHandler' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -run 'UpdateCustomAttributes|ConversationHandler|ConversationService|Router' -count=1; rg -n "chatwootParityStub|TODO.*placeholder|placeholder|stub" internal/handler internal/service internal/router -g'*.go'; go test ./...; git diff --check. |
P6 conversation custom-attribute response drift moves to Review; continue Phase 6 placeholder burn-down from the remaining webhook nil-handler fallbacks or fresh reference/smoke evidence. |
docs: expand help center route parity tracking |
Audit/tooling checkpoint that expands cmd/route_parity to track the full Chatwoot help-center portal/category/article route family from reference/chatwoot/config/routes.rb:385-404, including portal member actions, nested category CRUD/reorder, nested article CRUD/reorder, and article bulk actions. Regenerated docs/parity/route_parity.md now reports 298 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 305 tracked critical routes; docs/parity/gochat_routes.txt remains TOTAL: 860. |
go run ./cmd/route_parity; go test ./cmd/route_parity -count=1; git diff --check. |
Continue Phase 6 placeholder burn-down or the next evidence-backed Phase 2/3 drift slice; keep route artifacts regenerated whenever the tracked set changes. |
feat(help-center): align article payloads |
Advances P3.7c help-center parity by matching Chatwoot ArticlesController, Articles::BulkActionsController, article Jbuilder views, and reused dashboard helpCenter/articles.js. Article index/create now expose no-trailing-slash routes; update exposes frontend PATCH; frontend bulk update-status/update-category/delete and translate routes are registered; portal IDs resolve through account-scoped slug lookup with numeric fallback; create/update accept raw frontend article bodies plus wrappers; list/search return { payload, meta }; show/create/update/edit return { payload: article } with category, author, views, Unix updated_at, meta, and associated article fields; delete/reorder/bulk actions return empty 200 OK; reorder accepts Chatwoot positions_hash; and bulk action errors return 422 { error }. |
go test ./internal/handler/api/v1 -run ArticleHandler -count=1; go test ./internal/service ./internal/repository -run Article -count=1; go test ./internal/router -run Router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; sandboxed full go test ./... failed only on local socket restrictions; escalated full go test ./... passed; git diff --check. Route dump is TOTAL: 860; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
P3.7c moves to Review; continue Phase 2/3 drift audit or Phase 6 placeholder burn-down from fresh reference/smoke evidence. |
docs: lock parity execution tracker |
Documentation-only checkpoint that makes this file the single active tracker after user confirmation. It locks direct Chatwoot frontend reuse, local reference/chatwoot as the only baseline, go test ./... first, Meilisearch as mandatory final search, and enterprise scope excluding only SSO/SAML/LDAP/OIDC. It also maps the remaining Hermes search and automation/macro/CSAT notes into the existing Phase 1/B6, B8/B9, Phase 5, B7-B12, and active P3.7 help-center rows. |
git diff --check. No Go code changed. |
Continue P3.7c help-center articles and article bulk actions; future slices update this tracker in the same commit. |
feat(help-center): align category payloads |
Advances P3.7b help-center parity by matching Chatwoot CategoriesController, category Jbuilder views, and reused dashboard helpCenter/categories.js plus category store actions. Category index/create now expose no-trailing-slash routes; update exposes frontend PATCH; portal IDs resolve through account-scoped slug lookup with numeric fallback; create/update accept raw frontend category bodies plus wrappers; list returns { payload, meta } with locale filtering; show/create/update return { payload: category } with related/parent/root category partials and meta.articles_count; delete returns empty 200 OK; and reorder accepts Chatwoot positions_hash while preserving legacy positions arrays. |
go test ./internal/handler/api/v1 -run CategoryHandler -count=1; go test ./internal/service ./internal/repository -run Category -count=1; go test ./internal/router -run Router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 853; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
P3.7b moves to Review; continue P3.7c help-center articles and article bulk actions. |
feat(help-center): align portal payloads |
Advances P3.7a help-center parity by matching Chatwoot PortalsController, portal Jbuilder views, enterprise SSL status behavior, and reused dashboard helpCenter/portals.js plus portal store actions. Portal index/create now expose no-trailing-slash routes; update exposes frontend PATCH; archive exposes Chatwoot PATCH; route IDs resolve through account-scoped slug lookup with legacy numeric fallback; create/update accept raw frontend portal bodies plus wrappers; index returns { payload, meta }; show/create/update return raw portal objects with config, meta, logo, and SSL settings; delete/logo/archive return empty 200 OK; send_instructions returns { message }; and SSL status returns { status, verification_errors } or 422 when no custom domain is configured. |
go test ./internal/handler/api/v1 -run PortalHandler -count=1; go test ./internal/service ./internal/repository -run Portal -count=1; go test ./internal/router -run Router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; full go test ./...; git diff --check. Route dump is TOTAL: 850; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
P3.7a moves to Review; continue P3.7b help-center categories, then P3.7c articles/bulk actions. |
docs: land help center parity plan |
Documentation-only handoff checkpoint after a02d11e feat(campaigns): derive chatwoot scheduling. Records the latest clean campaign baseline and turns fresh help-center reference/frontend inspection into executable P3.7a-P3.7c contracts for portals, categories, articles, and article bulk actions. |
git diff --check. No Go code changed. |
Start feat(help-center): align portal payloads, then continue categories/articles and route parity regeneration. |
feat(campaigns): derive chatwoot scheduling |
Advances P3.7 campaign behavior parity beyond response envelopes by matching Chatwoot Campaign#ensure_correct_campaign_attributes, campaign_params, and the reused SMS/WhatsApp/live-chat campaign forms. Create no longer requires frontend-absent campaign_type; SMS/Twilio/WhatsApp inboxes derive one-off campaigns, persist ISO scheduled_at, and default missing schedules to current UTC; web-widget/live-chat inboxes derive ongoing campaigns and clear schedules; update accepts frontend inbox_id, sender_id, and scheduled_at, preserves omitted descriptions, reloads nested inbox/sender data, and validates inbox/sender account scope. |
go test ./internal/handler/api/v1 -run CampaignHandler -count=1; go test ./internal/service ./internal/repository -run Campaign -count=1; full go test ./...; git diff --check. No route changes; route dump remains TOTAL: 846. |
Continue Phase 6 placeholder audit or the next named Phase 2/3 drift from reference/smoke evidence. |
feat(campaigns): align chatwoot payloads |
Advances P3.7 campaign/settings parity by matching Chatwoot CampaignsController#index/show/create/update/destroy, _campaign.json.jbuilder, and the reused dashboard campaigns API/store. Account campaigns now return raw arrays/objects instead of local success/data/meta envelopes, ignore frontend page/per_page, use display_id as the external id, resolve show/update/delete/start/stop through display ID with legacy primary-key fallback, serialize nested inbox/sender data plus parsed JSON audience, trigger_rules, and template_params, create returns 200 OK, delete returns empty 200 OK, and the frontend-used no-trailing-slash index/create plus PATCH update routes are registered. |
go test ./internal/handler/api/v1 -run CampaignHandler -count=1; go test ./internal/service ./internal/repository -run Campaign -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository ./internal/router -run 'Campaign|Router' -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 846; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
Continue Phase 6 placeholder audit or the next named Phase 2/3 drift from reference/smoke evidence. |
feat(notification-settings): align chatwoot payloads |
Advances P3.1 auth/profile settings parity by matching Chatwoot NotificationSettingsController#show/#update, show.json.jbuilder, and reused dashboard userNotificationSettings store/API. Account notification-settings show/update now return the raw object with id, user_id, account_id, all_email_flags, selected_email_flags, all_push_flags, and selected_push_flags; the local { success, data: { notification_setting } } wrapper is removed, nested update params are retained, and no-trailing-slash route aliases are registered for the frontend ApiClient URL. |
go test ./internal/handler/api/v1 -run NotificationSetting -count=1; go test ./internal/service -run NotificationSetting -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. Route dump is TOTAL: 843; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
Continue Phase 6 placeholder audit or the next named Phase 2/3 drift from reference/smoke evidence. |
feat(agents): align index pagination |
Advances P3.2 agents/settings parity by matching Chatwoot AgentsController#index, which assigns @agents = agents from Current.account.users.order_by_full_name.includes(...) without pagination. Account agent lists now ignore reused-frontend page/per_page query values, return the full account-agent array, and retain lower(users.name) ASC ordering plus account-user role, availability, auto-offline, and custom-role metadata behind the Chatwoot _agent serializer. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestList' -count=1; go test ./internal/service ./internal/repository -run Agent -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
feat(agents): align update validation errors |
Advances P3.2 agents/settings parity by matching Chatwoot AgentsController#update, User#update!, and RequestExceptionHandler#render_record_invalid. Explicit blank agent[name] updates now return 422 { message: "Name can't be blank", attributes: ["name"] } and preserve the existing user name instead of silently ignoring the invalid value with a 200 OK. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestUpdateAgent' -count=1; go test ./internal/service -run Agent -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
feat(agents): align delete scope |
Advances P3.2 agents/settings parity by matching Chatwoot AgentsController#fetch_agent before destroy: deletes are scoped through current account membership and return 404 when the target user is not an agent in the account. The local delete path now looks up the AccountUser before deletion so cross-account/non-member users are not accidentally deleted. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestDeleteAgent' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
feat(agents): align duplicate create errors |
Advances P3.2 agents/settings parity by matching Chatwoot's ActiveRecord::RecordInvalid handling for duplicate account memberships created through AgentsController#create. Re-inviting an existing account member now returns 422 { message: "User has already been taken", attributes: ["user_id"] } instead of the local 409 conflict envelope, while bulk invite continues to skip duplicate failures like the reference controller. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestCreateAgentDuplicate' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
feat(agents): align create name defaults |
Advances P3.2 agents/settings parity by matching Chatwoot AgentsController#create and AgentBuilder. Agent creation now accepts blank or omitted agent[name]; for newly created users, the name falls back to the email local-part exactly like email.split('@').first, while existing-user invitation behavior remains unchanged. |
go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestCreateAgent' -count=1; go test ./internal/service ./internal/repository -run Agent -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
feat(attachments): align list pagination |
Advances Phase 6 dashboard/CRM attachment drift by matching Chatwoot ConversationsController#attachments, Contacts::AttachmentsController#index, and the shared attachment Jbuilder payloads. Conversation and contact attachment lists now page with the reference fixed 100-row limit from ATTACHMENT_RESULTS_PER_PAGE/RESULTS_PER_PAGE and ignore reused-frontend per_page query values, while retaining newest-first attachment ordering and { meta: { total_count }, payload } responses. |
`go test ./internal/handler/api/v1 -run 'Test(ConversationHandlerTestSuite | ContactHandlerCRUDTestSuite)/TestListAttachments' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840`. |
feat(profile): align account permissions |
Advances P3.1/P3.2 profile and permission parity by matching Chatwoot api/v1/models/_user.json.jbuilder, AccountUser#permissions, and the enterprise custom-role prepend used by dashboard route guards. Profile account entries now expose ordinary role permissions as ["administrator"] or ["agent"], and custom-role memberships expose the role's permission keys plus custom_role while retaining nested custom_role metadata. |
go test ./internal/handler/api/v1 -run ProfileHandler -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 840. |
Continue P3.2 permissions/invitations drift or the next Phase 6 reference/smoke finding. |
feat(platform): align account user membership |
Advances P3.2 platform account-user parity by matching Platform::Api::V1::AccountUsersController. Platform account-user index/create now return raw AccountUser JSON instead of the local { success, data } envelope, create uses Chatwoot find_or_initialize_by(user_id).update! semantics so duplicate membership requests update role instead of failing, and the Chatwoot collection route DELETE /platform/api/v1/accounts/:account_id/account_users/destroy?user_id=... is registered while the legacy /:user_id alias remains. |
go test ./internal/handler/api/v1 -run PlatformAccountUser -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./internal/handler/api/v1 ./internal/repository ./internal/router -count=1; go test ./...; git diff --check. Route dump is TOTAL: 840; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
Continue P3.2 permissions/invitations drift or the next Phase 6 reference/smoke finding. |
feat(agents): align list ordering |
Advances P3.2 agents/settings parity by matching Chatwoot AgentsController#index, which renders Current.account.users.order_by_full_name. Account agent lists now order by lower(users.name) ASC before pagination while preserving account-user role, availability, auto-offline, and custom-role data behind the Chatwoot _agent serializer. |
go test ./internal/handler/api/v1 -run AgentHandler -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 839. |
Continue P3.2 account users/invitations/permissions drift or the next Phase 6 reference/smoke finding. |
feat(agents): align update route semantics |
Advances P3.2 agents/settings parity by aligning update with Chatwoot AgentsController#update and the reused dashboard ApiClient.update path. Agent update now exposes PATCH /api/v1/accounts/:account_id/agents/:agent_id alongside the existing PUT alias, accepts nested { agent: ... } payloads, preserves auto_offline when the field is omitted, and still applies explicit auto_offline: false like Rails strong params with compact. |
go test ./internal/handler/api/v1 -run AgentHandler -count=1; go test ./internal/service -run Agent -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. Route dump is TOTAL: 839; tracked route parity remains 270 exact, 7 parameter-compatible, 0 missing. |
Continue P3.2 account users/invitations/permissions drift or the next Phase 6 reference/smoke finding. |
feat(agents): align bulk invite behavior |
Advances P3.2 agents/onboarding parity by aligning POST /api/v1/accounts/:account_id/agents/bulk_create with Chatwoot AgentsController#bulk_create. Bulk invite now treats the email list as a best-effort loop: invalid entries are skipped instead of failing the whole request, duplicate failures remain ignored, empty arrays return empty 200 OK, valid agents in the same request are still created, and the local onboarding step is cleared after the bulk flow. |
go test ./internal/handler/api/v1 -run AgentHandler -count=1; go test ./internal/service -run Agent -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
Continue P3.2 account users/invitations/permissions drift or the next Phase 6 reference/smoke finding. |
feat(custom-roles): align update route semantics |
Advances B10.3 CustomRole frontend parity by registering Chatwoot's Rails resource PATCH /api/v1/accounts/:account_id/custom_roles/:id update route used by the reused ApiClient.update path, while keeping the existing PUT alias. Custom role updates now distinguish omitted description from an explicit empty string, so the enterprise settings form can clear descriptions like CustomRolesController#update with strong params. |
go test ./internal/handler/api/v1 -run CustomRole -count=1; go test ./internal/service -run CustomRole -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
Continue the next named Phase 2/3 or Phase 6 drift from reference/smoke evidence. |
feat(contacts): align active pagination payloads |
Advances Phase 6 dashboard CRM drift by aligning contact list/search/filter/active pagination with Chatwoot ContactsController::RESULTS_PER_PAGE = 15 and contacts/active.json.jbuilder. The reused dashboard active-contacts route now returns { meta: { count, current_page }, payload } using the contact serializer instead of the local { contacts, meta: { page, page_size } } envelope, and selected-key POST /contacts/:id/destroy_custom_attributes is covered against Chatwoot's { payload } response with contact_inboxes. |
go test ./internal/handler/api/v1 -run 'ContactHandlerCRUD' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
Continue the next named Phase 2/3 or Phase 6 drift from reference/smoke evidence. |
feat(conversations): align custom attribute response |
Advances Phase 6 dashboard conversation drift by aligning POST /api/v1/accounts/:account_id/conversations/:conversation_id/custom_attributes with Chatwoot custom_attributes.json.jbuilder and the reused conversation store action. The endpoint still persists the submitted custom attributes, but the response is now only { custom_attributes: ... } and no longer leaks the full local conversation serializer, id, or local envelope fields. |
go test ./internal/handler/api/v1 -run 'TestConversationHandlerTestSuite/TestUpdateCustomAttributes' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
Continue the next named Phase 2/3 or Phase 6 drift from reference/smoke evidence. |
feat(conversations): align transcript responses |
Advances Phase 6 dashboard conversation drift by aligning POST /api/v1/accounts/:account_id/conversations/:conversation_id/transcript with Chatwoot ConversationsController#transcript and the reused dashboard conversation API. Transcript success now returns empty 200 OK instead of the local { success, data } envelope; missing email returns Chatwoot 422 { error: "email param missing" }; local email-format validation no longer rejects nonblank values that Chatwoot would pass through to the mailer. Fresh placeholder audit found only webhook handler nil-fallback chatwootParityStub routes, with no account/contact/conversation/message/inbox dashboard-critical stub owner left by this slice. |
go test ./internal/handler/api/v1 -run 'TestConversationHandlerTestSuite/TestTranscript' -count=1; go test ./internal/service -run ConversationService_SendTranscript -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
Continue the next named Phase 2/3 or Phase 6 drift from reference/smoke evidence. |
feat(custom-fields): align chatwoot saved view payloads |
Advances P3.6 serializer/controller parity for saved views and settings attributes. Account /custom_filters now matches Chatwoot CustomFiltersController, _custom_filter.json.jbuilder, and dashboard customViews.js/store behavior: list returns a raw array, defaults to filter_type=conversation, scopes by current user, accepts raw frontend bodies plus nested { custom_filter }, returns raw mutation payloads with no account_id/user_id, and uses 200 OK for create/update plus 204 delete. Account /custom_attribute_definitions now matches CustomAttributeDefinitionsController, _custom_attribute_definition.json.jbuilder, dashboard attributes.js/store, and settings attribute enum constants: list/show/create/update return raw arrays/objects, create/update accept raw integer enum bodies plus wrappers, stored/serialized attribute_model values normalize to Chatwoot conversation_attribute/contact_attribute/company_attribute, and custom attribute value validation can find normalized and legacy definition rows. |
go test ./internal/handler/api/v1 -run 'CustomFilter|CustomAttributeDefinition' -count=1; go test ./internal/service -run 'CustomFilter|CustomAttributeDefinition' -count=1; go test ./internal/repository -run 'CustomFilter|CustomAttributeDefinition' -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
P3.6 custom filters/custom attribute definitions move to Review; continue Phase 6 placeholder audit from fresh reference/smoke evidence. |
feat(labels): align chatwoot label payloads |
Advances P3.6 label CRUD parity by aligning account /labels with Chatwoot LabelsController, label Jbuilder views, Label model/schema, and dashboard labels API/store. List now returns { payload }; create/show/update return raw label payloads with id, title, description, color, and show_on_sidebar; create/update accept nested { label } bodies and legacy raw name; delete returns empty 200 OK; title normalization, default color, account scope, persisted description/sidebar flags, and local conversation/contact association cleanup are covered. |
go test ./internal/handler/api/v1 -run LabelHandler -count=1; `go test ./internal/service -run 'TagService |
LabelService' -count=1; go test ./internal/repository -run TagRepo -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./cmd/migrate -count=1; go test ./...; git diff --check`. |
docs: refresh parity tracker baseline |
Documentation-only checkpoint after 2ab7b58 feat(accounts): align account payloads. Refreshes the Current Baseline to the committed account payload work, records the clean handoff state, and lands the next executable P3.6 label CRUD parity contract from direct Chatwoot reference/frontend inspection. |
git diff --check. No Go code changed. |
Start feat(labels): align chatwoot label payloads, then continue Phase 6 placeholder audit. |
feat(accounts): align account payloads |
Advances Phase 2/3 account settings parity by aligning core reused-frontend account endpoints with Chatwoot AccountsController, _account.json.jbuilder, show/update/create views, dashboard account.js, and the account Vuex store. Account show/update/settings now return raw account objects instead of local { success, data } envelopes, with settings, custom_attributes, features, cache_keys, support_email, created_at, locale/name/domain/status fields. Authenticated account creation accepts Chatwoot account_name and returns { data: { account_id } } for the add-account modal. update_active_at now returns empty 200 OK, and cache_keys now returns Chatwoot { cache_keys: { label, inbox, team } } with zero-epoch defaults. |
go test ./internal/handler/api/v1 -run AccountHandler -count=1; go test ./internal/service -run AccountService -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
P3.2 remains Doing for account users/invitations/permissions drift; continue Phase 6 placeholder audit from fresh reference/smoke evidence. |
feat(settings): align assignable agents payloads |
Advances P3.2/P3.3 settings and inbox assignment parity by aligning GET /api/v1/accounts/:account_id/assignable_agents and GET /api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents with Chatwoot AssignableAgentsController#index, inboxes#assignable_agents, and _agent.json.jbuilder. Assignable-agent endpoints now return { payload: [...] } instead of the local { success, data } envelope and serialize agent fields required by the reused conversation assignment UI: account_id, availability_status, auto_offline, confirmed, provider, available_name, thumbnail, role, and enterprise custom_role_id. AccountUser role, availability, auto-offline, and custom-role data are loaded from the account membership instead of the global user role. |
go test ./internal/handler/api/v1 -run AssignableAgent -count=1; go test ./internal/service -run AssignableAgent -count=1; go test ./internal/handler/api/v1 ./internal/service ./internal/router -count=1; go test ./...; git diff --check. |
P3.2/P3.3 remain Doing for deeper account users/invitations/permissions and inbox serializer drift; continue Phase 6 placeholder audit from fresh reference/smoke evidence. |
feat(settings): align agents teams payloads |
Advances P3.2 account/users/teams parity by aligning settings agents, teams, and team-member endpoints with Chatwoot agents_controller, teams_controller, team_members_controller, and the reused frontend stores/API clients. Agent list/create/update/get now return raw Chatwoot agent objects/arrays with availability_status, available_name, confirmed, thumbnail, auto_offline, and custom_role_id; create/update accept the raw frontend body as well as wrapped params; bulk create and delete return Chatwoot head :ok. Team list/show/create/update now return raw team objects/arrays with allow_auto_assign and is_member, accept raw allow_auto_assign, preserve explicit false values despite DB defaults, and team-member list/create/update return agent serializer arrays. The handler also accepts router :team_id params used by the real account-scoped routes. |
go test ./internal/handler/api/v1 -run 'AgentHandler|TeamHandler' -count=1; go test ./internal/service -run 'AgentService|TeamService' -count=1; go test ./internal/repository ./internal/handler/api/v1 ./internal/router -count=1; go test ./...; git diff --check. |
P3.2 moves to Doing; continue accounts/users/invitations/permissions drift, then Phase 6 placeholder audit from fresh reference/smoke evidence. |
feat(crm): align shared attachment payloads |
Advances Phase 3/6 CRM serializer parity by adding Chatwoot's GET /api/v1/accounts/:account_id/contacts/:contact_id/attachments shared-file route and aligning conversation/contact attachment responses with contacts/attachments#index, conversations#attachments, and _attachment.json.jbuilder. Attachment lists now return { meta: { total_count }, payload }, order newest first, serialize data_url, thumb_url, file_size, file_type, extension, dimensions, created_at, sender data, and contact attachment conversation_id as the Chatwoot display ID. |
go test ./internal/service -run 'MessageService|ContactService' -count=1; go test ./internal/handler/api/v1 -run 'ContactHandlerCRUD|ConversationHandler' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/router -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check. |
P3.5 remains Doing for broader contact/company drift; continue Phase 6 placeholder audit and CRM serializer fixture expansion from reference/smoke evidence. |
feat(auth): align password reset flows |
Advances Phase 2/3 auth/session parity by replacing the password reset and confirmation placeholders with Chatwoot Devise-compatible token columns and frontend routes. POST /auth/password now returns Chatwoot's reset request message while storing a reset token digest; PUT /auth/password validates reset_password_token, updates and confirms the user password, clears reset/confirmation tokens, emits Devise auth headers, and returns { data: profile }; POST /auth/confirmation confirms by confirmation_token, clears it, emits auth headers, and returns { data: profile }. |
go test ./internal/service -run AuthService -count=1; go test ./internal/handler/api/v1 -run ChatwootAuth -count=1; go test ./...; git diff --check. |
Auth/session/profile remains Done for current reused frontend flow; future auth drift should come from B12 smoke or reference evidence. |
feat(inboxes): queue template sync jobs |
Advances P6.5 inbox API parity by aligning POST /inboxes/:id/sync_templates with Api::V1::Accounts::Concerns::WhatsappHealthManagement#sync_templates. The endpoint no longer blocks on live provider template fetches or returns a local templates envelope; it validates WhatsApp inboxes, queues durable low-priority inbox:sync_templates jobs, replays the existing WhatsApp template fetch/update path in the worker, and returns Chatwoot { message: "Template sync initiated successfully" }. |
go test ./internal/service -run 'InboxService.*SyncTemplates|InboxService.*Health|InboxService.*RegisterWebhook' -count=1; go test ./internal/handler/api/v1 -run InboxSyncTemplates -count=1; go test ./internal/app -count=1; go test ./...; git diff --check. |
P6.5 remains Doing for broader inbox/channel settings parity; continue Phase 6 placeholder audit across account/contact/conversation/message/inbox surfaces. |
feat(inboxes): align whatsapp health endpoints |
Advances P6.5 inbox API parity by replacing the generic inbox health result with Chatwoot's WhatsApp Cloud-only health contract and aligning register_webhook with the reference concern. Health now returns raw Graph phone-number fields plus expected_webhook_url and business_id; non-WhatsApp/non-Cloud inboxes return the Chatwoot 400 error; register webhook accepts an empty body, builds the default callback from FRONTEND_URL, and returns Webhook registered successfully. WhatsApp Graph health reads now use WHATSAPP_API_VERSION default v22.0. |
go test ./internal/channel/whatsapp -count=1; go test ./internal/service -run InboxService -count=1; go test ./internal/handler/api/v1 -run Inbox -count=1; go test ./...; git diff --check. |
P6.5 remains Doing for broader inbox/channel settings parity; continue Phase 6 placeholder audit across account/contact/conversation/message/inbox surfaces. |
feat(notifications): align chatwoot notification payloads |
Advances Phase 2/3 drift by aligning the account notifications API with reference/chatwoot/app/controllers/api/v1/accounts/notifications_controller.rb, NotificationFinder, index.json.jbuilder, and the reused frontend notifications.js client. Notification index now returns Chatwoot { data: { meta, payload } }, supports includes[]=read/snoozed, fixed unread/count metadata, raw notification action responses, primary-actor-scoped read_all, raw unread count, and destroy_all read/all semantics from query or JSON body. |
go test ./internal/handler/api/v1 -run Notification -count=1; go test ./internal/service -run Notification -count=1; go test ./internal/handler/api/v1 ./internal/service -count=1; go test ./...; git diff --check. |
Notification serializer/action drift moves to Review; continue Phase 6 placeholder audit, then further Phase 2/3 drift from smoke/reference evidence. |
feat(automation): close delayed action parity |
Closes B9.3 for the current reference. reference/chatwoot automation models, action service, enterprise action service, listener, and dashboard action components expose no configurable delayed action params, so no separate delayed automation scheduler is required beyond P5.12 scheduled items. The checkpoint completes remaining automation action side effects: send_email_to_team now queues durable automation:team_email_delivery jobs and replays through the fakeable mailer boundary, while enterprise add_sla attaches an account-scoped SLA policy and creates idempotent AppliedSLA targets. The automation serializer now preserves Chatwoot's object-shaped send_email_to_team params. |
go test ./internal/automation -run 'ActionService|AutomationRuleService_MatchAndExecute|ExecutionLogService' -count=1; go test ./internal/handler/api/v1 -run AutomationRule -count=1; go test ./internal/automation ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check. |
B9.3 and Phase 5 move to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, and optional B12 live smoke. |
feat(reports): add analytics timeseries rollups |
Completes P5.13b for scheduled/cached analytics parity. GET /api/v1/accounts/:account_id/reports and v2 /reports now route to metric timeseries instead of summary, with support for account/inbox/agent/team/label dimensions, day/hour/week/month/year buckets, conversation/message/reporting-event metrics, and business-hours averages. Analytics summary/dimension/traffic reads call EnsureRollupsForRange for lazy freshness; reporting:rollup_day jobs provide durable day recompute; rollup replacement uses Unscoped delete so soft-deleted rows cannot violate uniqueness on recompute. |
go test ./internal/service -run 'Analytics' -count=1; go test ./internal/handler/api/v1 -run 'Analytics|LiveReport' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/router ./internal/worker ./internal/app -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check; full verification recorded in the P5.13 section. |
P5.13 moves to Review; B9.3 is completed by feat(automation): close delayed action parity; continue Phase 2/3 drift and Phase 6 placeholder audits. |
feat(reports): derive analytics aggregates |
Advances P5.13a by replacing frontend-visible analytics placeholder responses with persisted aggregations. Live report conversation metrics now count open/unattended/unassigned/pending conversations with team filtering; grouped live reports return assignee/team grouped counts; bot summary/metrics, conversation summary, inbox-label matrix, first-response distribution, and outgoing-message counts are derived from conversations, messages, labels, agent-bot bindings, and reporting events instead of fixed zero/empty JSON. | go test ./internal/service -run 'Analytics' -count=1; go test ./internal/handler/api/v1 -run 'Analytics|LiveReport' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.13 section. |
P5.13a moves to Review; continue P5.13b scheduled/cached rollup freshness and /reports timeseries index parity, then B9.3 delayed automation action check. |
feat(captain): queue copilot response jobs |
Completes P5.11c with durable Chatwoot Captain::Copilot::ResponseJob and Captain::Conversation::ResponseBuilderJob equivalents. Copilot thread/message creation now persists the user message and enqueues captain:copilot_response when a WorkerPool is configured, while worker replay reloads the account/user/thread/message scope and persists assistant replies through a fakeable backend or the existing no-provider fallback. Incoming pending conversation messages for Captain-enabled inboxes now enqueue captain:conversation_response_builder; replay collects public incoming/outgoing history, creates Captain outgoing replies, enqueues provider send-reply, and opens the conversation with a handoff message when the backend requests handoff. |
go test ./internal/service -run 'CopilotResponse|CaptainConversation|MessageService' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.11 section. |
P5.11 moves to Review; continue P5.13 analytics aggregation, then Phase 2/3 drift and Phase 6 placeholder audits as smoke exposes gaps. |
feat(captain): queue response embedding jobs |
Advances P5.11b with durable Captain document response building and embedding update fan-out. Successful document sync/parser content updates enqueue captain:document_response_builder, worker replay resets only unedited document responses, preserves edited responses, creates approved Captain::Document assistant responses from a fakeable FAQ backend, and enqueues captain:llm_update_embedding jobs for created responses. Embedding replay reloads account-scoped responses, uses a fakeable embedding backend or configured LLM provider, and surfaces missing provider config as retryable worker failures. |
go test ./internal/service -run 'CaptainDocumentService|EnqueueCaptainDocumentScheduleSyncs' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.11 section. |
P5.11b moves to Review; continue P5.11c Copilot/conversation response jobs, then P5.13 analytics aggregation. |
feat(captain): queue document crawl jobs |
Advances P5.11a with durable Captain document crawl, simple-page parser, and auto-sync scheduler jobs. Document create/crawl requests enqueue captain:document_crawl, crawl replay uses a fakeable crawl backend to enqueue normalized captain:document_page_crawl_parse jobs, parser replay creates or updates account-scoped document content through a fakeable parser backend, and the captain:documents_schedule_syncs root job scans stale synced/failed/syncing documents for accounts with captain_document_auto_sync enabled before enqueueing captain:document_sync work with daily idempotency. Bootstrap seeds the scheduler after Captain document services are wired. |
go test ./internal/service -run 'CaptainDocumentService|EnqueueCaptainDocumentScheduleSyncs' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.11 section. |
P5.11a moves to Review; continue P5.11b response/embedding fan-out, then P5.11c Copilot/conversation response jobs. |
docs: land parity execution tracker |
Documentation-only checkpoint requested before continuing implementation. Updates the current baseline to d756473, front-loads executable open checkpoint contracts for P5.11 Captain/Copilot jobs, P5.13 analytics aggregation, Phase 2/3 drift, Phase 6 placeholder burn-down, and B12 live smoke, and records commit discipline for future parity slices. |
git diff --check; documentation-only change. |
Start P5.11a Captain document crawl/schedule jobs, then P5.11b response/embedding fan-out and P5.11c Copilot/conversation response jobs. |
feat(webhook): queue incoming messages |
Completes P5.9 normalized inbound persistence deferral. IncomingPersister.PersistIncoming now validates provider-parsed messages and, when a WorkerPool is configured, enqueues webhook:incoming_message_persist instead of creating contacts/conversations/messages inline. Worker replay reloads the inbox, performs the existing Chatwoot-style contact inbox/conversation/message persistence, keeps source_id idempotency, and dispatches the same conversation/message events from the durable path. Telegram, LINE, Facebook/Instagram, WhatsApp, TikTok, and Twilio webhook handlers are wired to the WorkerPool while no-worker construction remains synchronous for focused tests. |
go test ./internal/handler/webhook -run 'IncomingPersister.*Incoming|IncomingPersister.*Status|StatusJob|TwilioInboundSMS|TwilioDeliveryStatus' -count=1; go test ./internal/handler/webhook ./internal/channel/whatsapp ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.9 section. |
Move P5.9 to Review; next active slice is remaining P5.11 Captain/Copilot jobs, then P5.13 analytics aggregation. |
feat(messages): queue delivery statuses |
Completes P5.10 provider delivery-status depth. IncomingPersister can now enqueue direct message status updates as webhook:message_status_update jobs and contact-scoped read receipts as webhook:contact_messages_status_update jobs on the low queue; worker replay reloads the inbox, applies sent/delivered/read/failed transitions without downgrading already-read messages, upserts delivery_statuses, dispatches status events, and records provider failure details in content_attributes.external_error. Twilio delivery callbacks now return Chatwoot-style 204, include the exact /twilio/delivery_status route plus the legacy /webhooks/twilio/delivery_status alias, resolve channels by MessagingServiceSid or AccountSid/From, and enqueue through the same durable path when the WorkerPool is wired. |
go test ./internal/handler/webhook -run 'IncomingPersister.*Status|StatusJob|TwilioDeliveryStatus' -count=1; go test ./internal/handler/webhook ./internal/service ./internal/worker ./internal/app -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; git diff --check; full verification recorded in the P5.10 section. |
P5.10 moved to Review; inbound persistence deferral is completed by feat(webhook): queue incoming messages. |
feat(messages): queue send replies |
Advances P5.10 with a durable Chatwoot SendReplyJob equivalent for outbound message delivery. MessageService.Create now enqueues message:send_reply on the high queue when a WorkerPool is configured, keeps the legacy message.outgoing dispatch fallback only for no-worker construction, and worker replay reloads message/conversation/inbox/contact, skips already-sent/private/voice-call/non-outgoing messages, calls the registered channel provider, persists source_id, and records retryable provider failures as failed with content_attributes.external_error. |
go test ./internal/service -run 'MessageDeliveryWorker|MessageService' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.10 section. |
Send-reply core moved to Review; provider delivery-status depth is completed by feat(messages): queue delivery statuses. |
docs: land active parity tracking board |
Documentation-only checkpoint that reconciles the current state after cda7d02. Adds the front-loaded Active Remaining Work Board, makes Phase 5 the active slice, records P5.10/P5.9/P5.11/P5.13 as the next implementation order, and updates stale B12/M6/M7 tracker rows to Review/Doing. |
git diff --check; documentation-only change. |
Start P5.10 outbound message delivery from the new active board. |
feat(conversations): queue bulk actions |
Advances P5.12 with a durable Chatwoot BulkActionsJob equivalent for account conversation bulk actions. POST /bulk_actions now accepts frontend Chatwoot payloads without action_name, including fields, nested labels.add/remove, snoozed_until, and display-ID ids; when a WorkerPool is configured it enqueues conversation:bulk_action on the medium queue and returns empty 200 OK. Worker replay scopes by account and display IDs, merges labels, applies status/team/assignee/snooze updates, and leaves same-display conversations in other accounts untouched. |
go test ./internal/service -run 'ConversationMaintenanceJobs.*BulkAction|ConversationMaintenanceJobs' -count=1; go test ./internal/handler/api/v1 -run 'BulkActionHandler' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.12 section. |
P5.12 account bulk actions moved to Review; continue remaining Captain/Copilot jobs and analytics aggregation. |
feat(conversations): queue message status updates |
Advances P5.12 with a durable Chatwoot Conversations::UpdateMessageStatusJob equivalent. Widget and public inbox update_last_seen paths now enqueue conversation:update_message_status on the deferred queue when a WorkerPool is configured. Worker replay validates read/delivered, skips missing conversations, and updates only non-incoming messages created at or before the viewing timestamp while preserving already-read and post-cutoff messages. |
go test ./internal/service -run 'ConversationMaintenanceJobs|WidgetService_UpdateLastSeenQueuesMessageStatusJob' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.12 section. |
P5.12 update-message-status depth moved to Review; continue remaining Captain/Copilot jobs and analytics aggregation. |
feat(captain): queue document syncs |
Advances P5.11 with a durable Chatwoot Captain::Documents::PerformSyncJob equivalent. POST /captain/documents/:id/sync still returns 202 after marking the account-scoped document as syncing, but now enqueues captain:document_sync on the low queue when a WorkerPool is configured. Worker replay calls the existing fakeable CaptainDocumentSyncBackend, persists synced/failed/disabled states through SyncDocumentByAccount, and lets missing documents or malformed payloads retry through background_jobs. |
go test ./internal/service -run 'CaptainDocumentService' -count=1; go test ./internal/handler/api/v1 -run 'CaptainDocument|CaptainResource' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.11 section. |
P5.11 document sync core moved to Review; continue Captain crawl/response-building/embedding/Copilot response jobs and analytics aggregation. |
feat(csat): queue channel templates |
Advances P5.6 with durable WhatsApp/Twilio CSAT template creation. POST /inboxes/:inbox_id/csat_template accepts Chatwoot nested template params, persists the singular template as pending, and enqueues csat:template_create when a WorkerPool is configured. Worker replay calls a fakeable provider boundary, records WhatsApp template metadata or Twilio content/approval SIDs into inbox.csat_config, updates template status, and retries provider failures through background_jobs. |
go test ./internal/service -run 'CsatTemplate' -count=1; go test ./internal/handler/api/v1 -run 'InboxCsatTemplate' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.6 section. |
P5.6 channel templates moved to Review; continue Captain/Copilot jobs and analytics aggregation. |
feat(conversations): queue maintenance jobs |
Advances P5.12 and the scheduled-items portion of P5.5. scheduled:trigger_items now mirrors Chatwoot TriggerScheduledItemsJob by queueing due one-off campaigns, snoozed conversation reopening, account auto-resolution scheduling, and the next hourly root trigger. One-off campaigns claim active rows as processing before execution and complete after artifact creation, so duplicate worker replays do not duplicate campaign messages. |
go test ./internal/service -run 'ConversationMaintenanceJobs' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.12 section. |
P5.12 conversation maintenance core moved to Review; continue Captain/Copilot jobs and analytics aggregation. |
feat(crm): queue contact exports |
Advances P5.8 with a durable Chatwoot Account::ContactsExportJob equivalent. Contact export requests now create a pending contact_exports artifact and enqueue contact:export on the low queue when a WorkerPool is configured; worker replay generates the CSV artifact, marks completion, creates the persisted notification, and invokes the fakeable completion mailer. No-worker construction keeps the synchronous fallback for focused tests. |
go test ./internal/service -run 'ContactService_ExportContacts|ContactExportJob' -count=1; go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.8 section. |
Move P5.8 to Review; continue delayed automation scheduled items, CSAT channel templates, Captain/Copilot jobs, provider delivery, and conversation maintenance. |
feat(sla): queue applied sla scans |
Advances P5.7 with the durable Chatwoot enterprise SLA chain. sla:trigger_accounts scans accounts with SLA policies, queues sla:process_account jobs, self-schedules the next five-minute scan, and sla:process_account queues sla:process_applied jobs for active/active_with_misses AppliedSLA rows; applied jobs call the existing idempotent AppliedSlaService.Evaluate. Bootstrap registers the handlers and seeds the first root scan. |
go test ./internal/service -run 'SlaProcessingJobs|AppliedSlaService' -count=1; go test ./internal/service ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.7 section. |
Move P5.7 to Review; continue delayed automation scheduled items, CSAT channel templates, Captain/Copilot jobs, provider delivery, and conversation maintenance. |
feat(search): queue index updates durably |
Advances P5.3 with a durable SearchIndexer wrapper. Service-layer create/update/delete hooks now enqueue search:index jobs for conversations, messages, contacts, companies, and articles when a WorkerPool is configured; worker replay reloads the current account-scoped record before calling the real Meilisearch-backed SearchService, turns missing index records into delete jobs, and leaves search reads pointed at the live search service. |
go test ./internal/service -run 'DurableSearchIndexer|SearchIndexHooks' -count=1; go test ./internal/service ./internal/search ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.3 section. |
Move P5.3 to Review; continue delayed automation scheduled items and provider/template jobs. |
feat(automation): queue macro and csat jobs |
Advances P5.5/P5.6 with durable macro fan-out and resolve-triggered CSAT sends. MacroService.ExecuteForDisplayIDs now enqueues automation:macro_execution jobs when a WorkerPool is configured, resolving display IDs at perform time like Chatwoot MacrosExecutionJob; CsatSurveyListener now enqueues idempotent csat:survey_send jobs for resolved conversations while keeping message-updated response building inline. |
go test ./internal/automation ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.5/P5.6 section. |
Move macro fan-out and generic CSAT send to Review; continue delayed automation actions and WhatsApp/Twilio CSAT template jobs. |
feat(automation): queue external action deliveries |
Advances P5.4 with durable automation external side effects. send_webhook_event and send_email_transcript now enqueue automation:webhook_delivery and automation:transcript_delivery jobs when a WorkerPool is configured; automation rules and macros can carry the WorkerPool through ActionService, and app bootstrap starts the durable worker plus dispatcher event jobs. |
go test ./internal/automation ./internal/worker ./internal/app -count=1; go test ./...; git diff --check; full verification recorded in the P5.4 section. |
Move P5.4 to Review; continue P5.5 delayed automation/macro fan-out and P5.6 CSAT channel-template jobs. |
feat(dispatch): queue async events durably |
Advances P5.2 with durable async event dispatch. channel.Dispatcher.DispatchAsync now enqueues event:dispatch_async jobs when a WorkerPool is configured, and dispatch.EventDispatcher now enqueues per-listener event:listener_dispatch jobs for async listener replay while preserving immediate sync listener behavior. |
go test ./internal/channel ./internal/dispatch ./internal/worker -count=1; go test ./...; git diff --check; full verification recorded in the P5.2 section. |
Move P5.2 to Review; continue P5.4/P5.5 automation webhook/transcript and CSAT durable job integration. |
feat(worker): add durable background jobs |
Advances P5.1 with a persistent background_jobs model and migration plus internal/worker.WorkerPool enqueue, scheduled claim, handler registry, retry/backoff, dead-letter, idempotency-key reuse, queue filtering, PostgreSQL SKIP LOCKED claim support, stale lock recovery, and graceful start/stop loop. The model is registered in app auto-migration and test DB defaults. |
go test ./internal/worker -count=1; go test ./...; git diff --check; full verification recorded in the P5.1 section. |
Move P5.1 to Review; continue P5.2 dispatcher async routing and P5.4/P5.5 automation/CSAT job integration on top of the durable core. |
test(parity): add enterprise browser smoke coverage |
Advances B12.3b with scripts/parity_frontend_smoke.sh --enterprise-browser-smoke. The browser driver now supports --enterprise, creates route-specific reused Chatwoot dashboard shells, and asserts browser-origin requests for SLA reports, CSAT reports, automation rules, macros, audit logs, custom roles, agent capacity list/edit, Captain settings/assistants, and Copilot thread requests. The smoke seed now enables the Chatwoot enterprise feature flags required by those route guards. |
bash -n scripts/parity_frontend_smoke.sh; node --check scripts/parity_frontend_browser_smoke.mjs; scripts/parity_frontend_smoke.sh --print; scripts/parity_frontend_smoke.sh --check; go test ./cmd/gochat -count=1; full verification recorded in the B12.3b section. Live enterprise browser smoke requires running GoChat and Chatwoot Vite. |
Move B12 verification harness to Review; continue Phase 5 job parity and convert any optional live-smoke failures into named slices. |
test(parity): add enterprise api smoke coverage |
Advances B12.3a with scripts/parity_frontend_smoke.sh --enterprise-smoke. The command composes the core API smoke with enterprise API assertions for SLA reports/metrics/download, public and account CSAT reports/metrics/download, automation-rule create/list, macro create/execute, audit-log list, custom-role list, agent-capacity list/users, Captain preferences/assistants, and Copilot thread/message payloads. |
bash -n scripts/parity_frontend_smoke.sh; scripts/parity_frontend_smoke.sh --print; scripts/parity_frontend_smoke.sh --check; focused verification recorded in the B12.3a section. Live enterprise smoke requires running GoChat with PostgreSQL/Redis/Meilisearch. |
Continue B12.3b with reused-frontend browser navigation for the same enterprise screens. |
test(parity): add browser smoke harness |
Advances B12.2b with scripts/parity_frontend_smoke.sh --browser-smoke and scripts/parity_frontend_browser_smoke.mjs. The browser smoke creates temporary Vite-served HTML shells for the reused Chatwoot v3app login and dashboard entrypoints, drives login through headless Chrome via the DevTools protocol without adding Node dependencies, confirms the Chatwoot auth cookie, and checks dashboard validation/conversation requests. |
bash -n scripts/parity_frontend_smoke.sh; node --check scripts/parity_frontend_browser_smoke.mjs; scripts/parity_frontend_smoke.sh --print; scripts/parity_frontend_smoke.sh --check; full verification recorded in the B12.2b section. Live browser smoke requires running GoChat and Chatwoot Vite. |
Continue B12.3 with enterprise screen browser/API smoke for SLA, CSAT reports, automation/macros, audit/custom roles, capacity, Captain, and Copilot. |
test(parity): add api smoke assertions |
Advances B12.2 with a repeatable scripts/parity_frontend_smoke.sh --api-smoke path that seeds deterministic frontend data, signs in through Chatwoot auth headers, asserts current-user/profile, inbox list, conversation/message list and create, contact/company show, widget config/message, and public CSAT show. The seed now includes company, conversation display ID, and CSAT message IDs for smoke assertions. |
bash -n scripts/parity_frontend_smoke.sh; scripts/parity_frontend_smoke.sh --print; scripts/parity_frontend_smoke.sh --check; go test ./cmd/gochat -count=1; full verification recorded in the B12.2a section. Live API smoke requires a running DB/backend and is not run in default check mode. |
Continue B12.2b with live browser navigation/assertions against the reused Vite frontend; B12.3 enterprise screen smoke remains open. |
test(parity): add frontend smoke harness |
Completes B12.1 readiness tooling: adds cmd/gochat as the reusable server/seed entrypoint, adds scripts/parity_frontend_smoke.sh to print/check/boot GoChat plus the reused reference/chatwoot Vite frontend, keeps the boot command Meilisearch-first, and writes docs/parity/frontend_smoke_report.md with seed credentials, command lines, logs, and the smoke matrix. |
bash -n scripts/parity_frontend_smoke.sh; scripts/parity_frontend_smoke.sh --print; scripts/parity_frontend_smoke.sh --check; go test ./cmd/gochat -count=1; full verification recorded in the B12.1 section. |
Continue B12.2 with browser/API assertions for auth, inbox, conversation, CRM, widget, CSAT, and enterprise screens. |
feat(captain): align streaming fallbacks |
Completed B11.3e streaming/realtime compatibility: Captain task stream routes now reuse non-stream request DTOs, resolve conversations by account-scoped display ID or legacy ID, guard missing LLM providers with deterministic SSE error/done events, validate rewrite operations, and keep stream errors frontend-readable. Copilot message nested thread payloads now use Chatwoot push_event_data shape and event payload helpers omit REST-only fields. |
Focused CaptainTask/Captain/Copilot handler and service tests passed; Copilot/Captain repository tests passed; full verification recorded below. | B11 moves to Review; start B12 reused frontend smoke. |
docs: land parity tracker handoff |
Froze the active tracker after B11.3d, copied the remaining Hermes-derived work into explicit B11.3e/B12/Phase 5/Phase 6 tracking rows, and clarified the commit/update rules for future checkpoints. | Documentation-only checkpoint; git diff --check is sufficient. |
Start B11.3e streaming/realtime compatibility. |
6aa62c6 docs: consolidate chatwoot parity roadmap |
Promoted Hermes-era plans into this master tracker; locked user decisions; added milestone, slice, enterprise, and webhook provider tracking. | Documentation-only checkpoint. | B1/P6.7 selected as next implementation slice. |
9e3f561 feat(webhook): align chatwoot ingress routes |
Added Chatwoot public webhook paths for Twitter, Telegram, LINE, SMS/Twilio, WhatsApp, Instagram, TikTok, and Shopify; removed generic fallback success masking. | go test ./...; route dump regenerated with TOTAL: 801; route parity stayed 251 exact, 0 missing. |
Provider-specific lookup and verification moved to review. |
bc7da9e feat(webhook): implement instagram and shopify ingress |
Implemented Instagram verify/event handling, Shopify HMAC/redact/event forwarding, and WhatsApp verify-token/app-secret signature corrections. | Focused webhook tests, go test ./..., route dump TOTAL: 801, route parity 251 exact, 0 missing, git diff --check. |
Remaining P6.7 work is durable incoming-message persistence and provider dispatch parity. |
5eb726b feat(webhook): persist incoming provider messages |
Added provider incoming-message persistence boundary and wired Telegram, LINE, SMS/Twilio, WhatsApp, Facebook/Instagram, and TikTok parsed incoming messages into ContactInbox, Conversation, and Message storage. | Focused webhook/channel tests passed; full go test ./... passed. |
Continue P6.7 review with delivery/read receipt status updates and async dispatch/events. |
66ecabb feat(webhook): persist provider receipt statuses |
Added delivery/read/failed status persistence for Twilio, WhatsApp, Facebook/Instagram, and TikTok receipt events. | Focused webhook/channel tests passed; full go test ./... passed. |
Continue P6.7 review with async dispatch/events and broader provider fixture assertions. |
06b999b feat(webhook): dispatch persisted provider events |
Wired webhook incoming persistence and status updates into the existing channel.Dispatcher fan-out boundary. |
Focused webhook tests passed; full go test ./... passed. |
Continue P6.7 review with broader provider fixture assertions. |
55295dd test(webhook): cover provider ingress persistence fixtures |
Added provider-specific webhook persistence fixture assertions for LINE, Twilio SMS, WhatsApp, Instagram, and TikTok, extending the existing Telegram fixture. | Focused webhook tests passed; full go test ./... passed. |
Continue P6.7 review with signature edge fixtures and final provider Done/Review classification. |
0ea7a08 feat(webhook): verify tiktok ingress signatures |
Added Chatwoot-style TikTok Tiktok-Signature HMAC verification using TIKTOK_APP_SECRET, timestamp freshness, and invalid-signature rejection coverage. |
Focused webhook tests passed; full go test ./... passed. |
Continue P6.7 review with LINE/WhatsApp missing-signature edge fixtures and final provider classification. |
0439f3b feat(webhook): require line ingress signatures |
Tightened LINE webhook signature parity so configured channel_secret requires a present and valid X-Line-Signature, with missing-signature rejection coverage. |
Focused webhook tests passed; full go test ./... passed. |
Continue P6.7 review with WhatsApp missing-signature edge fixtures and Shopify/Twitter final classification. |
1c30933 test(webhook): cover whatsapp ingress verification |
Added WhatsApp route-level verification coverage for verify-token challenge echo, signed Cloud API POST persistence, and missing Meta signature rejection without persistence. | Focused webhook tests passed; full go test ./... passed. |
Continue P6.7 review with Shopify/Twitter final classification. |
ebd8f08 feat(webhook): finalize provider ingress parity |
Finalized P6.7 provider classification with Twitter CRC prefix parity, Twitter route-level webhook tests, Instagram missing-signature rejection coverage, and Shopify real-handler classification. | Focused webhook/API tests passed; full go test ./... passed. |
Move next to serializer/API parity queue. |
56a8c5b feat(profile): align chatwoot user serializer |
Aligned /api/v1/profile, profile update, avatar delete/update, availability, auto-offline, and reset-access-token responses to Chatwoot's raw api/v1/models/_user.json.jbuilder shape. Added user/account/account_user fields and personal access-token persistence needed by the serializer. |
Focused profile/API tests passed; sandboxed go test ./... hit socket restrictions; escalated full go test ./... passed; git diff --check passed. |
Continue B2 with /auth/sign_in, /auth/validate_token, auth headers/cookie contract, and current-user payload fixtures. |
e59f9f3 feat(auth): add chatwoot session routes |
Added root /auth/sign_in, /auth/validate_token, /auth/sign_out, and /auth/password routes consumed by the reused Chatwoot frontend. Sign-in now emits DeviseTokenAuth-compatible access-token, token-type, client, expiry, and uid headers and returns raw { data: user }; validate-token returns { payload: { success, data } }. |
Focused auth/profile tests passed; route dump regenerated with TOTAL: 805; route parity remained 251 exact, 0 missing; escalated full go test ./... passed; git diff --check passed. |
Continue B2 with profile settings update parity: display_name, message_signature, ui_settings, password update, and multipart avatar handling. |
7aa3362 feat(profile): support chatwoot settings updates |
Completed profile settings update parity for display_name, message_signature, ui_settings, phone_number custom attributes, password changes with current-password verification, and multipart profile[...] FormData including avatar file input. |
Focused profile tests passed; escalated full go test ./... passed; git diff --check passed. |
B2 core auth/profile fixtures are complete enough to move next to conversations/messages serializer parity. |
9f89cbf feat(conversations): align chatwoot message serializers |
Added Chatwoot conversation/message serializer boundary for dashboard list/show/filter, message index, message create/update/retry, status toggle payloads, display-id route resolution, outgoing/private message defaults, echo_id, content_attributes, and conversation/message parity migration fields. |
Focused conversation/message handler tests passed; full go test ./... passed; git diff --check passed. |
Continue B3 with delete/update status parity, multipart attachment create, team assignment response parity, and deeper message finder before moving to contacts/companies. |
a465bbe feat(conversations): finish message mutation parity |
Finished the B3 mutation gap set: message delete now returns the Chatwoot deleted-message serializer and clears attachments; message status update supports status/external_error with API-inbox-only enforcement; MessageFinder now supports latest, before, after, and between windows; multipart attachments[] create attachment rows and serialize them in message payloads; team assignment returns the raw team payload. |
Focused handler/service/repository tests passed; full go test ./... passed; git diff --check passed. |
B3 core dashboard message flows move to review; continue with B4 contacts/companies while tracking deeper delivery/storage side effects. |
3481597 feat(crm): align contact company payloads |
Started B4 contacts/companies parity: added Chatwoot-shaped { meta, payload } and { payload } CRM serializers, strict empty-query 422 handling, contact create { contact, contact_inbox } envelope, selected custom-attribute deletion, companies nested company params, company contacts on contacts.company_id, relation search/add routes, and Rails-compatible PATCH routes. |
Focused contacts/companies tests passed; handler/service/repository tests passed; route dump regenerated with TOTAL: 809; route parity remained 251 exact, 0 missing; sandboxed full go test ./... failed on socket restrictions, escalated full go test ./... passed; git diff --check passed. |
Continue B4 with merge/import/export/labels/avatar/company destroy-custom-attributes, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
7a033e2 feat(crm): complete contact label avatar gaps |
Completed the next B4 CRM route/behavior slice: added contact_labels persistence, Chatwoot { payload: labels } contact label list/update endpoints, contact list/search label filtering via contact labels, contact/company avatar delete responses, company destroy_custom_attributes, and tracked trailing-slash nested route aliases. |
Focused CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with TOTAL: 817; route parity is 261 exact, 0 missing; sandboxed full go test ./... failed on local socket restrictions, escalated full go test ./... passed; git diff --check passed. |
Continue B4 with contact merge, import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
1e3bf47 feat(crm): align contact merge action |
Completed B4 contact merge parity for the reused dashboard action: added the exact POST /api/v1/accounts/:account_id/actions/contact_merge route, changed merge responses to the raw Chatwoot contact model serializer, moved conversations/contact inboxes/notes/messages from mergee to base, preserved base attributes while filling blanks from mergee, and covered legacy lowercase contact message senders. |
Focused merge/CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with TOTAL: 818; route parity is 262 exact, 0 missing; full go test ./... passed; git diff --check passed. |
Continue B4 with import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
8eed2f1 feat(crm): persist contact data imports |
Advanced B4 import/data-import parity: added Chatwoot-shaped data_imports fields and migration, changed contacts import missing-file errors to 422, successful imports to empty 200 OK, persisted import lifecycle states/counts/errors, and made CSV import merge/update existing contacts by identifier/email/phone with labels/custom attributes. |
Focused import tests passed; handler/service/repository/router package tests passed; full go test ./... passed; git diff --check passed. |
Continue B4.6 with contacts export async artifact/download/notification parity, then notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
47c2b80 feat(crm): persist contact export artifacts |
Advanced B4 export parity: POST /contacts/export now creates a persisted contact_exports artifact, stores Chatwoot default/requested CSV columns with UTF-8 BOM, supports label/filter export inputs, exposes a local download URL, and creates a user notification with export metadata. |
Focused export tests passed; handler/service/repository/router package tests passed; regenerated route dump TOTAL: 819; route parity remains 262 exact, 0 missing; full go test ./... passed; git diff --check passed. |
Continue B4 with real email delivery for export completion if required by mailer infrastructure, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
af57482 feat(crm): align contact note payloads |
Advanced B4 notes parity: contact notes now return raw Chatwoot note arrays/objects with nested agent payloads, flat and nested note.content requests are accepted, create/update/show avoid the Go API envelope, delete returns empty 200 OK, note deletion hard-deletes like Chatwoot, company note payloads include user metadata, and the PUT note update route is registered. |
Focused notes/API tests passed; full go test ./... passed; route dump regenerated with TOTAL: 820; tracked route parity remains 262 exact, 0 missing; git diff --check passed. |
Continue B4 with nested contact/company conversations payload depth, CRM Meilisearch search shape, frontend smoke fixtures, and deferred export email delivery. |
fb775b7 feat(crm): align nested conversation payloads |
Advanced B4 nested CRM conversation parity: contact and company conversations now render Chatwoot's { payload: [conversation partial] } shape with nested meta.sender, messages, last_non_activity_message, account/inbox IDs, timestamps, priority, labels, and unread fields from the shared conversation serializer. Company conversations now follow the enterprise controller's latest-20, last_activity_at DESC behavior instead of paginated raw models. |
Focused nested conversation handler tests passed; focused handler/service/repository/router tests passed; escalated full go test ./... passed after sandbox socket limits blocked local-only auth/middleware tests; git diff --check passed. No route changes; route dump remains TOTAL: 820. |
Continue B4 with CRM Meilisearch search shape, stricter import label validation, frontend smoke fixtures, and deferred export email delivery. |
202da19 feat(crm): route crm search through meilisearch |
Advanced B4 CRM search parity: contact and company service search now use the configured search service for non-empty queries, preserving Chatwoot CRM { meta, payload } serializers by resolving Meilisearch result IDs back through account-scoped repositories. Bootstrap wires the Meilisearch-backed search service into contact/company services, and DB search remains only the no-reader/test fallback. Global search DB fallback now includes companies so the company document shape has a repository equivalent. |
Focused search/service/repository/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. |
Continue B4 with stricter import label validation, frontend smoke fixtures, and deferred export email delivery. |
653db81 feat(crm): validate imported contact labels |
Completed the remaining B4 import validation gap: contact CSV import now validates labels against existing account labels before creating/updating contacts, rejects rows with unknown labels, records failed row counts, preserves canonical existing label names, and no longer creates arbitrary account tags from import input. |
Focused import/service/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. |
Continue B4 with frontend CRM smoke fixtures and deferred export email delivery. |
c2766fe feat(crm): email contact export completions |
Completed B4 contact export email delivery: the completed export artifact now triggers a Chatwoot-style contact_export_complete mailer boundary for the requesting user's email, using the same subject and download URL semantics while preserving the existing persisted notification. The default mailer reads Chatwoot-compatible SMTP env vars and no-ops when SMTP is not configured. |
Focused export/service/API tests passed; full go test ./... passed; git diff --check passed. No route changes; route dump remains TOTAL: 820. |
Continue B4 with frontend CRM smoke fixtures. |
ad29dc3 test(crm): cover chatwoot frontend crm smoke |
Closed the B4 CRM API smoke gap with a repeatable handler-level flow that mirrors the reused Chatwoot dashboard contact/company clients: contact create/list/search/show/update/custom-attribute deletion/labels/contactable-inboxes/notes/conversations plus company create/list/search/show/update/custom-attribute deletion/contact attach/list/search/notes/conversations. | go test ./internal/handler/api/v1 -run TestChatwootFrontendCRMSmoke -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. |
Move B4 to review. True browser Playwright validation with the reused frontend remains tracked by B12/M7. |
0e83e8d feat(inboxes): align chatwoot inbox serializers |
Started B5 inbox/channel parity by rendering Chatwoot api/v1/models/_inbox.json.jbuilder-style payloads for inbox list/show/create/update/reset-secret flows, mapping local channel slugs to Channel::* class names, exposing channel-specific settings from channel_config, returning Chatwoot's async delete message, making avatar delete empty 200 OK, and adding the frontend-used PATCH /inboxes/:id route. |
`go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity | TestInbox' -count=1; go test ./internal/handler/api/v1 -count=1; regenerated docs/parity/gochat_routes.txt (TOTAL: 821); go run ./cmd/route_parity; go test ./...; git diff --check`. |
ee93546 feat(inboxes): bind chatwoot channel settings |
Completed B5.2 request binding for reused dashboard inbox create/update flows: JSON and multipart/urlencoded bodies now accept top-level inbox settings, nested channel[...], nested csat_config[...], selected feature flags, API webhook config, generated web widget/API secrets, and Telegram create without explicit name. |
go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInboxHandler_ChatwootCreateUpdateRequestBinding|TestInbox' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; escalated go test ./... after sandbox socket denial; git diff --check. |
Continue B5.3 with durable working-hours update behavior and out-of-office parity, then B5.4 inbox members and B5.5 channel-specific config depth. |
945782e feat(inboxes): persist chatwoot working hours |
Completed B5.3 working-hours parity for the generic inbox update flow: inbox create initializes Chatwoot default weekly schedule where the table exists, inbox update accepts frontend working_hours JSON with string/empty time fields from timeSlotTransform, persists schedules in working_hours, serializes working_hours from the relation, and preserves working_hours_enabled, out_of_office_message, and timezone behavior. |
go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInboxHandler_ChatwootCreateUpdateRequestBinding|TestInbox' -count=1; go test ./internal/service -run 'Inbox|WorkingHour' -count=1; go test ./internal/handler/api/v1 -count=1; escalated go test ./...; git diff --check. |
Continue B5.4 with inbox member payload and round-robin membership side effects, then B5.5 channel-specific config depth. |
82167f5 feat(inboxes): align chatwoot inbox members |
Completed B5.4 inbox member parity for reused dashboard agent assignment flows: account-scoped and nested member endpoints now return Chatwoot { payload: [agent] } responses, agent payloads expose frontend-required user fields, duplicate adds are idempotent, create adds only missing users, and update performs a diff so preserved rows are not recreated unnecessarily. |
go test ./internal/handler/api/v1 -run InboxMember -count=1; go test ./internal/service -run InboxMember -count=1; go test ./internal/repository -run InboxMember -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. |
Continue B5.5 with deeper channel-specific config parity and InboxLimit enforcement on channel creation. |
f0aae79 feat(inboxes): deepen channel config parity |
Advanced B5.5 channel config parity: generic inbox JSON updates now hoist flat frontend SMTP/IMAP/channel setting keys into channel_config, the serializer exposes deeper Email, WhatsApp, LINE, SMS, and Twilio voice/config fields, channel defaults match Chatwoot closer for WebWidget/API/Email/WhatsApp, and the Twilio frontend create route accepts nested twilio_channel payloads while returning the raw Chatwoot inbox serializer. |
go test ./internal/handler/api/v1 -run 'TestInboxHandler_Chatwoot(ChannelSpecificConfigDepth|CreateUpdateRequestBinding|SerializerParity)|TestTwilioChannel_Create' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. |
Continue B5.5 with InboxLimit enforcement and any remaining channel-specific route response cleanup. |
8fd40ec docs: record channel config checkpoint |
Recorded the B5.5 channel config checkpoint and kept the route count at TOTAL: 821 with tracked route parity still at 262 exact and 0 missing. |
Documentation-only checkpoint after the B5.5 channel config implementation. | Next active slice is B5.5c AgentCapacityPolicy/InboxCapacityLimit route, serializer, and service parity. |
b197e54 feat(capacity): align chatwoot inbox capacity limits |
Completed B5.5c AgentCapacityPolicy/InboxCapacityLimit API/data parity: policy CRUD now returns raw Chatwoot payloads with Unix timestamps, assigned_agent_count, and inbox_capacity_limits; assignment_logic is optional/defaulted; nested policy users and inbox limits are registered; InboxCapacityLimit validates account scope, duplicate inbox assignment, and non-negative limits; account users can be assigned/unassigned to capacity policies. |
go test ./internal/service -run AgentCapacity -count=1; go test ./internal/handler/api/v1 -run AgentCapacity -count=1; go test ./internal/router -count=1; go test ./internal/handler/api/v1 -count=1; sandboxed go test ./... failed on local socket restrictions; escalated go test ./... passed; route dump regenerated with TOTAL: 829; route parity is 267 exact, 7 parameter-compatible, 0 missing; git diff --check. |
Continue final B5.5 review for remaining channel-specific route response cleanup, then move to B6 Meilisearch live-shape review. |
f04a03b feat(channels): align channel route inbox payloads |
Completed the final B5.5 channel route-response cleanup: Email, Twilio SMS, and LINE dedicated channel create/get/update/list routes now return Chatwoot frontend-compatible raw inbox payloads or { payload: [...] } lists instead of { channel }, { channels }, { channel, inbox }, or success-message envelopes. Delete routes now return empty 200 OK, and channel creation binds the dedicated channel ID/config back onto the inbox before serialization. |
go test ./internal/handler/api/v1 -run 'Test(Email|Twilio|LINE)Channel' -count=1; go test ./internal/service -run Inbox -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. |
B5 inbox/channel API parity moves to Done for current frontend-critical scope; next active slice is B6 Meilisearch live-shape review. |
a16c23c feat(search): align chatwoot search payloads |
Advanced B6 search live-shape parity: global and entity search endpoints now return Chatwoot frontend payload envelopes, search filters accept Chatwoot since, until, and from=contact:id/agent:id params, message sender IDs are indexed/filterable in Meilisearch and DB fallback, SearchController default page size is aligned to 15, and Meilisearch hit maps are serialized into frontend message/contact/conversation/article shapes. |
go test ./internal/handler/api/v1 -run SearchHandler -count=1; go test ./internal/search -run 'SearchFilter|Meili|Engine' -count=1; go test ./internal/repository -run Search -count=1; go test ./internal/handler/api/v1 -count=1; go test ./internal/search -count=1; go test ./internal/repository -count=1; sandboxed go test ./... failed on local socket restrictions; escalated go test ./... passed; git diff --check. No route changes; route dump remains TOTAL: 829. |
B6.1 and B6.3 are Done; B6.2 is in Review with mocked Meilisearch coverage. Continue B6.4 live integration gate and B6.5 DB-fallback hardening before moving B6 to Review/Done. |
f08c743 test(search): add meilisearch live gate |
Completed B6 gate/hardening work: added an env-gated live Meilisearch test that bootstraps isolated indexes, indexes message/contact documents, verifies account-scoped searches and sender filters, rejects search.engine=db in release mode, and prevents cmd/reindex_search from silently running against the DB fallback. |
go test ./internal/config -count=1; go test ./cmd/reindex_search -count=1; go test ./internal/search -count=1; go test ./...; git diff --check. Live gate is skipped unless GOCHAT_LIVE_MEILI_HOST is set. No route changes; route dump remains TOTAL: 829. |
B6 moves to Review. Optional next verification is running GOCHAT_LIVE_MEILI_HOST=http://localhost:7700 GOCHAT_LIVE_MEILI_API_KEY=... go test ./internal/search -run TestLiveMeiliSearchEngineIndexesAndSearchesChatwootShapes -count=1; otherwise continue B7 SLA/assignment capacity. |
a98dc2c feat(capacity): enforce inbox assignment limits |
Started B7 assignment/capacity enforcement: manual assignment, team assignment with explicit agent, team overflow fallback, and auto-assignment now respect account_users.agent_capacity_policy_id plus matching inbox_capacity_limits.conversation_limit; only open conversations in the same inbox count toward capacity and resolved conversations do not block new assignments. |
go test ./internal/service -run 'AssignAgent|AssignTeam' -count=1; go test ./internal/autoassignment -run Capacity -count=1 escalated after sandbox socket denial; go test ./internal/service -count=1; go test ./internal/autoassignment -count=1 escalated after sandbox socket denial; go test ./internal/handler/api/v1 -run 'Conversation|AgentCapacity' -count=1; escalated go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. |
Continue B7 with SLA policy CRUD payload review, applied-SLA persistence for new conversations, and timer/breach lifecycle tests. |
95224fa feat(sla): align sla policy payloads |
Completed B7.2 SLA policy CRUD payload parity for the reused Chatwoot frontend: create/show/update now return { payload: sla_policy }, index returns { payload: [...] }, destroy returns empty 200 OK, and the SLA policy serializer emits only the fields from Chatwoot's enterprise SLA policy partial. |
go test ./internal/handler/api/v1 -run SlaPolicy -count=1; go test ./internal/service -run Sla -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. |
Continue B7.3 with applied-SLA persistence for new/open conversations, then B7.4 timer/breach lifecycle. |
a11bb96 feat(sla): persist applied sla on conversations |
Completed the first B7.3 applied-SLA persistence slice: conversation create/update now accept Chatwoot enterprise sla_policy_id, validate account ownership and replacement/removal rules, create one applied_slas row when a policy is attached, keep status-only updates idempotent for existing SLA conversations, and serialize applied_sla in conversation payloads using Chatwoot's applied-SLA partial fields. |
go test ./internal/service -run 'ConversationService_.*Sla|ConversationService_Update|ConversationService_Create' -count=1; go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite/Test(Update_WithSlaPolicy|Update_Success|Create_Success)' -count=1; go test ./internal/service -run 'Conversation|AppliedSla|Sla' -count=1; go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite|SlaPolicy' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. |
Continue B7.4 with idempotent SLA event/timer lifecycle and then B7.5 applied-SLA report/list payload parity. |
213bf2b feat(sla): make sla breach events idempotent |
Completed B7.4 timer/breach lifecycle core parity: SLA thresholds now use Chatwoot seconds units, FRT/NRT/RT checks treat the exact threshold as breached, missed events are idempotent by applied_sla_id, event_type, and meta, NRT events store the last incoming message ID in meta, and resolved active/active-with-misses applied SLAs transition to hit or missed. |
go test ./internal/service -run 'AppliedSlaService|ConversationService_Create_AppliesSlaPolicy' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicy|ConversationCrudTestSuite/TestUpdate_WithSlaPolicy' -count=1; go test ./internal/service -run 'AppliedSla|Sla|Conversation' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicy|ConversationCrudTestSuite' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 829. |
Continue B7.5 with applied-SLA report/list payload parity, then review notifications/business-hours depth. |
4f85ef1 feat(sla): align applied sla reports |
Completed B7.5 applied-SLA report/list payload parity for the reused Chatwoot SLA reports screen: added GET /api/v1/accounts/:account_id/applied_slas, aligned the index { payload, meta } shape, filtered missed reports by date/inbox/team/SLA policy/label/assignee, returned Chatwoot applied-SLA/conversation/SLA-event fields, changed metrics to raw total_applied_slas, number_of_sla_misses, and hit_rate, and changed download to the breached-conversation CSV. |
go test ./internal/service -run 'SlaPolicyService_.*AppliedSla|AppliedSlaReport' -count=1; go test ./internal/handler/api/v1 -run 'SlaPolicyHandler_.*AppliedSla' -count=1; go test ./internal/service -count=1; go test ./internal/handler/api/v1 -count=1; go run ./cmd/dump_routes > docs/parity/gochat_routes.txt; go run ./cmd/route_parity; go test ./...; go test ./cmd/route_parity -count=1; git diff --check. Route dump is now TOTAL: 830; route parity tracks 277 routes with 270 exact, 7 parameter-compatible, and 0 missing. |
Continue B7 with business-hours timer math and SLA notification delivery review before closing the SLA package. |
330f875 docs: record applied sla reports checkpoint |
Recorded the B7.5 applied-SLA reports checkpoint in this tracker and made B7.6 the active follow-up. | Documentation-only checkpoint after the applied-SLA report/list implementation verification. | Continue B7.6 with business-hours timer math and SLA notification delivery. |
64926db docs: land chatwoot parity tracking plan |
Normalized this tracker after B7.5: added the planning control board, mapped Hermes plans into active B8/B9/B6 slices, and made B7.6's investigation/landing contract explicit. | Documentation-only checkpoint; git diff --check passed before commit. |
Continue B7.6 implementation. |
d23f3f0 feat(sla): notify users on sla misses |
Completed B7.6 notification parity and business-hours timing review: new SLA miss events now create Chatwoot notification types for conversation participants, account administrators, and assignee with user de-duplication; repeated SLA evaluation remains event/notification idempotent; the current reference/chatwoot service stores only_during_business_hours but still calculates thresholds as elapsed seconds, so Go has a regression test for that reference timing. |
go test ./internal/service -run AppliedSlaService -count=1; go test ./internal/service -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. |
B7 moves to Review. Next active enterprise slice is B8 CSAT account/public/report/send parity. |
9e0826b docs: record sla notification checkpoint |
Recorded the B7.6 SLA notification checkpoint, moved B7 to Review, and set B8 as the next active enterprise slice. | Documentation-only checkpoint after d23f3f0; git diff --check passed before commit. |
Continue B8 CSAT account/public/report/send parity. |
f441680 feat(csat): align account report payloads |
Started B8 with account-side CSAT report parity: GET /csat_survey_responses now returns the raw Chatwoot array of CSAT response serializers; list and metrics filters accept frontend since/until, user_ids, inbox_id, team_id, and rating; metrics now returns total_count, ratings_count, and total_sent_messages_count; CSAT update/review-note responses return the Chatwoot CSAT response serializer with nested contact, assigned agent, reviewer, display conversation ID, and Unix timestamps. |
go test ./internal/handler/api/v1 -run CsatSurvey -count=1; go test ./internal/automation -run CsatSurveyService -count=1; go test ./internal/handler/api/v1 -count=1; go test ./internal/automation -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. |
Continue B8 with public CSAT submit/update lock regression, resolve-triggered survey send idempotency, and download CSV parity. |
fca6838 docs: record csat report checkpoint |
Recorded the B8.1 CSAT report checkpoint in this tracker, moved B8 to Doing, and made B8.2/B8.3 the next active implementation slices. | Documentation-only checkpoint after f441680; git diff --check passed before commit. |
Continue B8 public CSAT submit/update, resolve-triggered survey send idempotency, and CSAT download parity. |
bda5ad5 docs: expand parity execution plan |
Expanded this tracker with executable B8-B12 breakdowns for CSAT, automation/macros, audit/custom roles/limits, Captain/Copilot, and reused frontend smoke validation. | Documentation-only checkpoint; git diff --check HEAD~1 HEAD passed. |
Continue B8 public CSAT submit/update and resolve-triggered send parity. |
ef3a909 feat(csat): send surveys on resolved conversations |
Advanced B8 public/send parity: public CSAT update now accepts the reused survey frontend's object-shaped message.submitted_values, keeps one message-linked CSAT response per input_csat message across repeated submissions, and still enforces the 14-day message-date lock. The resolve listener now loads the resolved conversation, checks inbox CSAT enablement and survey label rules, creates one Chatwoot-style input_csat template message with inbox CSAT content/display type, and no longer creates pending rating-0 responses before customer submission. Message-updated events also build CSAT responses from submitted_values when present. |
go test ./internal/automation -run 'CsatSurveyListener|CsatSurveyService' -count=1; go test ./internal/handler/api/v1 -run CsatSurvey -count=1; go test ./internal/handler/widget -run 'CSAT|Csat|PublicInbox' -count=1; go test ./internal/service -run Widget -count=1; go test ./internal/automation -count=1; go test ./internal/handler/api/v1 -count=1; go test ./...; git diff --check. No route changes; route dump remains TOTAL: 830. |
Continue B8.4 CSAT download CSV parity and then review channel-specific WhatsApp/Twilio template send hooks as broader integration work. |
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. |
feat(captain): align task payload persistence |
Completes B11.3d's frontend task contract: rewrite/summarize/reply suggestion now accept Chatwoot operation and conversation_display_id payloads, return raw { message, follow_up_context } or 422 { error }, avoid no-provider panics with Captain is disabled, resolve conversations by account-scoped display ID, and persist task outputs into copilot_suggestion_messages. Label suggestion and follow-up POST routes now consume the dashboard tasks.js bodies, preserve/update Chatwoot follow-up context, and return raw payloads. CopilotMessage now validates the Chatwoot allowed JSON keys content, reasoning, function_name, and reply_suggestion for reloadable tool-call state. |
go test ./internal/handler/api/v1 -run 'CaptainTask|Captain|Copilot' -count=1; go test ./internal/service -run 'CaptainTask|Captain|Copilot' -count=1; go test ./internal/repository -run 'Copilot|Captain' -count=1; go test ./internal/handler/api/v1 -count=1; go test ./internal/service -count=1; TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp go test ./... with existing caches; git diff --check. Focused fixtures cover raw no-envelope task responses, disabled state, display-ID lookup, suggestion persistence, follow-up history update, and Copilot message key validation. |
Continue B11.3e streaming/realtime compatibility; B12 frontend smoke remains after B11.3e. |
Next Slice Contract
Completed implementation slice: P3.6 label CRUD now closes the reused-frontend account label list/create/show/update/delete payload drift.
Completed documentation slice: this checkpoint records the label CRUD implementation, verification commands, and next handoff target in the active tracker.
Next implementation slice: continue Phase 6 placeholder burn-down from a fresh audit, then open the next P3.6 custom-attributes/custom-filters drift slice from reference or smoke evidence. Optional B12 live smoke remains environment-dependent and should convert failures into named slice owners.
| 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 | Keep B11.3d Copilot task/tool-call persistence as current task baseline. | resource :tasks routes, Copilot/Captain task services, dashboard Copilot clients, current local copilot_* models. |
Done by feat(captain): align task payload persistence; task routes accept frontend bodies, persist suggestions, return raw payloads/disabled states, and validate reloadable Copilot message keys. |
| N25 | Keep B11.3e streaming/realtime compatibility as current Captain/Copilot disabled-state baseline. | Chatwoot Copilot/Captain streaming, push/event payloads, current Go channel dispatcher. | Done by feat(captain): align streaming fallbacks; Captain stream routes have deterministic SSE error/done states and Copilot push payload helpers match reference shapes. |
| N26 | Keep B12.1 smoke harness as the current frontend verification baseline. | reference/chatwoot frontend boot scripts and GoChat dev/test boot flow. |
Done by test(parity): add frontend smoke harness; next step is B12.2 browser/API assertions against the checked command/report. |
| 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. |
| N29 | Keep P3.6 label CRUD as current label baseline. | Chatwoot labels controller/views/model/schema and dashboard labels API/store. | Done by feat(labels): align chatwoot label payloads; raw/list payloads, persistence, account scope, and association cleanup are covered. |
| N30 | Resume Phase 6 placeholder burn-down from fresh evidence. | rg placeholder audit, reused frontend API calls, matching reference controllers/Jbuilder views. |
Next implementation slice should record every frontend-reachable stub owner and burn down the highest-impact remaining placeholder. |
Current B2 profile checkpoint:
- Added a
ProfileUserResponsematching Chatwoot'sapi/v1/models/_user.json.jbuilder, includingaccess_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_tokenlike Chatwoot'sPubsubableconcern, avoiding duplicate empty token rows and enabling ActionCable-compatible profile payloads. - Added migration
000017_add_profile_serializer_fieldsfor profile serializer fields on users, accounts, and account_users. - Added Chatwoot root auth/session routes.
/auth/sign_innow accepts normalized email/password login, emits the auth headers stored by frontendcw_d_session_info, and returns the profile serializer underdata./auth/validate_tokenvalidates the sameaccess-tokenheader and returns the frontend-expectedpayload.datashape./auth/sign_outrevokes 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], JSONprofile.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.jbuildershape:{ data: { meta, payload } }, withpayloadentries built fromapi/v1/conversations/partials/_conversation.json.jbuilderfields such asmeta.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_idfirst, 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 matchingapi/v1/models/_message.json.jbuilderfield names. - Message create accepts the reused frontend payload from
dashboard/api/inbox/message.js, including omittedmessage_type,private,echo_id, JSON or multipartcontent_attributes, and defaults to outgoing text messages like Chatwoot's dashboard composer. - Message create/update/retry return raw Chatwoot message payloads with numeric
message_typevalues expected byshared/constants/messages.js. - Message delete now follows
MessagesController#destroy: it keeps the message row, changes content toThis message was deleted, setscontent_attributes.deleted, deletes message attachments, and returns the raw message serializer instead of204. - Message status updates now follow
Messages::StatusUpdateServiceforsent/delivered/read/failed, preserve the read-to-delivered guard, recordexternal_erroronly on failed messages, and reject status updates for non-API inboxes with403. - Message list now follows
MessageFinderwindows for latest,before,after, and between ranges, includingfilter_internal_messageshandling. - 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_teamand renders the raw team payload when assigning byteam_id. - Added migration
000018_add_conversation_message_parity_fieldsfor display-id routing and message serializer fields such asecho_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/payloadenvelopes instead of legacy Gocontactsor 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
422withSpecify search string with parameter q, matching Chatwoot controllers. - Company list/search/show/create/update now return enterprise Chatwoot
payloadandmeta.total_count/pageshapes; create/update accept nested{ company: ... }params while retaining flat compatibility inside the service boundary. - Company contact relations now use
contacts.company_idinstead 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, andPOST /companies/:company_id/contactsbody-based contact assignment routes. - Added
contact_labelspersistence and Chatwoot-compatibleGET/POST /contacts/:contact_id/labelsresponses 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, andPOST /companies/:company_id/destroy_custom_attributeswith Chatwoot-shaped{ payload }responses and422 { 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_mergeand 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_importspersistence for contact CSV imports. Missingimport_filenow returns422 { error: "failed to import contacts" }, successful imports return empty200 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 OKdestroy, hard-delete semantics, andPUT/PATCHupdate 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 bylast_activity_at DESClike 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: 820and tracked route parity was262 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.jbuilderand 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 empty200 OK. - Local channel slugs are serialized to frontend-required Rails channel class names such as
Channel::WebWidget,Channel::Api,Channel::Telegram, andChannel::TwilioSms. - Channel-specific fields stored in
channel_configare 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_idroute alias; the current route dump is nowTOTAL: 830after 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 nestedcsat_config[...]values generated bybuildInboxData. - 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 widgetwebsite_token/hmac_token, generates APIsecret/hmac_token/identifier, and derives a Telegram inbox name when the frontend omitsname. - Inbox create/update now follows Chatwoot's
OutOfOffisableschedule lifecycle: default seven-day working hours are created for new inboxes,working_hoursupdates persist the weekly relation, empty string time fields from the frontend are normalized, open-all-day rows are expanded to00:00-23:59, and the serializer returnsweekly_scheduleshape from the relation. - Inbox member routes now follow Chatwoot's account-scoped
InboxMembersControllercontract:GET /inbox_members/:inbox_id,POST /inbox_members, andPATCH /inbox_membersreturn{ payload: [agent] };DELETE /inbox_membersreturns empty200 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 enterprisecustom_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
channelpayloads. - Inbox serializer depth now covers Email IMAP/SMTP/forwarding fields, WhatsApp
provider_configand generated webhook verify token, LINE channel credentials, SMS provider config, and Twilio SMS/WhatsApp/voice fields. Twilio channel creation accepts Chatwoot's nestedtwilio_channelpayload 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, andassigned_agent_countthroughaccount_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 empty200 OKfor deletes. The handlers bind the dedicated channel record ID/config back ontoinboxes.channel_id/channel_configbefore serialization. - Remaining B5 gap: none for the current frontend-critical API scope. Browser validation remains deferred to B12/M7.
Active B5 task board:
| ID | Task | Reference source | Status | Exit gate |
|---|---|---|---|---|
| B5.1 | Inbox CRUD serializer and route shape. | InboxesController, _inbox.json.jbuilder, dashboard inbox store/API. |
Done | 0e83e8d; serializer parity test, route dump TOTAL: 821, full tests. |
| B5.2 | Inbox create/update request binding and settings persistence. | buildInboxData, permitted_params, settings pages. |
Done | ee93546; JSON and multipart channel[...] requests persist core inbox/channel settings. |
| B5.3 | Working hours and out-of-office parity. | OutOfOffisable, working hour controller/model. |
Done | 945782e; weekly schedule create/update/show and out_of_office service tests match Chatwoot semantics. |
| B5.4 | Inbox member payload and round-robin side effects. | InboxMembersController, Inbox#add_members/remove_members. |
Done | 82167f5; account-scoped and nested member routes return { payload } agents, create is add-only/idempotent, update is diff-based, and delete is empty 200 OK. |
| B5.5 | Channel-specific config depth and inbox limits. | WebWidget/API/Telegram/Email/WhatsApp/SMS/LINE channel models/controllers, enterprise inbox limits. | Done | f04a03b; Email/WhatsApp/LINE/SMS/Twilio config payloads, Twilio create route, AgentCapacityPolicy/InboxCapacityLimit API/data parity, and dedicated Email/Twilio/LINE response cleanup are covered. |
B5.2 execution breakdown:
| Step | Implementation target | Reference source | Required tests | Status |
|---|---|---|---|---|
| B5.2a | Add a request binder that accepts JSON, urlencoded form, and multipart FormData from the reused dashboard inbox screens. It must parse top-level settings plus nested keys such as channel[type], channel[website_url], channel[selected_feature_flags][], and csat_config[...]. |
reference/chatwoot/app/javascript/dashboard/store/modules/inboxes/channelActions.js, reference/chatwoot/app/javascript/dashboard/store/modules/inboxes.js |
Handler tests for website multipart create, API JSON create, Telegram JSON create without explicit name, and settings update. | Done |
| B5.2b | Normalize channel type aliases from frontend slugs and Rails class names into the local storage format while keeping serializers Rails-compatible. | reference/chatwoot/app/models/channel/*, _inbox.json.jbuilder |
Serializer tests prove stored slug returns Channel::* and unknown channel types are rejected with frontend-readable validation errors. |
Done |
| B5.2c | Persist core inbox settings sent by permitted_params: greeting, email collection, CSAT flag/config, auto-assignment, working-hours flag, out-of-office message, timezone, post-resolve messages, single-conversation lock, sender name type, business name, and portal ID. |
reference/chatwoot/app/controllers/api/v1/accounts/inboxes_controller.rb, inbox settings Vue screens |
Create/update tests reload the inbox and assert fields survive round trip in Chatwoot payload names. | Done |
| B5.2d | Persist channel-specific settings into dedicated fields where they exist and channel_config where GoChat has no first-class column yet. |
WebWidget/API/Telegram/LINE/Twilio/Email channel models and settings screens | Tests cover web widget website_url/widget_color/welcome_*, API webhook_url, and Telegram bot_token shapes. LINE deep settings remain in B5.5. |
Review |
| B5.2e | Preserve backward compatibility for internal tests and existing local callers that still construct service.CreateInboxRequest with channel_type and simple fields. |
Existing Go service callers and handler tests | Existing TestInbox* suite stays green. |
Done |
B5.2 exit command set:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'TestInboxHandler_ChatwootSerializerParity|TestInbox' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run Inbox -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
B5.4 execution breakdown:
| Step | Implementation target | Reference source | Required tests | Status |
|---|---|---|---|---|
| B5.4a | Return Chatwoot account-scoped inbox member envelopes as { payload: [agent] } instead of local { members, meta } model envelopes. |
reference/chatwoot/app/controllers/api/v1/accounts/inbox_members_controller.rb, app/views/api/v1/accounts/inbox_members/*.json.jbuilder |
TestAccountScopedInboxMembers_ChatwootPayloadAndDiffUpdate validates payload shape and agent fields. |
Done |
| B5.4b | Serialize assigned users through the Chatwoot agent partial field set, including enterprise custom_role_id. |
reference/chatwoot/app/views/api/v1/models/_agent.json.jbuilder |
Handler payload assertions cover available_name, availability_status, and absence of local inbox_id. |
Done |
| B5.4c | Match create/update/delete mutation semantics: create adds missing users only, update diffs desired membership, duplicate user IDs are idempotent, and delete returns empty 200 OK. |
Inbox#add_members, Inbox#remove_members, dashboard inboxMembers.js store actions. |
Account-scoped handler regression creates duplicates, patches to a replacement set, and deletes back to zero. | Done |
| B5.4d | Preserve loaded User and Inbox relations for member serialization after create/list/update. |
Go repository preload boundary; Chatwoot views expect account/user fields. | Repository/handler focused tests and full go test ./.... |
Done |
B5.5 planned breakdown:
| Step | Implementation target | Reference source | Required tests | Status |
|---|---|---|---|---|
| B5.5a | Compare current Go channel config fields against Chatwoot channel models/controllers for WebWidget, API, Telegram, Email, WhatsApp, Twilio SMS, and LINE. | reference/chatwoot/app/models/channel/*, channel controllers, dashboard inbox setting screens. |
Done by f04a03b; config matrix is covered and dedicated Email/Twilio/LINE route responses are frontend-compatible. |
Done |
| B5.5b | Persist and serialize deeper channel-specific settings used by reused settings screens, preferring first-class columns only where GoChat already has them and channel_config otherwise. |
Chatwoot channel model validations and InboxesController#permitted_params. |
Done by f0aae79; handler tests cover Email IMAP/SMTP flat JSON update, WhatsApp provider config, LINE credentials, and Twilio nested create raw inbox payload. |
Done |
| B5.5c | Enforce enterprise InboxLimit during inbox/channel creation and return frontend-compatible over-limit errors. | Chatwoot enterprise inbox limit policy/model behavior. | Done by b197e54; nested policy inbox limits validate duplicate/wrong-account/non-negative inputs and expose Chatwoot payloads. Assignment-path enforcement remains in B7/P4.4. |
Done |
| B5.5d | Keep route artifacts unchanged unless new Chatwoot-compatible routes are registered; regenerate route dump only on route changes. | cmd/dump_routes, cmd/route_parity. |
Done by b197e54; route dump regenerated to TOTAL: 829, route parity expanded to 274 tracked routes with 0 missing. |
Done |
| B5.5e | Clean up dedicated channel controller envelopes outside generic inbox CRUD. | Twilio channel controller/view, dashboard channel clients, api/v1/models/_inbox.json.jbuilder. |
Done by f04a03b; Email/Twilio/LINE create/get/update return raw inbox payloads, list returns { payload: [...] }, delete returns empty 200 OK, and no route artifacts changed. |
Done |
B5.5c AgentCapacityPolicy / InboxCapacityLimit parity contract:
| Area | Chatwoot reference contract | Current Go gap | Required landing work |
|---|---|---|---|
| Policy routes | reference/chatwoot/config/routes.rb:123-127 exposes /api/v1/accounts/:account_id/agent_capacity_policies, nested /:policy_id/users, and nested /:policy_id/inbox_limits. |
Go registers only policy CRUD through RegisterAgentCapacityRoutes; the old local /inboxes/:inbox_id/inbox_limits handler is not the Chatwoot enterprise route. |
Register nested Chatwoot-compatible users and inbox_limits routes under agent_capacity_policies; keep old routes only as compatibility surfaces. |
| Policy request/response | Enterprise controller accepts { agent_capacity_policy: { name, description, exclusion_rules } }; index/show/create/update return raw policy JSON, destroy is head :ok; assignment_logic is not a permitted Chatwoot field. |
Go response helpers wrap policy payloads in { success, data } or pagination meta, and service creation currently requires assignment_logic. |
Add raw Chatwoot serializers, accept wrapped and flat bodies where useful, make assignment_logic optional/defaulted for storage compatibility, and keep update wrapper-compatible. |
| Policy serializer | _agent_capacity_policy.json.jbuilder emits id, name, description, exclusion_rules, Unix created_at, Unix updated_at, account_id, assigned_agent_count, and inbox_capacity_limits. |
Repository does not preload InboxCapacityLimits; response exposes model timestamps and local fields directly; assigned count is not serialized in Chatwoot shape. |
Add a serializer boundary that preloads capacity limits, counts assigned users through account users, and emits Unix timestamps plus inbox_capacity_limits. |
| Inbox capacity limits | Nested inbox_limits#create/update/destroy lives under a current account policy, validates current-account inbox, prevents duplicate inbox assignment, requires conversation_limit >= 0, create returns raw limit JSON, update also emits inbox_name, destroy is 204 No Content. |
Existing InboxCapacityLimit model exists, but no nested handler/service/repo flow is wired; old InboxLimit model/handler has a different path and shape. |
Add repo/service methods for create/update/delete under policy/account scope, duplicate and negative-limit errors, raw create/update serializers, and 204 destroy. |
| Enforcement | Capacity policies and inbox limits must feed assignment and auto-assignment limits before the feature can leave Review. | B5.5c API/data parity and B7 assignment enforcement are done; P4.9 now covers Chatwoot assignment policy payload/binding parity. | Keep manual and automatic assignment enforcement covered by B7 regression tests; reopen only from smoke/reference drift. |
| 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. |
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. | |
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.ParseSearchFilternow understands Chatwoot dashboardsince/untilUnix seconds andfrom=contact:id/from=agent:idsender filters while preserving legacydate_from/date_toandsender_idinputs.- 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_pageis not supplied. - The optional live gate is implemented but not run by default. It requires
GOCHAT_LIVE_MEILI_HOSTand 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_searchnow 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.jsbefore changing Go code. - Keep SLA threshold units in seconds, matching
213bf2band 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.txtandgo run ./cmd/route_paritybefore committing. - Exit commands for the implementation checkpoint should include focused applied-SLA handler/service tests,
go test ./..., andgit 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 side effects, retries, and observable failures; reference audit proves no explicit delayed action params exist. | Review by feat(automation): close delayed action parity; current reference has no delayed action params, P5.12 covers scheduled items, send_email_to_team queues durable team notification jobs, and add_sla applies account-scoped SLA state idempotently |
| 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. | Review; B11.3a preferences, B11.3b playground, B11.3c document sync gate, B11.3d task/tool-call persistence, and B11.3e streaming fallback landed. Remaining durable Copilot/conversation response jobs are P5.11 |
| 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. | Done by test(parity): add frontend smoke harness |
| 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. |
Review; B12.2a API assertion command and B12.2b browser smoke harness landed; live run is environment-dependent |
| B12 | B12.3 | Add enterprise smoke coverage for SLA reports/download, CSAT reports/download, automation/macros, audit/custom roles, capacity, Captain, and Copilot. | Enterprise dashboard clients and reference/chatwoot enterprise screens. |
Enterprise smoke command records pass/fail and names failed route/serializer owners. | Review; B12.3a enterprise API assertions and B12.3b enterprise browser navigation landed. Optional live execution is environment-dependent |
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, andupdatenow return Chatwoot{ payload }envelopes withid,name, stringvisibility,account_id, array-shapedactions, and nestedcreated_by/updated_byagent 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, matchingMacrosController#destroy/#execute. - Macro execute accepts
conversation_ids, resolves them as account-scoped conversation display IDs likeMacrosExecutionJob, and executes real action side effects throughActionService: labels, status, outgoing messages, and private notes are covered by handler tests. - Deeper ActiveStorage-backed macro file attachment behavior stays visible under B9.4 instead of being hidden under this API checkpoint; durable macro execution is covered by P5.5.
B9.2 verification:
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.3 current checkpoint:
- Current
reference/chatwootautomation rule model, listener, action service, enterprise action service, and dashboard action inputs expose no configurable delayed automation action params. No separate delayed automation scheduler is required beyond P5.12 scheduled item/root jobs unless a future reference or smoke pass proves a new param shape. send_email_to_teamnow accepts Chatwoot object-shaped{ team_ids, message }action params, normalizes frontend array values, and queuesautomation:team_email_deliveryjobs when a WorkerPool is configured.- Team-email worker replay reloads the account/rule/conversation scope and delivers generated transcript emails through the existing fakeable mailer boundary; no-worker construction keeps deterministic focused-test behavior.
- Enterprise
add_slanow matches the current referenceEnterprise::ActionService#add_sla: it only applies when the conversation has no SLA, validates an account-scoped policy, setsconversation.sla_policy_id, and creates idempotentAppliedSLAtarget rows. - B9.3 and Phase 5 move to
Review; deeper macro attachment/file parity remains B9.4, and future delayed-action work requires new reference or smoke evidence.
B9.3 verification:
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/handler/api/v1 -run AutomationRule -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation ./internal/handler/api/v1 ./internal/worker ./internal/app -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:
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, andclonenow return the Chatwoot{ payload }envelopes;createreturns the raw automation rule partial;destroyreturns empty200 OK. - The handler accepts Chatwoot frontend conditions with
attribute_key,custom_attribute_type, and operators such asequal_to/not_equal_to, while normalizing to internal operators for matching. - The handler accepts frontend action
action_paramsarrays 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-shapedaction_params, Unixcreated_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_idfrom partial client payloads. - B9.1 remains
Doingbecause trigger coverage, durable execution logs, stop-on-match, webhook retry, and email transcript boundaries are still active B9 follow-ups.
B9.1a verification:
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:
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:
AutomationRuleListenernow follows the current Chatwoot reference listener event set:conversation_created,conversation_updated,conversation_opened,conversation_resolved, andmessage_created;contact_updatedremains 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"], andevent.Data["message"], with account fallback from conversation/message payloads. - Conversation update/status/priority paths now dispatch Chatwoot-style
changed_attributessoattribute_changedautomation conditions can match status, priority, and SLA policy changes. - Conversation creation with an initial message now dispatches
message.createdwithConversationID,ContactID, message payload, and conversation payload; provider webhook persistence is covered by an automation regression proving incoming provider messages triggermessage_createdrules once. - Historical checkpoint note: B9 stayed
Doinghere because B9.2/B9.3 were still active at this point; the active board above now treats B9.3 as Review and keeps only B9.4 macro attachment depth open.
B9.1b verification:
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 JSONaction_resultsfor each action in a matched rule. - Rules whose event matches but conditions do not match now write
skippedexecution records withconditions_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#performrescue-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.
- Historical checkpoint note: B9 stayed
Doinghere because B9.2/B9.3 were still active at this point; the active board above now treats B9.3 as Review and keeps only B9.4 macro attachment depth open.
B9.1c verification:
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 now in Review after B9.3/P5.1-P5.13. | Keep future async gaps evidence-backed under named drift rows, not 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_resultsmetadata. - 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:
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_eventis no longer a no-op. It accepts Chatwoot array params asurl, builds a conversation webhook payload withevent: 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
5xxand429as retryable, records response code/body, and is injectable for tests. send_email_transcriptis no longer a no-op. It parses comma-separated recipients, builds a Chatwoot-style[#display_id] Conversation Transcriptsubject plus transcript body, and delivers per recipient through a fakeable SMTP transcript boundary.ActionService.ExecuteWithResultpreserves the existingExecuteAPI while returning per-action metadata for automation execution logs.AutomationExecution.action_resultsnow includes optionaldelivery_type,target,attempts,response_code,response_body, andretryablefields. Failed external actions still allow later actions/rules to continue under the B9.1c behavior.- B9.1 moves to
Review. Historical next work was B9.2/B9.3; the active board above now records B9.2 and B9.3 in Review and keeps B9.4 as the remaining automation/macro depth row.
B9.1d verification:
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 dashboardauditLogs.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, andaudit_logs, without the local{ success, data, meta }wrapper.- Audit list pagination is fixed to 25 rows per page like
RESULTS_PER_PAGE = 25; clientper_pageoverrides are ignored. - Audit rows are account-scoped through either local
account_idor Chatwoot-styleassociated_type = Accountplusassociated_id = account_id. - Audit list/get now require
administratororsuper_adminrole, 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 explicitauditablekey. Full polymorphicauditable.push_event_datadepth remains a B10.2/B10 writer-depth follow-up where the mutating resources are wired.
B10.1 verification:
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.Recordis now the shared audit writer boundary for account-associated mutations. It recordsaccount_id,associated_type: Account,associated_id,auditable_type,auditable_id,action, JSONaudited_changes, optionaluser_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
AutomationRuleaudit rows, matching the enterprise audit concern that audits automation rules associated with the account. - Macro create/update/delete now write
Macroaudit rows, matching the enterprise macro audit concern. - CustomRole create/update/delete now write
CustomRoleaudit 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
CsatSurveyResponseupdate 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:
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
Inboxaudit rows through the shared writer, matching Chatwoot enterpriseaudited associated_with: :account, on: [:create, :update]for inboxes. - Conversation update/delete/agent assignment/status toggle now write
Conversationaudit 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
SlaPolicyaudit rows with serialized threshold/name fields. - AgentCapacityPolicy create/update/delete now write
AgentCapacityPolicyaudit rows; nested inbox capacity limit create/update/delete and policy user assignment/removal writeInboxCapacityLimitandAgentCapacityPolicyUserrows. - 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:
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:
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, dashboardcustomRole.js, andpermissionsHelper.jswere used as the reference contract.- CustomRole create/update now accepts
custom_role.permissionsas Chatwoot string arrays and stores new rows as JSON arrays while retaining legacy JSON-map reads for old local rows. - CustomRole update is registered for both Rails resource methods (
PATCHandPUT), and explicit emptydescriptionvalues clear the field while omitted descriptions are preserved. - CustomRole list/show/create/update now return raw Chatwoot custom-role serializers with array
permissionsand no local{ success, data, meta }envelope; destroy returns empty200 OK. - CustomRole admin gates now require administrator/super_admin role and focused handler tests cover non-admin denial.
AccountUser.roleremainsagentfor custom-role users.custom_role_id > 0drivesHasCustomRole, RBAC policy construction, AccountScope custom-role permission loading, and profile account payload permissions.- Profile account payloads now include enterprise
custom_role_id,custom_role, andpermissionsvalues matching Chatwoot's enterprise account-user partial andAccountUser#permissionsextension. - CustomRole delete clears related
account_users.custom_role_idand keeps those users as agents, matchingdependent: :nullifybehavior. - Migration
000023_align_custom_role_permissionsupdates the default permission storage from{}to[]for new deployments and existing empty rows.
B10.3 verification:
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 run ./cmd/dump_routes > docs/parity/gochat_routes.txt
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity
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, andenterprise/app/models/enterprise/account/plan_usage_and_limits.rbwere used as the reference contract.model.Account.InboxLimitand migration000024_add_account_inbox_limitnow represent Chatwootusage_limits[:inboxes]; local0keeps the community/unlimited default behavior.InboxService.EnsureCanCreateInboxchecks the current account andInboxRepo.CountByAccountbefore persistence;ErrInboxLimitExceededcarries the exact Chatwoot messageAccount limit exceeded. Upgrade to a higher plan.- Generic inbox create maps the sentinel error to HTTP
402with{ "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_linerow 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
InboxCapacityLimitremains separate assignment-capacity data and is not used as the account create-limit source.
B10.4 exit commands:
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. | Done by feat(captain): align task payload persistence |
| 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. | Done by feat(captain): align streaming fallbacks |
B11.1a current checkpoint:
- Reference files inspected for this landing slice:
reference/chatwoot/config/routes.rb:62-89, enterprise Captainassistants_controller.rb,inboxes_controller.rb, assistant/inbox Jbuilder views, dashboardassistant.js/inboxes.js, andreference/chatwoot/config/agents/tools.yml. - Assistant list now returns
{ payload: [...], meta: { total_count, page: 1 } }; show/create/update return the raw assistant serializer with Unixcreated_at/updated_at, JSONconfig,guardrails, andresponse_guidelines; delete returns204 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/toolsreturns the raw built-in tool array from the current referencetools.ymlcontract 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 returns204for account-scoped delete. - Route dump/parity were regenerated after the router handler remap; external route count remains
TOTAL: 830and 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, dashboardscenarios.js, andcustomTools.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 omitsslug; list returns{ payload, meta }; show/update/delete are account-scoped; single-resource mutations return the raw custom-tool serializer with Unix timestamps and JSONauth_config/param_schemavalues. - 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 dashboarddocument.js,response.js,bulkActions.js, andcustomTools.jsclients. - Document create accepts nested
{ document: ... }withassistant_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 returns204, and sync returns202 Acceptedwhile markingsync_statusas frontend-visiblesyncingthrough 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, markeditedwhen question or answer changes, scope all mutations to account, and delete with204. - Bulk actions accept Chatwoot
{ type, ids, fields: { status } }:AssistantResponsesupportsapproveanddelete;AssistantDocumentsupportssyncanddelete; invalid requests return422 { success: false }. - Custom-tool
testnow 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, dashboardcopilotThreads.js,copilotMessages.js, and the Copilot Vue/store callers. - Thread create now accepts Chatwoot
{ message, assistant_id, conversation_id }, rejects blank messages with422 { 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 containinguser,assistant,account_id, and Unixcreated_at. - Nested
copilot_messageslist/create are scoped through the current user's thread, return{ payload }lists ordered ascending and raw message create payloads, and include embeddedcopilot_threadpush data for the reused frontend. GetThread/DeleteThreadremain available for local compatibility but now use the same account/user scoping; delete removes thread messages and returns204.- 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, dashboardapi/captain/preferences.js, and storecaptain/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_modelsandcaptain_featuresJSON fields now persist the same settings Chatwoot stores throughAccount#settings; updates merge partial incoming maps with existing account settings. - Invalid model selections return
422 { error }, non-admin updates return401, and agents can still read the preferences payload. - Route dump was regenerated and remains
TOTAL: 830; route parity remains270 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, dashboardapi/captain/assistant.js, andcomponents-next/captain/assistant/AssistantPlayground.vue. - Playground now accepts the exact frontend body with top-level
message_contentandmessage_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: ... }, matchingCaptain::Llm::AssistantChatService;captain_integration_v2returns raw{ response: ... }, matchingAgentRunnerService. - 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. |
Done by feat(captain): gate document sync backend; remaining crawl/embedding jobs are tracked in P5.11. |
| 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. | Done by feat(captain): align task payload persistence. |
| 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. | Done by feat(captain): align streaming fallbacks; B11 is in Review. |
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. CaptainDocumentServicenow exposes a fakeableCaptainDocumentSyncBackendboundary andSyncDocumentByAccountworker-style entry point, separate from the controller's immediate202 Acceptedqueue marker.- Missing sync backend marks the document
failedwithlast_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 asaccess_deniedare 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.rbfor route actions, request params, andrender_resultresponse rules.reference/chatwoot/app/javascript/dashboard/api/captain/tasks.jsandreference/chatwoot/app/javascript/dashboard/composables/useCaptain.jsfor 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, andfollow_up_service.rbfor operation names, disabled/error behavior, follow-up context, and event names.reference/chatwoot/enterprise/app/models/copilot_message.rbfor reloadable Copilot message JSON keys.
B11.3d exit commands:
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.3d current checkpoint:
- Reference files inspected for this slice:
reference/chatwoot/app/controllers/api/v1/accounts/captain/tasks_controller.rb, dashboardapi/captain/tasks.js,useCaptain.js,Captain::BaseTaskService,RewriteService,SummaryService,ReplySuggestionService,LabelSuggestionService,FollowUpService, and enterpriseCopilotMessage. rewrite,summarize, andreply_suggestionnow accept Chatwoot frontendconversation_display_idpayloads, keep legacy local IDs only as service compatibility, and resolve conversations through account-scoped display-ID lookup.- Task handlers now return raw Chatwoot
{ message, follow_up_context }success payloads and422 { error }disabled/error payloads instead of the localresponse.OKenvelope. - Missing LLM provider returns the deterministic raw
Captain is disabledstate for task routes instead of panicking or producing500. - Rewrite validates Chatwoot operations:
fix_spelling_grammar,improve,casual,professional,friendly,confident, andstraightforward; invalid operations return422. - Label suggestion and follow-up POST routes now consume the reused dashboard JSON bodies. Follow-up validates allowed event names, appends user/assistant turns to
conversation_history, updateslast_response, and preserveschannel_type. - Task outputs persist to
copilot_suggestion_messagesby account and resolved conversation so reloadable frontend state survives beyond the request. CopilotMessagenow rejects JSON keys outside Chatwoot's allowed set:content,reasoning,function_name, andreply_suggestion.- Focused handler/service/repository fixtures cover raw no-envelope payloads, display-ID lookup, no-provider disabled state, suggestion persistence, follow-up history updates, and Copilot message key validation.
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. |
B11.3e current checkpoint:
- Captain
reply_suggestion/stream,summarize/stream, andrewrite/streamnow use the same frontend DTOs as their non-streaming task routes, includingconversation_display_id,operation, and legacy ID fallback only inside the service adapter. - Stream services now guard missing LLM providers with
422 Captain is disabledtask errors instead of nil-provider panics. - Stream handlers convert known task errors into deterministic SSE
errorevents containingerror,status, anddone, followed by adoneevent. Invalid request bodies and account IDs use the same frontend-readable SSE shape. - Stream conversation lookup is account-scoped through
FindByAccountAndDisplayIDOrID; reply suggestion assistant lookup is also account-scoped and optional like the non-stream route. - Rewrite stream validates the Chatwoot operation list and includes conversation context for
improvewhenconversation_display_idis provided. - Copilot message serializers now embed the nested
copilot_threadusing ChatwootCopilotThread#push_event_datashape without REST-onlyassistant, and dedicated push payload helpers matchCopilotMessage#push_event_databy omitting top-levelaccount_id. - The legacy Copilot SSE route now returns deterministic
Captain is disabledSSE output when no LLM provider is configured after thread/account validation.
B11.3e verification:
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/handler/api/v1 -run 'CaptainTask|Captain|Copilot|SSE' -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 TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
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:
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/. |
Done by scripts/parity_frontend_smoke.sh, cmd/gochat seed, and docs/parity/frontend_smoke_report.md |
| B12.2 | Cover login, current user, inbox list, conversation list/detail, message send, contact/company view, widget init/message, and public CSAT. | Dashboard/widget frontend routes and API clients. | Smoke output records pass/fail and links failed API calls to route/serializer tasks. | Review; API and browser harnesses are repeatable, live execution requires running GoChat/Vite |
| 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. | Review; B12.3a API assertions and B12.3b reused-frontend browser route navigation landed, live run is environment-dependent |
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 optional live smoke does not block code commits if the failure is named, scoped, and tracked; hidden failures block moving B12 to Done. |
B12 stays in Review until live pass/fail results are recorded or every environment-dependent gap has an owner. |
B12.1 current checkpoint:
cmd/gochatis the canonical local entrypoint for smoke runs.gochat serveboots the existing app, andgochat seedcreates deterministic admin/account/inbox/contact/conversation data plus SLA, CustomRole, AgentCapacity, and Captain fixtures.scripts/parity_frontend_smoke.sh --checkverifies local command prerequisites, validatescmd/gochat, and writesdocs/parity/frontend_smoke_report.mdwithout requiring live PostgreSQL/Redis/Meilisearch/frontend boot.scripts/parity_frontend_smoke.sh --printprints the exact GoChat and reused Chatwoot frontend commands.scripts/parity_frontend_smoke.sh --boot-onlystarts GoChat plusreference/chatwootVite and verifies backend/healthplus frontend HTTP readiness. The default backend command is Meilisearch-first viaGOCHAT_SEARCH_ENGINE=meilisearch; DB fallback is only opt-in throughGOCHAT_SMOKE_SEARCH_ENGINE=dbfor local debugging.- B12.1 itself is a readiness checkpoint. B12.2 and B12.3 below add the repeatable API/browser assertions and enterprise route coverage; optional live execution still depends on the full local service stack.
B12.1 verification:
bash -n scripts/parity_frontend_smoke.sh
scripts/parity_frontend_smoke.sh --print
scripts/parity_frontend_smoke.sh --check
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./cmd/gochat -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
B12.2a current checkpoint:
scripts/parity_frontend_smoke.sh --api-smokenow runs against a live GoChat backend. It seeds deterministic data, logs in with/auth/sign_in, captures DeviseTokenAuth-compatible headers, and exercises the frontend-critical API paths without frontend adapters.- Covered API assertions:
/auth/validate_token,/api/v1/profile, inbox list, conversation list/messages/message create, contact show, company show, widget config, widget message create, and public CSAT show. cmd/gochat seednow emitscompany_id,conversation_display_id, andcsat_message_id; it also creates a deterministic company relation and aninput_csatmessage so B12 API smoke can verify CRM and public CSAT surfaces.- This is the core API assertion checkpoint. B12.2b below adds reused Vite browser navigation, and B12.3 adds enterprise API/browser coverage.
B12.2a verification:
bash -n scripts/parity_frontend_smoke.sh
scripts/parity_frontend_smoke.sh --print
scripts/parity_frontend_smoke.sh --check
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./cmd/gochat -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
Optional live API smoke, when PostgreSQL/Redis/Meilisearch and GoChat are running:
scripts/parity_frontend_smoke.sh --api-smoke
B12.2b current checkpoint:
scripts/parity_frontend_smoke.sh --browser-smokeruns the B12.2a API smoke first, verifies the reused Chatwoot Vite frontend is reachable, then launches headless Chrome through a dependency-free DevTools protocol driver.scripts/parity_frontend_browser_smoke.mjscreates temporary Vite-served HTML shells underreference/chatwoot/tmp/that injectwindow.chatwootConfig.apiHost,window.globalConfig, and the exact Chatwootv3appanddashboardentrypoints. It drives the login form, waits for the Chatwootcw_d_session_infocookie, then loads the dashboard route and waits forauth/validate_tokenplus account conversation requests.- Browser captures are written to
.tmp/frontend-smoke/browser-smoke-report.json; API payload captures remain under.tmp/frontend-smoke/. - This makes B12.2 browser navigation repeatable without adding Playwright or frontend adapters. Live execution still requires PostgreSQL, Redis, Meilisearch, GoChat, and Chatwoot Vite to be running.
B12.2b verification:
bash -n scripts/parity_frontend_smoke.sh
node --check scripts/parity_frontend_browser_smoke.mjs
scripts/parity_frontend_smoke.sh --print
scripts/parity_frontend_smoke.sh --check
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
Optional live browser smoke, when PostgreSQL/Redis/Meilisearch, GoChat, and Chatwoot Vite are running:
scripts/parity_frontend_smoke.sh --browser-smoke
B12.3a current checkpoint:
scripts/parity_frontend_smoke.sh --enterprise-smokenow layers enterprise API assertions on top of the B12.2a core API smoke.- Covered enterprise paths: SLA applied report list/metrics/download, public CSAT submit, account CSAT report list/metrics/download, automation rule create/list, macro create/execute, custom role list, agent capacity policy list/users, audit log list, Captain preferences/assistants, and Copilot thread/message payloads.
- Captures are written under
.tmp/frontend-smoke/enterprise_*, keeping failed enterprise API calls inspectable before browser screen automation is added. - Live execution still requires PostgreSQL, Redis, Meilisearch, and GoChat. It does not require Chatwoot Vite unless B12.3b browser screen navigation is being run.
B12.3a verification:
bash -n scripts/parity_frontend_smoke.sh
scripts/parity_frontend_smoke.sh --print
scripts/parity_frontend_smoke.sh --check
Optional live enterprise API smoke, when PostgreSQL/Redis/Meilisearch and GoChat are running:
scripts/parity_frontend_smoke.sh --enterprise-smoke
B12.3b current checkpoint:
scripts/parity_frontend_smoke.sh --enterprise-browser-smokenow runs the enterprise API smoke, verifies the reused Chatwoot Vite frontend, and launches the same dependency-free Chrome DevTools browser driver in enterprise mode.scripts/parity_frontend_browser_smoke.mjs --enterprisecreates route-specific dashboard shells for Chatwoot enterprise routes and asserts browser-origin requests for/applied_slas,/csat_survey_responses,/automation_rules,/macros,/audit_logs,/custom_roles,/agent_capacity_policies,/captain/preferences,/captain/assistants, and/captain/copilot_threads.cmd/gochat seednow enables the enterprise feature flags that the reused Chatwoot route guards expect: SLA, reports, automations, macros, audit logs, custom roles, assignment v2, advanced assignment, Captain, Captain v2/tasks/custom tools, CRM, companies, and inbox management.- Browser captures are still written to
.tmp/frontend-smoke/browser-smoke-report.json. Live execution requires PostgreSQL, Redis, Meilisearch, GoChat, Chatwoot Vite, and Chrome.
B12.3b verification:
bash -n scripts/parity_frontend_smoke.sh
node --check scripts/parity_frontend_browser_smoke.mjs
scripts/parity_frontend_smoke.sh --print
scripts/parity_frontend_smoke.sh --check
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./cmd/gochat -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
Optional live enterprise browser smoke, when PostgreSQL/Redis/Meilisearch, GoChat, Chatwoot Vite, and Chrome are running:
scripts/parity_frontend_smoke.sh --enterprise-browser-smoke
Hermes plan material now mapped:
.hermes/plans/2025-05-24-global-search-meilisearch.mdmaps 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.mdmaps 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. | Review |
| 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, widget init/message, public CSAT, and key enterprise routes have repeatable API/browser smoke commands. | Review |
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. | Review |
| 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. | Review |
| M6 | Durable jobs and external integrations. | Search indexing, CSAT send, automation actions, notifications, webhooks, and external deliveries are queued, retryable, logged, and idempotent. | Review |
| 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. | Review |
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. | Review |
| B12 | Frontend smoke harness. | reference/chatwoot frontend. |
Repeatable smoke command and checked gap report. | Review; B12.1 boot harness, B12.2 API/browser assertions, and B12.3 enterprise API/browser assertions landed. Optional live runs remain environment-dependent |
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, assignment policy CRUD/inbox binding payload parity, 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. | Durable SLA job scheduling remains Phase 5; future assignment policy work should come only from B12 smoke or fresh reference evidence. | Review after feat(assignment-policies): align chatwoot payloads; move to Done only after B12 confirms enterprise assignment/SLA screens or any smoke gaps are split into owned follow-ups. |
| 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, B9.2 macro frontend CRUD/execute side effects, and B9.3 delayed-action reference closure plus team email/SLA action parity. | Deeper macro attachment/file parity remains B9.4; future delayed automation action work requires new reference or smoke evidence. | Review after feat(automation): close delayed action parity; move to Done only after B9.4 is 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.3e: Captain Assistant CRUD, inbox binding, scenarios, documents, responses, custom tools, Copilot threads/messages, Captain preferences, playground, document sync gates, task/tool-call payload persistence, stream disabled-state SSE, and Copilot push payload fixtures. | Reused frontend smoke remains B12; provider-specific durable realtime/LLM delivery can move to Phase 5 if smoke exposes it. | Review after B11.3e; move to Done only after B12 proves reused frontend Captain/Copilot screens or names any remaining provider/deployment follow-ups. |
| B12 | B12.1 boot/readiness, B12.2 core API/browser assertions, and B12.3 enterprise API/browser route assertions landed. | Optional live smoke runs in a full PostgreSQL/Redis/Meilisearch/Vite/Chrome environment must map every failure to an owning slice. | Move to Done only after live-smoke pass/fail results are recorded and every failure is owned. |
Per-slice documentation rule:
| Checkpoint type | Required doc update |
|---|---|
| Implementation commit | Add a commit ledger row, update the active task board status, add or refresh the current checkpoint bullets, and record verification commands. |
| Route change | Regenerate docs/parity/gochat_routes.txt; regenerate docs/parity/route_parity.md when tracked route sets change; update route counts in Current Baseline. |
| Serializer or request contract change | Link the reference/chatwoot controller/view/frontend client and add the fixture or handler test name that proves the contract. |
| Enterprise feature checkpoint | Update both the enterprise tracking table and the relevant B7-B11 remaining-slice row so the paid-feature scope stays visible. |
| Deferred gap | Keep the parent slice in Review or Doing; add a named follow-up instead of silently marking Done. |
Phase 0: Test And Route Baseline
Status: done.
Checklist:
- Fix
internal/handler/api/v1route param mismatches around:account_id,:id, and resource IDs. - Fix malformed test routes that conflict with Gin wildcard rules.
- Add defensive handling for zero-value services in handler edge tests.
- Align handler error responses where tests encode the expected Chatwoot-compatible status category.
- Keep platform routes bootable and dumpable.
- Verify
go test ./.... - Verify
go run ./cmd/dump_routes.
Verification commands:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes
Phase 1: Meilisearch Search Engine
Status: review.
Source material:
.hermes/plans/2025-05-24-global-search-meilisearch.mdreference/chatwootsearch controllers, models, and indexing behavior- Current local packages under
internal/search,internal/handler/api/v1/search_handler.go, and entity repositories
Checklist:
- Define a stable
SearchEngineinterface for Meilisearch-backed search and indexing. - Add search config for engine, host, API key, and index prefix.
- Implement Meilisearch client wrapper with index bootstrapping and settings.
- Define per-entity documents for conversations, messages, contacts, companies, articles, and help-center content as required by Chatwoot frontend behavior.
- Wire create/update/delete hooks from services into indexing.
- Add batch reindex command for existing data.
- Keep DB search only as explicit development fallback, not as final production mode.
- Add tests with a mocked search engine and integration hooks that can run without a live Meilisearch instance.
- Document required Meilisearch environment variables and local startup flow.
Acceptance:
- Global search endpoint and entity search endpoints return Chatwoot-compatible payloads.
- Search results are account-scoped.
- Search indexing survives entity updates and deletes.
go test ./...stays green.
Tracking table:
| ID | Task | Target files | Reference source | Status |
|---|---|---|---|---|
| P1.1 | Add SearchConfig with engine, host, API key, index prefix, env binding, and defaults. |
internal/config/config.go, configs/config.yaml, config tests |
.hermes/plans/2025-05-24-global-search-meilisearch.md |
Done |
| P1.2 | Add stable engine contract for search, indexing, deletes, batch indexing, and close. | internal/search/engine.go |
Chatwoot global/entity search behavior | Done |
| P1.3 | Implement Meilisearch engine wrapper, index naming, bootstrap, sortable/filterable/searchable settings. | internal/search/engine_meili.go |
Chatwoot search models/services | Review |
| P1.4 | Keep existing DB search as explicit dev fallback only. Production config must prefer Meilisearch. | internal/search/engine_db.go, internal/search/search_service.go, internal/config/validator.go, cmd/reindex_search |
User decision on Meilisearch | Done |
| P1.5 | Define documents and serializers for conversations, messages, contacts, companies, articles, and help-center content. | internal/search/engine.go |
reference/chatwoot models/serializers |
Review |
| P1.6 | Wire create/update/delete hooks from entity services into async or synchronous indexing boundary. | internal/service/*, internal/search/search_service.go, internal/app/bootstrap.go |
Chatwoot callbacks/jobs | Done |
| P1.7 | Add batch reindex command and account/entity filters. | cmd/reindex_search |
Chatwoot reindex/search tasks | Done |
| P1.8 | Add mocked engine tests and service integration tests without requiring live Meilisearch. | internal/search/engine_test.go, internal/search/engine_meili_live_test.go, internal/config/config_test.go |
Existing test style | Done |
| P1.9 | Document Meilisearch env vars and local startup flow. | this doc, ops docs if needed | Hermes plan | Done |
Meilisearch local flow:
docker run --rm -p 7700:7700 -e MEILI_MASTER_KEY=gochat_dev getmeili/meilisearch:latest
GOCHAT_SEARCH_ENGINE=meilisearch GOCHAT_SEARCH_HOST=http://localhost:7700 GOCHAT_SEARCH_API_KEY=gochat_dev go run ./cmd/reindex_search -types all
Search environment variables:
| Variable | Default | Notes |
|---|---|---|
GOCHAT_SEARCH_ENGINE |
meilisearch |
Use db only for explicit local fallback. |
GOCHAT_SEARCH_HOST |
http://localhost:7700 |
Meilisearch endpoint. |
GOCHAT_SEARCH_API_KEY |
empty | Set to Meilisearch master/search key when enabled. |
GOCHAT_SEARCH_INDEX_PREFIX |
gochat_ |
Prefixes indexes such as gochat_conversations. |
GOCHAT_SEARCH_TIMEOUT_SECONDS |
5 |
HTTP timeout for search/index requests. |
Phase 2: Route And Controller Parity Audit
Status: doing.
Checklist:
- Dump Chatwoot route source declarations from
reference/chatwoot/config/routes.rb. - Dump GoChat routes with
cmd/dump_routes. - Build a first tracked route parity table covering method, path, controller, source, and route status.
- Prioritize first frontend-critical API v1 account routes used by the Chatwoot web app.
- Extend parity table with auth scope, request params, response serializer, and handler implementation status.
- Patch first-batch route names and wildcard params where Gin constraints require different internal names, while preserving external URLs.
- Add regression tests for the contact conversations relation route added in this slice.
- Expand route boot regression coverage for tracked Gin wildcard/param conflict groups.
Acceptance:
- Route gap report is generated and checked in.
- All currently implemented routes boot without panic.
- Missing frontend-critical routes have tickets or implementation tasks.
Tracking table:
| ID | Task | Artifact | Status |
|---|---|---|---|
| P2.1 | Generate Chatwoot route dump from reference/chatwoot. |
docs/parity/chatwoot_routes_static.md |
Review |
| P2.2 | Generate GoChat route dump with cmd/dump_routes. |
docs/parity/gochat_routes.txt |
Done |
| P2.3 | Produce route parity table: method, path, controller/handler, auth, request params, serializer, status. | docs/parity/route_parity.md |
Review |
| P2.4 | Mark frontend-critical gaps from Chatwoot web app route usage. | docs/parity/route_parity.md |
Done |
| P2.5 | Convert existing placeholders/stubs into tracked feature tasks instead of hidden debt. | this doc and docs/parity/placeholder_audit.md |
Review |
| 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 |
| P2.12 | Track help-center portal/category/article routes from routes.rb:385-404, including article bulk actions. |
cmd/route_parity, help-center handlers |
Done |
Current Phase 2 route findings:
| Type | Count | Required action |
|---|---|---|
| Exact tracked critical routes | 298 | 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. |
| Help center | Portal CRUD/member routes, nested category CRUD/reorder, nested article CRUD/reorder, and article bulk actions from routes.rb:385-404. |
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 |
Chatwoot /assignment_policies and inbox assignment policy routes now exist with no-trailing-slash aliases; local /assignment_policies_v2 remains for compatibility. |
Tracked route set is 0 missing and payload/binding behavior is in Review by feat(assignment-policies): align chatwoot payloads. |
| 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. |
| Help center | reference/chatwoot/config/routes.rb:385-404 |
GoChat already exposed the portal/category/article dashboard routes after P3.7a-P3.7c. | Added the full help-center route family to the tracked critical set; route parity remains 0 missing. |
Closed tracked critical route gaps in this slice:
| Method | Path | Chatwoot controller | Implementation note |
|---|---|---|---|
| GET | /api/v1/accounts/:account_id/contacts/:contact_id/conversations |
api/v1/accounts/contacts/conversations#index |
Added contact conversation relation endpoint returning Chatwoot payload shape, optional inbox_id, and latest-20 ordering. |
| POST | /api/v1/accounts/:account_id/contacts/export |
api/v1/accounts/contacts#export |
Added Chatwoot async request path returning 200 OK; existing CSV download route remains for local compatibility. |
| POST | /api/v1/accounts/:account_id/conversations/:conversation_id/toggle_priority |
api/v1/accounts/conversations#toggle_priority |
Added Chatwoot action path returning 200 OK. |
Closed Rails update method gaps:
| Expected | Existing GoChat route | Implementation note |
|---|---|---|
PUT /api/v1/accounts/:account_id/conversations/:conversation_id |
PATCH /api/v1/accounts/:account_id/conversations/:conversation_id |
Added PUT alias to the same update handler. |
PUT /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id |
PATCH /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id |
Added PUT alias to the same update handler. |
Phase 2 commands:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity
Phase 3: Data And Serializer Parity
Status: review.
Checklist:
- Compare key Chatwoot serializers/entities with Go response payloads.
- Align account, user, inbox, conversation, message, contact, company, team, label, canned response, campaign, webhook, 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, Devise password reset, and confirmation. | Review; profile account permission arrays, conditional hmac_identifier, and user notification-settings raw payloads now match Chatwoot role/custom-role semantics, widget script helper, and dashboard settings store expectations. |
| P3.2 | Accounts/users/teams | Account settings, users, agents, teams, team members, invitations, roles, permissions. | Doing; account show/update/create/cache-key/active-at plus agents/teams/team-member, assignable-agent settings payloads, profile account permission arrays, platform account-user raw/idempotent membership, platform user raw serializer/create-login-token/update-delete semantics, AccountUser inviter_id persistence/audit naming, agents _agent serializer fields, order_by_full_name ordering with Chatwoot no-pagination index behavior, agents bulk invite best-effort behavior, and agent update PATCH/auto_offline/validation semantics now have focused Chatwoot-style coverage. |
| P3.3 | Inboxes/channels | Inbox CRUD, assignable agents, avatars, channel config, business hours, widget config. | Review; assignable-agent payload drift closed, broader inbox serializer drift remains evidence-driven. |
| 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, avatars, and shared attachments. | Doing; company multipart avatar create/update and fixed 25-row company pagination now match the reused dashboard companies store/API path. |
| P3.6 | Labels/custom attributes/custom filters | Create/update/list behavior and exact response shapes. | Doing; label CRUD is in Review with Chatwoot title/description/color/show_on_sidebar payloads and raw/list response contracts, while custom attributes/custom filters remain evidence-driven follow-ups. |
| P3.7 | Notifications/reports/help center/campaigns | Frontend-visible payloads and pagination/error envelopes. | Doing; notifications, reports, account campaign CRUD/scheduling, and help-center portal/category/article routes/payloads now have focused Chatwoot payload fixtures. Continue only with fresh reference/smoke drift. |
| P3.8 | Widget/public APIs | Widget init, campaigns, config, contact, conversations, messages, direct uploads, public inbox flow, public CSAT, public help-center portal/article/category JSON. | Review for current widget/public evidence; public help-center portal show/default-locale, popular-article lists, and category list/show JSON now match the inspected public routes. Reopen only from fresh widget/public smoke or reference evidence. |
| P3.9 | Search payloads | Global search and entity search documents backed by Meilisearch. | Review |
| P3.10 | Account webhooks/integration settings | Account webhook CRUD, integration app/hook payloads, event subscriptions, secrets, and delivery backing rows. | Review; reused dashboard Webhooks settings flow now gets Chatwoot { payload } list/mutation serializers, PATCH update, nested { webhook } bodies, generated secrets, scoped mutations, and empty 200 OK delete. P3.11 integration apps/hooks now also match the reused dashboard integrations list/create/delete path with raw app/hook serializers, app_id/reference_id, settings schemas, account-scoped hook mutations, and empty delete. |
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*, enterprise companies* |
fixture tests for list/show/search/merge/relation/shared attachment/avatar payloads | Doing; contact shared attachments, conversation shared-file payloads, company multipart avatar form bodies, and fixed company pagination now have focused Chatwoot-style coverage. |
| 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 | Review; list envelope, includes filters, unread counts, read_all, unread/snooze/update raw responses, and destroy_all read/all are covered. |
| 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 | Review |
| S9 | Account webhooks | reference/chatwoot/app/controllers/api/v1/accounts/webhooks_controller.rb, account webhook Jbuilder views, dashboard webhooks store/API |
fixture tests for webhook list/create/update/delete payloads | Review |
Serializer comparison rules:
- Compare against
reference/chatwootserializers/entities before changing Go responses. - Prefer fixture-driven tests for exact JSON shape, enum strings, pagination metadata, and error envelopes.
- Preserve Chatwoot field names even if Go internal naming differs.
Phase 4: Enterprise Feature Completion
Status: doing.
Excluded:
- SSO
- SAML
- LDAP
- OIDC
Included checklist:
- SLA policies and SLA event tracking.
- Audit logs and admin-readable audit endpoints.
- Custom roles and permission checks.
- Agent capacity and assignment limits.
- Assignment policies and auto-assignment compatibility.
- Captain/Copilot assistant, custom tools, scenarios, documents, responses, inbox bindings, preferences, Copilot threads/messages, tasks, and stream disabled-state compatibility. Provider-specific LLM depth remains feature-gated and B12/Phase 5 can name any deeper frontend or dispatch gaps.
- CSAT survey response flow, metrics, filters, and review notes.
- Inbox limits and account/inbox usage enforcement.
- Automation rules, macros, execution logs, and action side effects.
Acceptance:
- Each included feature has routes, persistence, authorization, tests, and Chatwoot-compatible response behavior.
- Unsupported SSO family features are explicitly disabled or omitted without breaking frontend navigation for enabled features.
Enterprise tracking table:
| ID | Feature | Existing Go surface | Required next work | Status |
|---|---|---|---|---|
| P4.1 | SLA policies/events | internal/model/sla_policy.go, internal/model/sla_event.go, internal/service/sla_policy_service.go, internal/service/applied_sla_service.go, internal/service/sla_event_service.go, internal/handler/api/v1/sla_policy_handler.go |
SLA policy CRUD, applied-SLA conversation attach, idempotent core breach lifecycle, applied-SLA reports, current reference timing review, and SLA miss notifications are implemented; durable job scheduling remains Phase 5. | Review |
| P4.2 | Audit logs | internal/model/audit.go, internal/service/audit_service.go, internal/repository/audit_repo.go, internal/handler/api/v1/audit_handler.go |
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, document sync gates through B11.3c, task/tool-call payload persistence through B11.3d, stream/realtime fallback payloads through B11.3e, and durable P5.11 Captain/Copilot response jobs are aligned. | Review |
| 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, download CSV, and channel-specific template job hooks are aligned. | 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, delayed-action reference closure, team-email and SLA actions, and macro CRUD/display-ID execution parity are implemented; deeper macro attachment/file parity remains B9.4. | Review |
| P4.9 | Assignment policies | internal/model/assignment_policy.go, internal/repository/assignment_policy_repo.go, internal/service/assignment_policy_service.go, internal/handler/api/v1/assignment_policy_handler.go, internal/router/router.go, plus legacy internal/autoassignment/* |
Chatwoot assignment policy CRUD, raw serializers, request wrappers, inbox policy binding/list/delete, assigned inbox counts, and no-trailing-slash routes are implemented; keep in Review until B12 proves the reused enterprise settings flow or opens named drift. | Review |
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. | Assignment policy API and inbox bindings match Chatwoot payloads by feat(assignment-policies): align chatwoot payloads; manual and automatic assignment respect availability, team/inbox membership, and limits by a98dc2c. |
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, transcript, team-email, and SLA actions. | B9.1a CRUD, B9.1b listener coverage, B9.1c execution outcome logging, B9.1d retryable webhook/email transcript delivery, and B9.3 current-reference delayed-action closure plus team-email/SLA action coverage are covered. | 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, durable response jobs. | 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; task/tool-call payload persistence is covered by feat(captain): align task payload persistence; stream disabled states and push payload shapes are covered by feat(captain): align streaming fallbacks; durable document/Copilot/conversation jobs are covered by P5.11 checkpoints through feat(captain): queue copilot response jobs. |
Review |
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 | Chatwoot assignment policy route and payload shapes match the reused frontend; inbox policy binding/list/delete is implemented; policy selection, availability, and capacity interact correctly or are reopened from B12/reference evidence. | 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: doing; this is the current active implementation slice.
Default implementation decision for tracking: use a PostgreSQL-backed durable job table first, with Redis used only for optional locks/rate limiting where existing code already depends on it. This keeps job state inspectable in the Go backend, avoids adding a new hard runtime beyond the already-required database, and still allows Chatwoot-style retry, scheduling, idempotency, and observability. If a specific reference behavior requires Redis timing semantics, record that under the owning P5 task before implementation.
Reference job families to keep in scope:
- Core dispatcher jobs:
reference/chatwoot/app/jobs/event_dispatcher_job.rb,action_cable_broadcast_job.rb,hook_job.rb,webhook_job.rb, and provider webhook/event jobs. - Conversation/message jobs:
send_reply_job.rb,bulk_actions_job.rb,macros_execution_job.rb,conversations/resolution_job.rb,conversations/reopen_snoozed_conversations_job.rb,conversations/update_message_status_job.rb, and activity/mention jobs. - CRM/import/export jobs:
account/contacts_export_job.rb, company/avatar/label jobs, and migration/backfill jobs that affect frontend-visible data. - Enterprise SLA jobs:
enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb,process_account_applied_slas_job.rb, andprocess_applied_sla_job.rb. - Captain/Copilot jobs: document schedule/perform/crawl/response-builder jobs, embedding update jobs, conversation response builder, and Copilot response jobs.
- Channel template/status jobs: WhatsApp/Twilio template sync, CSAT template delivery, delivery status, and provider-specific outbound/inbound jobs.
Known hotspots:
internal/worker/worker.gohas the committed P5.1 durable core and is already wired to dispatcher, search indexing, automation delivery, macro fan-out, CSAT survey/template sends, SLA scans, contact exports, Captain document sync, and conversation maintenance jobs.internal/service/message_delivery_worker.goandinternal/handler/webhook/incoming_persister_jobs.gonow cover P5.10 SendReplyJob-style outbound delivery plus provider delivery-status/read-receipt jobs.internal/handler/webhook/*and provider services now keep verification/parsing in the HTTP path whileIncomingPersistermoves normalized inbound persistence/dispatch behind durable jobs when the WorkerPool is configured.internal/service/captain_*andinternal/service/copilot_*now cover Captain document sync, crawl/parser, schedule-sync, response-builder, embedding-update, Copilot response, and Captain conversation response-builder jobs.internal/service/analytics_service.go,internal/service/analytics_query_helpers.go, andinternal/service/reporting_rollup_worker.gonow own P5.13 analytics aggregation, lazy rollup freshness, and durable day rollup replay.
Checklist:
- Add
background_jobspersistence with job type, payload, queue, status, attempt counters, scheduled/locked timestamps, idempotency key, last error, and completion timestamps. - Replace
internal/worker/worker.goplaceholder with enqueue, schedule, perform, retry/backoff, dead-letter, and graceful shutdown behavior. - Map the committed Chatwoot job/listener families to Go worker responsibilities in the tracking table.
- Finish durable job dispatch for delayed automation actions if current-reference params require it; current reference has no delayed action params, and remaining automation side effects are queued or explicitly covered.
- Add retry and failure logging for the remaining external provider calls.
- Add tests for the remaining enqueueing, uniqueness/idempotency, delayed execution, retries, worker restart pickup, and fakeable external side effects.
Acceptance:
- User-visible side effects do not depend on synchronous handler-only execution.
- Failed background work is observable and retryable.
Tracking table:
| ID | Task | Reference source | Current Go hotspot | Done boundary | Status |
|---|---|---|---|---|---|
| P5.1 | Implement durable worker core and job model. | reference/chatwoot/app/jobs/application_job.rb, mutex_application_job.rb |
internal/worker/worker.go, internal/model/background_job.go, migrations/000026_add_background_jobs.*.sql |
Job table, worker persistence API, enqueue API, worker loop, retry/backoff, scheduled jobs, mutex/idempotency keys, dead-letter state, and restart pickup tests exist. | Review by feat(worker): add durable background jobs |
| P5.2 | Route async dispatcher events through durable jobs. | event_dispatcher_job.rb, Chatwoot async dispatcher listeners |
internal/dispatch/dispatcher.go, internal/channel/dispatcher.go |
Heavy listeners can enqueue durable jobs without changing sync listener behavior; tests cover sync vs async routing and replay. | Review by feat(dispatch): queue async events durably |
| P5.3 | Move Meilisearch indexing and reindex fan-out into retryable jobs. | Meilisearch plan plus Chatwoot callbacks/jobs that index searchable records | search services, contact/company/conversation indexing hooks | Create/update/delete indexing survives handler success, retries on Meilisearch failure, and optional live Meilisearch gate remains green. | Review by feat(search): queue index updates durably |
| P5.4 | Queue automation webhook and transcript delivery. | webhook_job.rb, automation action execution services |
internal/automation/action_delivery.go, internal/automation/action_service.go |
Existing timeout/retry fakeable delivery is invoked by durable jobs; logs preserve attempt metadata and idempotency. | Review by feat(automation): queue external action deliveries |
| P5.5 | Queue delayed automation actions and macro execution. | trigger_scheduled_items_job.rb, macros_execution_job.rb |
automation rule listener, macro service | Delayed actions execute after schedule time if reference params exist; macro execute supports multi-conversation job fan-out, and repeated workers do not duplicate side effects. | Review by feat(automation): queue macro and csat jobs and feat(automation): close delayed action parity; current reference exposes no delayed automation action params |
| P5.6 | Queue CSAT survey sends and channel-specific templates. | CSAT listener/services, WhatsApp/Twilio template services/jobs | internal/automation/csat_survey_listener.go, internal/service/csat_template_service.go, channel send services |
Resolve-triggered CSAT send is durable; WhatsApp/Twilio template delivery and failure states are fakeable and observable. | Review by feat(automation): queue macro and csat jobs and feat(csat): queue channel templates |
| P5.7 | Queue SLA account-wide scans and applied-SLA processing. | enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb, process_account_applied_slas_job.rb, process_applied_sla_job.rb |
internal/service/applied_sla_service.go, SLA event services |
Scheduler enqueues account scans and per-SLA processing; threshold transitions remain idempotent and notification fan-out is retryable. | Review by feat(sla): queue applied sla scans |
| P5.8 | Queue contact export artifact generation and completion notification/email. | account/contacts_export_job.rb |
contact export service and mailer boundary | Export API returns immediately, artifact generation is durable, completion notification/email is fakeable, and download remains stable after restart. | Review by feat(crm): queue contact exports |
| P5.9 | Queue inbound provider webhook processing where Chatwoot defers work. | webhooks/*_events_job.rb, webhooks/*_delivery_job.rb |
webhook handlers, provider services | Provider HTTP ack behavior remains Chatwoot-compatible while persistence/dispatch runs through retryable jobs with signature validation preserved. | Review by feat(webhook): queue incoming messages |
| P5.10 | Queue outbound message delivery and delivery-status updates. | send_reply_job.rb, provider delivery/status jobs |
message send/channel services, delivery status handler | Outgoing message creation and provider delivery are separated; retries update message/delivery status exactly once. | Review by feat(messages): queue send replies and feat(messages): queue delivery statuses |
| P5.11 | Queue Captain document sync, crawl, response building, embeddings, and Copilot responses. | Captain document/crawl/response/embedding/Copilot/conversation jobs | internal/service/captain_document_service.go, internal/service/copilot_service.go, internal/service/captain_conversation_service.go, Captain/Copilot services |
Existing fakeable disabled/failure gates run under durable jobs; document statuses, Copilot message persistence, and Captain conversation replies survive worker restart. | Review by feat(captain): queue document syncs, feat(captain): queue document crawl jobs, feat(captain): queue response embedding jobs, and feat(captain): queue copilot response jobs |
| P5.12 | Queue conversation maintenance jobs. | trigger_scheduled_items_job.rb, campaigns/trigger_oneoff_campaign_job.rb, conversations/resolution_job.rb, reopen_snoozed_conversations_job.rb, update_message_status_job.rb, bulk_actions_job.rb |
conversation service/handlers | Auto-resolution, snooze reopen, status updates, and bulk actions are scheduled/retryable with idempotent tests. | Review by feat(conversations): queue maintenance jobs, feat(conversations): queue message status updates, and feat(conversations): queue bulk actions |
| P5.13 | Replace placeholder analytics/report builders that need background aggregation. | reporting jobs/services and report controllers | internal/service/analytics_service.go, internal/service/analytics_query_helpers.go, internal/service/reporting_rollup_worker.go, reporting services |
P5.13a derives live/bot/conversation summary/matrix/distribution/outgoing-count report values from persisted rows; P5.13b adds lazy cached rollup freshness, durable reporting:rollup_day jobs, and /reports metric timeseries index parity. |
Review by feat(reports): derive analytics aggregates and feat(reports): add analytics timeseries rollups; remaining report work should be created as drift slices when frontend/reference exposes additional metrics |
P5.1 current checkpoint:
- Added
background_jobsas the shared durable job table with queue, job type, JSON payload, status, priority, attempt counters, scheduled/locked timestamps, idempotency key, last error, and completion/dead-letter timestamps. - Replaced the
WorkerPoolstub with enqueue/schedule APIs, handler registration,ProcessOne, start/stop worker loops, PostgreSQLFOR UPDATE SKIP LOCKEDclaiming, retry/backoff, dead-letter state, and stale-lock recovery for worker restart pickup. - Kept older
NewWorkerPool()construction as a no-op-compatible path while addingNewWorkerPoolWithOptions(db, ...)for durable wiring. - Added focused tests proving enqueue/idempotency, due job completion, retry to dead-letter, queue/schedule filtering, and stale running job requeue.
- Remaining Phase 5 work: delayed automation scheduled-item execution if reference params require it.
P5.1 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/worker -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.2 current checkpoint:
channel.Dispatchercan now be constructed or later wired with aWorkerPool;DispatchAsyncenqueuesevent:dispatch_asyncjobs on theeventsqueue and falls back to current synchronous behavior when no worker is configured.dispatch.EventDispatchercan now attach aWorkerPool; sync listeners still execute inline, while async listeners andDispatchAsyncsubscribers enqueue durable per-listenerevent:listener_dispatchjobs.- Worker replay resolves the listener by name and invokes
OnEventwith the serialized event payload. Missing listeners or malformed payloads fail the job, preserving retry/dead-letter visibility. WorkerPooldefault queue handling was broadened to process all queues unless explicitly filtered, soeventsjobs are processed by the default worker.- Remaining integration work is feature-specific: delayed automation actions if still required; analytics aggregation now registers durable day rollup handlers on this path.
P5.2 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/channel ./internal/dispatch ./internal/worker -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.3 current checkpoint:
DurableSearchIndexernow wraps the Meilisearch-backedSearchServicewrite side. Conversation, message, contact, company, and article create/update/delete hooks enqueuesearch:indexjobs on thesearchqueue when aWorkerPoolis configured.- Worker replay reloads each account-scoped record before indexing so Meilisearch receives current database state instead of stale request-time payloads.
- Missing records during an index replay are converted into delegate delete calls, which keeps delayed create/update jobs from resurrecting documents after a database delete.
- Search read paths remain Meilisearch-first through
searchService; contact and company services still use the liveSearchServicereader instead of the durable wrapper. - No-worker construction still falls back to synchronous indexing for focused tests and development paths that do not start the durable worker.
- Remaining P5.3 work is limited to explicit reindex fan-out/CLI scheduling and optional live Meilisearch environment gates; normal service-layer writes are now durable.
P5.3 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'DurableSearchIndexer\|SearchIndexHooks' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/search ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.4 current checkpoint:
ActionServicecan now be constructed with aWorkerPool. When present,send_webhook_eventenqueuesautomation:webhook_deliveryjobs andsend_email_transcriptenqueues oneautomation:transcript_deliveryjob per parsed recipient, matching Chatwoot's asyncWebhookJob/mailer boundary instead of depending on the request path.- The durable job handlers reuse the existing fakeable HTTP webhook and SMTP transcript delivery interfaces, so timeout/retry behavior remains tested at the delivery boundary while worker attempts, errors, retry schedules, completion, and dead-letter state are visible through
background_jobs. AutomationRuleService,AutomationRuleListener, andMacroServicecan carry the WorkerPool through action execution.Bootstrapnow creates a durable WorkerPool, registers automation delivery jobs, wires the dispatcher with event jobs, and starts the worker inApp.Run.- Existing no-worker constructors still preserve the previous synchronous fallback for focused tests and development paths that do not start the durable worker yet.
- Remaining Phase 5 work: delayed automation actions if current-reference params require them; macro fan-out, CSAT sends/templates, Meilisearch indexing fan-out, SLA scans, contact export email, provider webhook/offline delivery, Captain/Copilot, conversation maintenance, and analytics rollups/timeseries are in Review.
P5.4 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.5/P5.6 current checkpoint:
MacroService.ExecuteForDisplayIDsnow follows ChatwootMacrosExecutionJobwhen a WorkerPool is configured: request handlers can return200 OKafter enqueue, the worker later resolves account-scoped conversation display IDs, and macro actions execute per conversation under the existing macro/action service boundary.- Macro job replay logs execution rows after each conversation and does not retry solely because the audit-style execution record failed, avoiding duplicate conversation side effects from log-only failures.
CsatSurveyListenernow queues resolve-triggered CSAT sends throughcsat:survey_sendjobs with an idempotency key per conversation. Duplicate resolved events reuse the same queued/completed job, and the worker still relies onSendSurveyForConversationID's existing one-survey-message guard.CsatTemplateServicenow queues WhatsApp/Twilio template creation throughcsat:template_createjobs when a WorkerPool is configured. Worker replay calls a fakeable provider boundary, persists provider metadata intoinbox.csat_config, and records provider failures in both template status andbackground_jobsretry state.- Inbox CSAT template create/analyze handlers now accept Chatwoot nested
{ template: { message, button_text, language } }payloads, and show returns the Chatwoot-styletemplate_existsstatus object. - Message-updated CSAT response building remains inline because it is part of the submitted widget/public API update path, not an external delivery side effect.
- B9.3 reference audit confirms the current Chatwoot automation action shape has no explicit delayed action params.
send_email_to_teamnow queues durableautomation:team_email_deliveryjobs and enterpriseadd_slacreates account-scoped conversation/applied-SLA state. - Remaining P5.5/P5.6 work: none known for the named delayed automation/macro/CSAT job rows; future delayed action work requires new reference or smoke evidence.
P5.5/P5.6 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/automation ./internal/worker ./internal/app -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CsatTemplate' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'InboxCsatTemplate' -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.7 current checkpoint:
sla:trigger_accountsmirrors Chatwoot enterpriseSla::TriggerSlasForAccountsJob: it scans accounts that have SLA policies and queues onesla:process_accountjob per account on themediumqueue.- The root trigger job runs on
scheduled_jobs, uses five-minute idempotency buckets, and self-schedules the next scan after a successful run so bootstrap only needs to seed the first job. sla:process_accountmirrorsSla::ProcessAccountAppliedSlasJob: it finds active and active_with_misses AppliedSLA rows for the account and queuessla:process_appliedjobs.sla:process_appliedmirrorsSla::ProcessAppliedSlaJob: it calls the existingAppliedSlaService.Evaluate, preserving idempotent miss events, notification fan-out, retry/backoff, and dead-letter visibility throughbackground_jobs.- Bootstrap registers the SLA job handlers and enqueues the initial root scan when the app boots.
- Remaining Phase 5 work: none known for the named SLA job row; future scheduling gaps require new reference or smoke evidence.
P5.7 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'SlaProcessingJobs\|AppliedSlaService' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.8 current checkpoint:
contact:exportnow mirrors ChatwootAccount::ContactsExportJobon thelowqueue. When a WorkerPool is configured,POST /contacts/exportcreates a pendingcontact_exportsartifact and returns after enqueue instead of generating CSV synchronously on the request path.- Worker replay reloads the export request from the persisted
column_namesandfilter_params, generates the CSV artifact, storesfile_url,csv_data,row_count, andcompleted_at, then creates the existingcontacts_export_completenotification and invokes the fakeable mailer boundary. - Completed export jobs are idempotent: a duplicate replay of an already-completed export is a no-op, so completion notifications and mail are not duplicated.
- Failed worker execution records the export error and leaves the background job retry/dead-letter state observable through
background_jobs. - No-worker construction still uses the synchronous fallback for focused tests and local paths that do not start the durable worker.
- Remaining Phase 5 work: delayed automation scheduled-item execution if reference params require it.
P5.8 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'ContactService_ExportContacts\|ContactExportJob' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.9 current checkpoint:
webhook:incoming_message_persistnow mirrors the persistence/dispatch deferral used by Chatwoot provider event jobs such asWebhooks::TelegramEventsJob,Webhooks::LineEventsJob,Webhooks::WhatsappEventsJob,Webhooks::TwilioEventsJob,Webhooks::FacebookEventsJob, andWebhooks::TiktokEventsJob.- Provider HTTP handlers still perform request acknowledgement, signature validation, inbox lookup, and provider-specific payload parsing on the request path, preserving current acknowledgement behavior.
- When a WorkerPool is configured,
IncomingPersister.PersistIncomingvalidates the normalizedchannel.IncomingMessage, enqueueswebhook:incoming_message_persist, and returns before creating contact inboxes, conversations, messages, or dispatching automation/realtime events. - Worker replay reloads the inbox, runs the existing Chatwoot-style
ContactInbox/Conversation/Messagepersistence path, keeps source-ID dedupe behavior, and dispatches the samecontact.created,conversation.created/opened/updated,message.created, andmessage.incomingevents from the durable path. - Incoming jobs use idempotency keys by inbox/source ID; WhatsApp and Twilio inbound messages use the
lowqueue to match their reference jobs, while Telegram, LINE, Facebook/Instagram, and TikTok use the default queue. - Bootstrap wires Telegram, LINE, Facebook/Instagram, WhatsApp, TikTok, and Twilio persisters to the WorkerPool. No-worker construction remains synchronous for existing focused unit tests and local non-worker paths.
- Remaining P5.9 work: none known for normalized inbound persistence/dispatch deferral; any future provider-specific raw event replay gap should be tracked as a separate Phase 2/3 drift slice if reference inspection or smoke exposes one.
P5.9 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -run 'IncomingPersister.*Incoming\|IncomingPersister.*Status\|StatusJob\|TwilioInboundSMS\|TwilioDeliveryStatus' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook ./internal/channel/whatsapp ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.10 current checkpoint:
message:send_replynow mirrors ChatwootSendReplyJobon thehighqueue for the send-reply core. Outgoing message creation enqueues provider delivery when a WorkerPool is configured, while no-worker service construction keeps the oldermessage.outgoingdispatch fallback.- Worker replay reloads the current message, conversation, inbox, and contact by account scope before calling the registered channel provider through
channel.SendMessageToChannel. - The job skips already provider-originated messages with
source_id, private messages, voice-call bubbles, and non-outgoing/non-template messages, matchingBase::SendOnChannelService#invalid_message?and preventing duplicate external sends. - Successful provider sends persist the returned external message ID into
messages.source_idand clear any staleexternal_errorfromcontent_attributeswhile keeping statussent. - Provider failures mark the message
failed, persistcontent_attributes.external_error, and return an error so durable retry/backoff/dead-letter state remains observable inbackground_jobs. - Bootstrap wires
MessageService.SetWorkerPool(workerPool), so production outgoing replies are replayable after process restart. IncomingPersister.SetWorkerPoolnow registers provider delivery-status jobs: direct receipts enqueuewebhook:message_status_update, contact-scoped read receipts enqueuewebhook:contact_messages_status_update, and worker replay reloads the inbox before applying status transitions.- Delivery-status replay upserts
delivery_statuses, dispatchesmessage.status_updated, records failed provider details incontent_attributes.external_error, clears stale external errors on non-failed updates, and prevents stalesent/deliveredreceipts from downgrading already-readmessages. - Twilio status callbacks now match the reference
Twilio::DeliveryStatusControlleracknowledgement with204 No Content, support the exact/twilio/delivery_statusroute plus the legacy/webhooks/twilio/delivery_statusalias, resolve channels byMessagingServiceSidorAccountSid/From, mapundeliveredtofailed, and persist TwilioErrorCode/ErrorMessageasexternal_error. - Bootstrap wires Facebook/Instagram, WhatsApp, TikTok, and Twilio webhook persisters to the WorkerPool, so provider status callbacks are replayable after process restart. No-worker construction still performs status updates inline for focused unit tests.
- Remaining P5.10 work: none known for the named outbound delivery/status row.
P5.10 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'MessageDeliveryWorker\|MessageService' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook -run 'IncomingPersister.*Status\|StatusJob\|TwilioDeliveryStatus' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/worker ./internal/app -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/webhook ./internal/service ./internal/worker ./internal/app -count=1
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
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.11 current checkpoint:
captain:document_syncnow mirrors Chatwoot enterpriseCaptain::Documents::PerformSyncJobon thelowqueue.POST /captain/documents/:id/syncmarks the account-scoped document as syncing and enqueues durable replay when a WorkerPool is configured.- Worker replay unmarshals the persisted account/document IDs and calls
SyncDocumentByAccount, reusing the B11.3c fakeable sync backend boundary for disabled provider config, backend failures, explicit backend error codes, successful content/title/fingerprint updates, and account scoping. - Missing documents or malformed payloads fail the job so retry/backoff/dead-letter state stays observable in
background_jobs, matching the Chatwoot background-job failure surface instead of silently swallowing bad work. captain:document_crawlnow mirrors the simple crawl fan-out portion of ChatwootCaptain::Documents::CrawlJobon thelowqueue. Document create/crawl requests enqueue durable crawl work when a WorkerPool is configured, and crawl replay uses a fakeable backend to produce normalized parser jobs for page links plus the original document URL.captain:document_page_crawl_parsenow mirrorsCaptain::Tools::SimplePageCrawlParserJobfor the local simple-crawl path. Worker replay validates assistant/account scope, creates or updates the matching document by assistant/link, stores title/content/fingerprint, marks sync assynced, and exposes parser-disabled or content-empty failures without hitting the network in default tests.captain:documents_schedule_syncsnow mirrors the reference auto-sync scheduler shape onscheduled_jobs. Bootstrap seeds the scheduler, scheduler jobs use daily idempotency, scan stale synced/failed/syncing completed documents for accounts withcaptain_document_auto_syncenabled, and enqueuecaptain:document_syncwork on thepurgablequeue for retryable execution.captain:document_response_buildernow mirrors Chatwoot enterpriseCaptain::Documents::ResponseBuilderJobon thelowqueue. Successful document sync/parser content updates enqueue response generation, worker replay resets only unedited document responses, preserves edited responses, creates approvedCaptain::Documentassistant responses from a fakeable FAQ backend, and keeps missing FAQ provider config as an observable retry error.captain:llm_update_embeddingnow mirrorsCaptain::Llm::UpdateEmbeddingJobfor assistant responses. Response-builder replay fans out embedding jobs for created responses, embedding replay reloads the account-scoped response, uses a fakeable embedding backend or the configured LLM provider, and records disabled/missing provider failures throughbackground_jobsretry state.captain:copilot_responsenow mirrors Chatwoot enterpriseCaptain::Copilot::ResponseJobon the default queue. Copilot thread/message creation persists the user message first, enqueues durable assistant generation when a WorkerPool is configured, and worker replay reloads the account/user/thread/user-message scope before persisting assistant messages.- Copilot response generation is fakeable for tests and keeps the existing no-provider unavailable-credit fallback as a persisted assistant message; backend failures remain retryable through
background_jobsinstead of being hidden behind synchronous request success. captain:conversation_response_buildernow mirrors Chatwoot enterpriseCaptain::Conversation::ResponseBuilderJobfor pending conversations in Captain-enabled inboxes. Incoming public messages enqueue durable work, attachment-bearing messages are scheduled with a short delay, and non-pending/private/non-Captain conversations are skipped before enqueue.- Conversation response replay collects public incoming/outgoing history, maps incoming to user and outgoing to assistant context, creates Captain outgoing messages with optional
agent_name, enqueuesmessage:send_reply, and handles handoff responses by creating the configured handoff message and opening the pending conversation. - No-worker construction keeps the previous mark-syncing fallback for focused tests and local paths that do not start the durable worker.
- Bootstrap registers Captain document, Copilot response, and Captain conversation response jobs on the shared WorkerPool and wires the assistant-response repository so production sync/crawl/schedule/response/embedding/Copilot/conversation requests are replayable after process restart.
- Remaining P5.11 work: none known for the named durable Captain/Copilot job row; analytics aggregation and delayed automation action checks are now in Review.
P5.11 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CaptainDocumentService' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CaptainDocumentService\|EnqueueCaptainDocumentScheduleSyncs' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'CopilotResponse\|CaptainConversation\|MessageService' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'CaptainDocument\|CaptainResource' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/handler/api/v1 ./internal/worker ./internal/app -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.12 current checkpoint:
scheduled:trigger_itemsnow mirrors ChatwootTriggerScheduledItemsJobon thescheduled_jobsqueue. Bootstrap registers the handlers and seeds the first root trigger; the root trigger self-schedules the next hourly run with an idempotency bucket.- Root replay scans active due one-off campaigns in Chatwoot's three-day window and enqueues one durable
campaign:trigger_oneoffjob per campaign on thelowqueue. - One-off campaign replay claims active campaigns as
processingbefore execution, creates the campaign conversation/message through the existing campaign builder, marks the campaign completed, and no-ops duplicate replays once the campaign is no longer active. - Root replay also queues
conversation:reopen_snoozedandaccount:conversations_resolution_scheduler. The snooze job reopens due snoozed conversations and clearssnoozed_until; the scheduler queuesconversation:resolutionjobs for accounts with auto-resolution enabled. - Account resolution replay resolves old open conversations using Chatwoot-style minute thresholds from
auto_resolve_duration, stampsresolved_at, and retries missing accounts throughbackground_jobs. conversation:update_message_statusnow mirrors ChatwootConversations::UpdateMessageStatusJobon thedeferredqueue. Widget and public inboxupdate_last_seenproducers enqueue the job after persistingcontact_last_seen_at.- Message-status replay validates only
readanddelivered, no-ops missing conversations, and updates non-incomingsent/deliveredmessages created before the viewer timestamp while skipping incoming, already-read, and post-cutoff messages. conversation:bulk_actionnow mirrors ChatwootBulkActionsJobon themediumqueue for account conversation bulk actions. The accountPOST /bulk_actionshandler accepts the reused frontend payload shape withfields, nestedlabels.add/remove,snoozed_until, and display-IDids, enqueues the job when a WorkerPool is configured, and returns empty200 OKlike Chatwoot.- Bulk-action replay scopes records by account and conversation display IDs, merges label adds/removals without duplicating labels, applies status/team/assignee/snooze fields, and leaves same-display conversations in other accounts untouched.
- Remaining P5.12 work: none known for the named maintenance jobs in this row.
P5.12 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'ConversationMaintenanceJobs' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'ConversationMaintenanceJobs\|WidgetService_UpdateLastSeenQueuesMessageStatusJob' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'ConversationMaintenanceJobs.*BulkAction\|ConversationMaintenanceJobs' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'BulkActionHandler' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/worker ./internal/app -count=1
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
P5.13 current checkpoint:
- P5.13a replaces the most visible analytics/report placeholder values with persisted queries instead of fixed zero or empty JSON.
- Live conversation metrics now count
open,unattended,unassigned, andpendingfromconversations, including Chatwoot-style unattended semantics andteam_idfiltering. - Grouped live reports now group open conversations by
assignee_idorteam_idand return the matching Chatwoot key plus open/unattended/unassigned counts. - Bot summary and bot metrics now derive bot resolutions/handoffs from
reporting_events, exclude handoff conversations from bot resolutions, and calculate bot conversation/message counts and rates from activeagent_bot_inboxes. - Conversation summary now derives conversation, incoming-message, outgoing-message, resolution, first-response, resolution-time, and reply-time values from persisted conversations/messages/reporting events.
- Inbox-label matrix now uses
inboxes,tags, andconversation_labels; first-response distribution bucketsfirst_responseevents by inbox channel type; outgoing-message counts group by agent, team, inbox, or label. - P5.13b routes
GET /api/v1/accounts/:account_id/reportsandGET /api/v2/accounts/:account_id/reportsto metric timeseries instead of summary. Supported metrics includeconversations_count,resolutions_count, incoming/outgoing messages, first-response/resolution/reply averages, and bot resolution/handoff counts. - Timeseries queries support
type=account|inbox|agent|team|label, optionalid,group_by=hour|day|week|month|year, andbusiness_hours=truefor reporting-event average metrics. EnsureRollupsForRangelazily computes missing daily rollups before summary, dimension, traffic, and timeseries reads, giving expensive reports a deterministic freshness boundary without requiring a separate scheduler for local tests.reporting:rollup_daydurable jobs are registered throughAnalyticsService.SetWorkerPooland wired in bootstrap; enqueue uses account/date idempotency keys and worker replay callsReportingRollupService.ComputeDailyRollup.- Rollup recompute now hard-deletes matching soft-deleted rollups through
Unscopedbefore insert, preventing unique-key conflicts during idempotent refresh. - Remaining P5.13 work: none known for the named scheduled/cached analytics row; any additional report metric or serializer mismatch found by reused frontend smoke should become a Phase 2/3 drift slice.
P5.13 verification:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service -run 'Analytics' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/handler/api/v1 -run 'Analytics\|LiveReport' -count=1
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./internal/service ./internal/handler/api/v1 ./internal/router ./internal/worker ./internal/app -count=1
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
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
git diff --check
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 |
Account show/update/create/cache-key/active-at, agents, teams, team members, assignable agents, notification settings, and profile permission payloads have focused Chatwoot-style coverage; continue only from fresh reference/smoke evidence. | Review |
| P6.2 | Contact APIs | docs/ROUTE_GAP_ANALYSIS.md, contact handlers/services |
Merge, import/export/data-import, labels/avatar, notes, nested conversations, active/search/filter pagination, selected custom-attribute deletion, shared attachments, and Meilisearch-backed CRM search have focused coverage; continue only from fresh reference/smoke evidence. | Review |
| P6.3 | Conversation APIs | docs/ROUTE_GAP_ANALYSIS.md, conversation handlers/services |
Frontend-critical filters, assignment, status, snooze, merge, bulk actions, transcript responses, and custom-attribute responses are implemented or covered by focused drift slices; continue only from fresh reference/smoke evidence. | Review |
| P6.4 | Message APIs | docs/ROUTE_GAP_ANALYSIS.md, message handlers/services |
Create/list/delete/private notes/attachments/source attribution have focused coverage; retry now matches Chatwoot status reset/content-attribute clearing and durable send-reply enqueue behavior. Continue only from fresh reference/smoke evidence. | Review |
| P6.5 | Inbox APIs | docs/ROUTE_GAP_ANALYSIS.md, inbox handlers/services |
CRUD, assignable agents, avatar, campaigns, channel settings, reset secret, working hours, members, WhatsApp Cloud health/register-webhook/sync-template, and agent-bot get/set member actions have focused Chatwoot-style coverage; continue only from fresh reference/smoke evidence. | Review |
| 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
WebhookAuthparams that do not exist on Chatwoot provider paths. - Provider handlers may still return
200 OKon 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
Doneonly 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 |
| P6.6g | Public help-center article lists | Widget getMostReadArticles, Public::Api::V1::Portals::ArticlesController#index, and public article Jbuilder views |
Review by feat(help-center): expose public article lists; /hc/:slug/:locale/articles(.json) returns published locale-scoped public article payloads, sort=views, archived-portal 404, and route parity coverage. |
| P6.6h | Public help-center categories | Public::Api::V1::Portals::CategoriesController#index/#show and public category Jbuilder views |
Review by feat(help-center): expose public category lists; /hc/:slug/:locale/categories(.json) and /categories/:category_slug return public category payloads, slug+locale show lookup, published article counts, and route parity coverage. |
| P6.6i | Public help-center portal show | Public::Api::V1::PortalsController#show, public HC portal Jbuilder, and default-locale redirect |
Review by feat(help-center): expose public portal show; /hc/:slug redirects to default locale and /hc/:slug/:locale returns public portal fields, categories, logo, and meta counts for non-archived portals. |
| P6.6j | Public help-center portal search | Public::Api::V1::Portals::SearchController#index, _search_article.json.jbuilder, and routes.rb:593 |
Review by feat(help-center): expose public portal search; /hc/:slug/:locale/search returns empty payloads for blank queries and published locale-scoped search article payloads with snippets and public article links for non-archived portals. |
| P6.6k | Public help-center article show/markdown/tracking | Public::Api::V1::Portals::ArticlesController#show/#show_markdown/#tracking_pixel, public article Jbuilder, and routes.rb:598-601 |
Review by feat(help-center): expose public article show; /hc/:slug/articles/:article_slug returns public article payloads, .md serves published markdown, and .png serves a 1x1 tracking pixel while incrementing published article views. |
| P6.6l | Public help-center sitemap | Public::Api::V1::PortalsController#sitemap, sitemap.xml.erb, and routes.rb:591 |
Review by feat(help-center): expose public sitemap; /hc/:slug/sitemap.xml returns XML for published article URLs with custom-domain base URLs and lastmod dates. |
Phase 7: Verification Harness
Status: planned.
Checklist:
- Add repeatable command to compare Chatwoot and GoChat route dumps.
- Add fixture-based JSON parity tests for major frontend endpoints.
- Add Meilisearch mock tests and optional integration test mode gated by env vars.
- Add frontend smoke path using reused Chatwoot frontend once backend boot flow is ready.
- Keep a generated gap report in
docs/parity/with date, reference commit, Go commit, and pass/fail summary.
Required verification before each feature commit:
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes
Verification milestone gates:
| Gate | Command or artifact | Required before |
|---|---|---|
| V1 | env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./... |
Every commit that changes Go code. |
| V2 | env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt |
Every route/router/handler registration change. |
| V3 | env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity |
Every tracked route or route generator change. |
| V4 | JSON fixture parity tests for touched endpoint family. | Every serializer or handler payload change. |
| V5 | Optional live Meilisearch integration test gated by env var. | Search behavior changes that claim Meilisearch compatibility. |
| V6 | Reused Chatwoot frontend smoke flow. | Before declaring frontend reuse viable. |
Task Status Legend
Todo: not started or only skeleton exists.Doing: implementation is underway in the current working tree.Review: code is implemented and needs parity/test verification.Done: committed, tested, and linked to the matchingreference/chatwootbehavior.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_routespassing. - 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/chatwootbehavior. - 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-06: P3.13 dashboard apps checkpoint prepared as
feat(dashboard-apps): align chatwoot payloads; audited Chatwootroutes.rb:130,DashboardAppsController, dashboard app Jbuilder views,DashboardAppcontent schema, reused dashboardapi/dashboardApps.js, VuexdashboardAppsstore, and DashboardApps modal/index components. Dashboard app routes are now tracked, create/update accept raw frontend bodies as well as{ dashboard_app: ... }, create returns raw200 OKpayloads, list/show/create/update return onlyid,title,content, andcreated_at, show/update/delete are account-scoped, delete returns204, and content validation rejects empty arrays, non-frame widgets, and non-HTTP(S) URLs. Focused dashboard app handler/service tests, combined handler/service/repository/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed; route dump remainsTOTAL: 907and tracked route parity is357 exact, 0 method-compatible, 13 parameter-compatible, 0 missing out of 370. - 2026-06-06: P3.12 account Dyte integration checkpoint prepared as
feat(integrations): align dyte meeting routes; audited Chatwootroutes.rb:357-358,Api::V1::Accounts::Integrations::DyteController,Integrations::Dyte::ProcessorService,lib/dyte.rb, reused dashboardapi/integrations/dyte.js,VideoCallButton.vue, and the Dyte message bubble. Account Dyte create-meeting/add-participant routes are now registered and tracked, create-meeting resolves conversations by display ID, checks inbox access, loads account hook credentials, creates an outgoingintegrationsmessage withcontent_attributes.type=dyte, and returns the normal message serializer; add-participant rejects non-integration messages with the reference error and returns the Dyte participant token payload through a fakeable backend. Focused Dyte service/handler tests, combined handler/service/router/app/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed; route dump isTOTAL: 907and tracked route parity is355 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 364. - 2026-06-06: P3.2 account-user inviter checkpoint prepared as
feat(account-users): align inviter id; audited ChatwootAccountUserschema,AgentBuilder#create_account_user,api/v1/models/_user.json.jbuilder, and enterpriseAudit::AccountUser. GoChat now persists invitation source through the Chatwootinviter_idcolumn/JSON name instead of the localinvited_bycolumn, copies existinginvited_byvalues forward in migration000031, emits account-user lifecycle events withinviter_id, and verifies agent creation stores the current user as inviter without leaking inviter fields through the_agentserializer. Focused model/agent/service/repository/migration tests, fullgo test ./..., andgit diff --checkpassed. Continue remaining P3.2 invitation mail/confirmation drift or B12 live smoke. - 2026-06-06: P3.1 profile HMAC checkpoint prepared as
feat(profile): expose hmac identifier; audited Chatwootapp/views/api/v1/models/_user.json.jbuilder,UserAttributeHelpers#hmac_identifier,spec/models/user_spec.rb, and reused frontendscriptHelpers.js. Profile responses now omithmac_identifierwhenCHATWOOT_INBOX_HMAC_KEYis absent and exposeOpenSSL::HMAC.hexdigest('sha256', key, email)compatible values when the installation config exists, letting the reused widget script helper sendidentifier_hashwithout adapter code. Focused profile handler tests, service compile test, fullgo test ./..., andgit diff --checkpassed. Continue remaining P3.2 invitations drift or B12 live smoke. - 2026-06-06: P3.2 platform user checkpoint prepared as
feat(platform): align user payloads; audited ChatwootPlatform::Api::V1::UsersController,platform/api/v1/models/_user.json.jbuilder,users/token.json.jbuilder, and platform user routes. Platform user show/create/update now return raw Chatwoot user payloads, create reuses existing users by email and creates the platform-app permissible idempotently, access tokens are generated/reused for user payloads,GET /users/:id/loginreturns a Chatwoot-shaped URL payload,POST /users/:id/tokenreturns{ access_token, expiry, user }, update mergescustom_attributes, and destroy returns empty200 OK. The stale non-referenceGET /platform/api/v1/users/:id/tokenroute was removed from the dump, leaving route dumpTOTAL: 905and tracked route parity at353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362. Focused platform handler/service/router tests, route parity test/generation, fullgo test ./..., andgit diff --checkpassed; continue remaining P3.2 invitations drift or B12 live smoke. - 2026-06-06: P3.5 company avatar form/pagination checkpoint prepared as
feat(companies): align avatar form payloads; audited Chatwoot enterpriseCompaniesController, company Jbuilder views, reused dashboardapi/companies.js, and Piniastores/companies.js. Company create/update now accepts nested multipartcompany[...]form payloads generated when the frontend includes an avatar, mapscompany[avatar]into the existing serializedavatar_urlboundary, preserves nested multipartcompany[custom_attributes][...], and makes company list/search/contact list/search use Chatwoot's fixedRESULTS_PER_PAGE = 25instead of localper_pageoverrides. Focused company handler tests, focused company service tests, fullgo test ./..., andgit diff --checkpassed; no route artifacts changed. - 2026-06-06: P3.11 account integrations apps/hooks checkpoint prepared as
feat(integrations): align app hook payloads; audited Chatwootroutes.rb:345-348,Integrations::AppsController,Integrations::HooksController, app/hook Jbuilder views,Integrations::App,Integrations::Hook,config/integration/apps.yml, and reused dashboardapi/integrations.js,store/modules/integrations.js, and settings integration components. Account integration apps/hooks now return Chatwoot{ payload: [...] }app index payloads and raw app/hook objects, accept raw frontend hook bodies plus nested{ hook: ... }, register frontend no-trailing-slash app/hook routes and hookPATCH, persistapp_id/reference_id, scope hook show/update/delete by account, serialize booleanstatus, account/inboxhook_type,settings,inbox, apphooks, settings schemas, and visible properties, seed additional reference app catalog rows, and return empty200 OKdelete. Focused integration hook handler/service tests, migration test, route parity test, router focused test, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed; route dump isTOTAL: 869and tracked route parity is321 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 328. - 2026-06-06: P3.10 account webhook API checkpoint prepared as
feat(webhooks): align account payloads; audited Chatwootroutes.rb:342,WebhooksController, account webhook Jbuilder views,Webhook, reused dashboardapi/webhooks.js,store/modules/webhooks.js, and settings Webhooks components. Account webhook APIs now return Chatwoot{ payload: { webhooks } }list payloads and{ payload: { webhook } }mutation/show payloads, accept nested{ webhook: ... }frontend bodies plus legacy raw bodies, register frontendPATCHupdate, persistname/url/subscriptions/inbox_id/secret, scope show/update/delete by account, validate HTTP/HTTPS URLs and allowed subscriptions, return empty200 OKdelete, and keep outbound delivery event filtering on the same rows. Focused webhook handler/service/router tests, migration test, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed; route dump isTOTAL: 865and tracked route parity is313 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 320. - 2026-06-06: P3.9 account agent-bot API checkpoint prepared as
feat(agent-bots): align account payloads; audited Chatwootroutes.rb:94-97,AgentBotsController, account agent-bot Jbuilder views,_agent_bot.json.jbuilder,AgentBotPresenter,AgentBot, and reused dashboardapi/agentBots.js,store/modules/agentBots.js, and settings agent-bot screens. Account agent-bot APIs now expose frontend no-trailing-slash GET/POST and PATCH update routes, return raw Chatwoot bot arrays/objects instead of local success/data/meta envelopes, serializethumbnail,bot_config,system_bot, account-scopedaccess_token/secret, accept JSON or multipart form bodies, preserve explicit empty update fields, scope create/update/delete/reset/avatar mutations to current-account bots, allow show/index of system bots throughaccessible_to, return empty200 OKdelete, and return full bot payloads after reset-access-token/reset-secret/avatar deletion. Focused AgentBot handler/service/router tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed; route dump isTOTAL: 864and tracked route parity is308 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 315. - 2026-06-06: P6.8 contact outbound voice call checkpoint prepared as
feat(contacts): initiate voice calls; audited Chatwootroutes.rb:216, enterpriseContacts::CallsController#create,Voice::OutboundCallBuilder,Voice::CallMessageBuilder,Call, and reused dashboardapi/contacts.js, contact store action, and voice API client. GoChat now registersPOST /api/v1/accounts/:account_id/contacts/:contact_id/call, validates account contact scope, current user's assigned Twilio voice inbox,voice_enabled, and contact phone number, reuses only matching open display-ID conversations, creates ContactInbox/open conversation rows when needed, persists outgoing Twilio call metadata and linkedvoice_callmessage content attributes, and returns{ conversation_id, inbox_id, call_sid, conference_sid }. Focused contact-call tests, combined service/handler/router tests, route dump/parity regeneration,go test ./cmd/route_parity -count=1, fullgo test ./..., andgit diff --checkpassed; route dump isTOTAL: 861and tracked route parity is299 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 306. - 2026-06-06: Documentation checkpoint prepared as
docs: land parity execution tracker; consolidated the active execution queue so future work can be followed fromdocs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.mdalone. The tracker now pins current baseline commits, maps Hermes source plans to active sections, defines slice lifecycle states, records the ordered next queue, and opens P6.8 for Chatwoot enterprise contact outbound voice calls after inspectingreference/chatwoot/config/routes.rb:216,enterprise/app/controllers/api/v1/accounts/contacts/calls_controller.rb,enterprise/app/services/voice/outbound_call_builder.rb, and reused dashboard contact/voice API clients. Verification for this docs-only checkpoint:git diff --check; no Go code or route artifacts changed. - 2026-06-06: P3.7d enterprise article translation checkpoint prepared as
feat(help-center): queue article translations; audited Chatwoot enterpriseArticles::BulkActionsController#translate,Captain::Articles::TranslateJob,Captain::Llm::ArticleTranslationService, reused dashboardapi/helpCenter/articles.js, andBulkTranslateDialog.vue. Account article bulk translate now validatescaptain_tasks, allowed portal locale, optional locale-scoped category, and selected articles; detects existing root-article translations and returns409 { duplicate_articles }unlessforceis true; queues durable low-prioritycaptain:article_translatejobs; replays jobs by creating or updating draft translated articles; and wires an LLM-backed translation backend in bootstrap with Chatwoot title/content prompts and English target language names. Focused service/handler tests, combined service/handler/repository/app tests, fullgo test ./..., andgit diff --checkpassed; no route artifacts changed. - 2026-06-06: P6.5 inbox agent-bot checkpoint prepared as
feat(inboxes): align agent bot member actions; audited ChatwootInboxesController#agent_bot,InboxesController#set_agent_bot,inboxes/agent_bot.json.jbuilder,_agent_bot.json.jbuilder, and the reused dashboardagentBotsstore/API.POST /inboxes/:id/set_agent_botnow accepts the frontend{ agent_bot: id|null }body, keeps legacy{ agent_bot_id }compatibility, treats omitted/null bodies as disconnect, returns empty200 OK, destroys disconnected bindings like Chatwoot@inbox.agent_bot_inbox.destroy!, and updates the inbox's single binding when reassigning bots.GET /inboxes/:id/agent_botnow returns{ agent_bot: {} }when disconnected and Chatwoot partial fields such asthumbnail,bot_type,bot_config, andoutgoing_urlwhen assigned. Focused inbox service/handler tests, combined handler/service tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder burn-down or the next evidence-backed inbox drift. - 2026-06-06: P6 message retry checkpoint prepared as
feat(messages): align retry status parity; audited ChatwootMessagesController#retry,messages/retry.json.jbuilder, and reused dashboardapi/inbox/message.js. Account message retry now resets status tosent, clearscontent_attributesto{}, returns the normal Chatwoot message serializer, and queues the existing durable send-reply job path when a WorkerPool is configured instead of exposing localretryingstate/provider failure details. Focused retry service/handler tests, combined handler/service retry tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder burn-down or a fresh evidence-backed Phase 2/3 drift slice. - 2026-06-06: P6 conversation custom-attribute response checkpoint prepared as
feat(conversations): align custom attribute response; audited ChatwootConversationsController#custom_attributes,custom_attributes.json.jbuilder, reused dashboardapi/inbox/conversation.js, and the conversation store action that readsresponse.data.custom_attributes. Account conversation custom-attribute updates now return only{ custom_attributes: ... }, exclude local conversation/payload/meta wrappers, and serialize empty/null custom attributes as{}for frontend merge stability. Focused handler tests, combined handler/service/router tests, placeholder audit, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder burn-down or a fresh evidence-backed Phase 2/3 drift slice. - 2026-06-06: Audit/tooling checkpoint prepared as
docs: expand help center route parity tracking; expandedcmd/route_parityto cover the full Chatwoot help-center portal/category/article route family fromreference/chatwoot/config/routes.rb:385-404, including portal CRUD/member actions, nested category CRUD/reorder, nested article CRUD/reorder, and article bulk actions. Regenerated route parity reports298 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 305 tracked critical routes; route dump remainsTOTAL: 860. Verification for this checkpoint:go run ./cmd/route_parity,go test ./cmd/route_parity -count=1, andgit diff --check; continue Phase 6 placeholder burn-down or a fresh evidence-backed Phase 2/3 drift slice. - 2026-06-06: P3.7c help-center article checkpoint prepared as
feat(help-center): align article payloads; audited ChatwootArticlesController,Articles::BulkActionsController, article Jbuilder views, and reused dashboardhelpCenter/articles.js. Account article APIs now accept raw frontend create/update bodies plus wrappers, resolve portal route IDs by account-scoped slug with numeric fallback, return Chatwoot{ payload, meta }list/search payloads and{ payload: article }mutation/show/edit payloads, expose no-trailing-slash index/create plus frontendPATCH, return empty200 OKfor delete/reorder/bulk actions, support status/category/locale/query/author filters and dashboard article counts, accept Chatwoot reorderpositions_hash, and implement frontend bulk update-status/update-category/delete routes with422 { error }failures. Focused article handler tests, service/repository article tests, router test, route dump/parity, escalated fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit or Phase 6 placeholder burn-down from fresh evidence. - 2026-06-06: Documentation checkpoint prepared as
docs: lock parity execution tracker; user decisions are now locked into the tracker: reuse Chatwoot frontend directly, treat localreference/chatwootas the only baseline, keepgo test ./...green before deeper parity, require Meilisearch as final search, exclude only SSO/SAML/LDAP/OIDC, and keep the remaining enterprise families in scope. The older Hermes search and automation/macro/CSAT plans are mapped to active tracker sections so future work can be followed from this document alone. Verification for this docs-only checkpoint:git diff --check; next implementation slice remains P3.7c help-center articles and bulk actions. - 2026-06-06: P3.7b help-center category checkpoint prepared as
feat(help-center): align category payloads; audited ChatwootCategoriesController, category Jbuilder views, reused dashboardhelpCenter/categories.js, and category store actions. Account category APIs now accept raw frontend create/update bodies plus wrappers, resolve portal route IDs by account-scoped slug with numeric fallback, return Chatwoot{ payload, meta }list payloads and{ payload: category }mutation/show payloads, expose no-trailing-slash index/create plus frontendPATCH, return empty200 OKfor delete/reorder, support locale filtering andmeta.articles_count, and accept Chatwoot reorderpositions_hashwhile keeping legacypositionsarrays. Focused category handler/service/repository tests, router test, route dump/parity, fullgo test ./..., andgit diff --checkpassed; continue P3.7c article/bulk-action parity next. - 2026-06-06: P3.7a help-center portal checkpoint prepared as
feat(help-center): align portal payloads; audited ChatwootPortalsController, enterprise portalssl_status, portal Jbuilder views, reused dashboardhelpCenter/portals.js, and portal store actions. Account portal APIs now accept raw frontend create/update bodies plus wrappers, resolve portal route IDs by account-scoped slug with numeric fallback, return Chatwoot raw portal/index payloads, expose no-trailing-slash index/create plus frontendPATCH, and return empty200 OKfor delete/logo/archive.send_instructionsnow returns{ message: "Instructions sent successfully" }, while SSL status returns{ status, verification_errors }or422for missing custom domain. Focused portal handler/service/repository tests, router test, route dump/parity, fullgo test ./..., andgit diff --checkpassed; continue P3.7b category parity next. - 2026-06-06: Documentation checkpoint prepared as
docs: land help center parity plan; current baseline now points ata02d11e feat(campaigns): derive chatwoot scheduling, and direct help-center inspection is converted into P3.7a-P3.7c executable contracts. Audited ChatwootPortalsController,CategoriesController,ArticlesController,Articles::BulkActionsController, portal/category/article Jbuilder views, and reused dashboardhelpCenter/*.jsclients. Current Go gaps are slug-vs-numeric portal resolution, local response envelopes, wrapped mutation bodies, missing no-trailing-slash/PATCH/frontend bulk routes, and non-Chatwoot delete/send-instruction status bodies. Verification for this docs-only checkpoint:git diff --check; next implementation commit isfeat(help-center): align portal payloads. - 2026-06-06: P3.7 campaigns scheduling checkpoint prepared as
feat(campaigns): derive chatwoot scheduling; audited ChatwootCampaignsController#campaign_params,Campaign#ensure_correct_campaign_attributes, and reused SMS/WhatsApp/live-chat campaign forms. Campaign create now accepts frontend bodies withoutcampaign_type, derives one-off campaigns for SMS/Twilio/WhatsApp inboxes, persists ISOscheduled_at, defaults missing one-off schedules to current UTC, derives ongoing campaigns for web-widget/live-chat inboxes and clears schedules, while update acceptsinbox_id,sender_id, andscheduled_atwith account-scope checks and preserves omitted descriptions. Focused campaign handler tests, service/repository campaign tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit or the next named Phase 2/3 drift. - 2026-06-06: P3.7 campaigns checkpoint prepared as
feat(campaigns): align chatwoot payloads; audited ChatwootCampaignsController, campaign Jbuilder views,_campaign.json.jbuilder, and the reused dashboard campaigns API/store. Account campaigns now return raw Chatwoot arrays/objects, ignorepage/per_page, usedisplay_idas APIid, resolve route IDs by display ID with legacy primary-key fallback, serialize nested inbox/sender data and parsed campaign JSON fields, accept object/array campaign params, return200 OKfor create/delete, and register frontend-used no-trailing-slash index/create plusPATCHupdate routes. Focused campaign handler tests, service/repository campaign tests, combined handler/service/repository/router tests, route dump/parity, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit or the next named Phase 2/3 drift. - 2026-06-06: P3.1 notification-settings checkpoint prepared as
feat(notification-settings): align chatwoot payloads; audited ChatwootNotificationSettingsController#show/#update,notification_settings/show.json.jbuilder, and the reused dashboarduserNotificationSettingsAPI/store. Account-scoped notification settings now return the raw Jbuilder object with flag arrays instead of the local success/data wrapper, keep nestednotification_settingsupdate params, and register no-trailing-slash GET/PATCH/PUT route aliases for the frontend client URL. Focused handler/service tests, combined handler/service/router tests, route dump/parity, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit or the next named Phase 2/3 drift. - 2026-06-06: P3.2 agent index-pagination checkpoint prepared as
feat(agents): align index pagination; audited ChatwootAgentsController#index,agents, andUser.order_by_full_name. Account agent lists now ignore reused-frontendpage/per_pagequery values and return the full account-agent array like Chatwoot's unpaginated@agents = agents, while preserving lowercased full-name ordering and account-user metadata. Focused list tests, service/repository agent tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. - 2026-06-06: P3.2 agent update-validation checkpoint prepared as
feat(agents): align update validation errors; audited ChatwootAgentsController#update,Username presence validation, andRequestExceptionHandler#render_record_invalid. Explicit blankagent[name]updates now return Chatwoot422 { message, attributes }, preserve the existing user name, and continue to allow omitted names plus explicitauto_offline: false. Focused update tests, service agent tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. - 2026-06-06: P3.2 agent delete-scope checkpoint prepared as
feat(agents): align delete scope; audited ChatwootAgentsController#destroy,fetch_agent, and account-scopedagentsrelation. Agent delete now first resolves the current account membership and returns404for non-members, preserving users and memberships in other accounts instead of treating an unscoped delete as success. Focused delete tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. - 2026-06-06: P3.2 agent duplicate-create checkpoint prepared as
feat(agents): align duplicate create errors; audited ChatwootAgentsController#create,AgentBuilder#create_account_user,AccountUseruniqueness validation, andRequestExceptionHandler#render_record_invalid. Duplicate account membership creation through/agentsnow returns Chatwoot422 { message, attributes }instead of local409, while bulk invite duplicate skipping remains unchanged. Focused duplicate-create tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. - 2026-06-06: P3.2 agent create-default checkpoint prepared as
feat(agents): align create name defaults; audited ChatwootAgentsController#create,AgentBuilder,AccountUser, and the reused dashboard agents API. Agent creation now accepts blank/omittedagent[name], derives new-user names from the email local-part likeemail.split('@').first, and keeps existing agent create role/availability/auto-offline behavior intact. Focused agent create tests, service/repository agent tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. - 2026-06-06: Phase 6 attachment pagination checkpoint prepared as
feat(attachments): align list pagination; audited ChatwootConversationsController#attachments,Contacts::AttachmentsController#index, and the shared attachment Jbuilder partials. Conversation and contact shared attachment lists now use Chatwoot's fixed 100-row page size and ignoreper_pagequery values, preserving{ meta: { total_count }, payload }, newest-first ordering, sender serialization, and contact attachmentconversation_id. Focused attachment suite tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue the next Phase 2/3 or Phase 6 drift from reference/smoke evidence. - 2026-06-06: P3.1/P3.2 profile permissions checkpoint prepared as
feat(profile): align account permissions; audited Chatwoot_user.json.jbuilder,AccountUser#permissions, enterpriseEnterprise::AccountUser, and dashboardpermissionsHelper.js. Profile account entries now expose["administrator"]/["agent"]for ordinary roles and custom-role permission keys pluscustom_rolefor custom-role memberships, preserving nested custom-role metadata for the reused frontend. Focused profile tests, combined handler/service/repository tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 permissions/invitations drift next. - 2026-06-06: P3.2 platform account-user checkpoint prepared as
feat(platform): align account user membership; audited ChatwootPlatform::Api::V1::AccountUsersControllerand platform routes. Platform account-user index/create now return raw AccountUser JSON, create updates existing memberships instead of failing duplicates, the user-permissible extra gate was removed to match the account-scoped reference controller, and collection destroy is available asDELETE /account_users/destroywithuser_id. Focused platform account-user tests, route generation/parity, combined handler/repository/router tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 permissions/invitations drift next. - 2026-06-06: P3.2 agents index-order checkpoint prepared as
feat(agents): align list ordering; audited ChatwootAgentsController#indexandUser.order_by_full_name. Account agent lists now join users and order bylower(users.name) ASCbefore pagination, while preserving account-user metadata in the Chatwoot agent serializer. Focused handler tests, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 account users/invitations drift next. - 2026-06-06: P3.2 agents update-route checkpoint prepared as
feat(agents): align update route semantics; audited ChatwootAgentsController#update, dashboardagents.js, and sharedApiClient.update. Agent update now exposes frontend-usedPATCH /api/v1/accounts/:account_id/agents/:agent_idalongside the existingPUTalias, preserves existingauto_offlinewhen omitted from the request, and still applies explicitauto_offline: falselike Rails strong params withcompact. Focused handler/service tests, route generation/parity, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 account users/invitations drift next. - 2026-06-06: P3.2 agents bulk invite checkpoint prepared as
feat(agents): align bulk invite behavior; audited ChatwootAgentsController#bulk_create, dashboardagents.js, and the reused onboarding/settings bulk invite path. Bulk invite now skips invalid email entries instead of rejecting the full request, keeps duplicate failures best-effort, creates valid agents from mixed arrays, returns empty200 OKfor empty/no-op arrays, and clears the local onboarding step after the bulk flow. Focused handler/service tests, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue P3.2 account users/invitations drift next. - 2026-06-06: B10.3 CustomRole update-route checkpoint prepared as
feat(custom-roles): align update route semantics; audited ChatwootCustomRolesController, custom role Jbuilder views, and dashboardcustomRole.js/ApiClient.update. CustomRole update now exposesPATCH /api/v1/accounts/:account_id/custom_roles/:idalongside the existingPUTalias, route dump isTOTAL: 838, tracked route parity remains270 exact, 7 parameter-compatible, 0 missing, and explicit emptydescriptionvalues now clear the role description while omitted descriptions are preserved. Focused handler/service tests, route generation/parity, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit next. - 2026-06-06: Phase 6 contacts active/pagination checkpoint prepared as
feat(contacts): align active pagination payloads; audited ChatwootContactsController,contacts/index.json.jbuilder,contacts/search.json.jbuilder,contacts/filter.json.jbuilder,contacts/active.json.jbuilder,contacts/destroy_custom_attributes.json.jbuilder, and dashboardcontacts.js. Contact list/search/filter/active now use Chatwoot's fixedRESULTS_PER_PAGE = 15; active contacts now return{ meta: { count, current_page }, payload }through the contact serializer instead of the local{ contacts, meta: { page, page_size } }envelope; selected-keydestroy_custom_attributespayload coverage confirms the Chatwoot{ payload }response andcontact_inboxesinclusion. Focused handler tests, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-06: P3.6 label CRUD checkpoint prepared as
feat(labels): align chatwoot label payloads; audited ChatwootLabelsController, label Jbuilder views,Labelmodel/schema, and dashboard labels API/store. Account/labelslist now returns{ payload }, create/show/update return raw label payloads, create/update accept nested{ label }bodies plus legacy rawname, title values are normalized/lowercased, color defaults to#1f93ff,descriptionandshow_on_sidebarare persisted, account-scoped get/update/delete prevents cross-account access, and delete returns empty200 OKwhile removing conversation/contact label associations and legacy conversation label text. Focused label handler/service/repository tests, combined handler/service/router tests, migration tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-06: Documentation checkpoint prepared as
docs: refresh parity tracker baseline; current baseline now points at2ab7b58 feat(accounts): align account payloads, the clean handoff state is recorded, and P3.6 label CRUD parity is fully landed as the next executable slice. Direct reference audit coveredLabelsController, label Jbuilder views,Labelmodel/schema, and dashboard labels API/store; current Go gaps are local{ success, data }envelopes,nameinstead oftitle, missingdescription/show_on_sidebar/default color,201/204status drift, and missing label title update/delete association side effects. Verification for this docs-only checkpoint:git diff --check; next implementation commit isfeat(labels): align chatwoot label payloads. - 2026-06-05: Phase 2/3 account settings checkpoint prepared as
feat(accounts): align account payloads; audited ChatwootAccountsController,_account.json.jbuilder, account create/show/update views, dashboardaccount.js, account Vuex store, and account settings components. Account show/update/settings now return raw Chatwoot account objects with settings, features, custom attributes, cache keys, locale/name/domain/status/support fields instead of local success/data envelopes. Account creation now accepts Chatwootaccount_nameand returns{ data: { account_id } }for the add-account modal.update_active_atreturns empty200 OK, andcache_keysreturns{ cache_keys: { label, inbox, team } }with zero-epoch defaults. Focused account handler/service tests, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue account users/invitations/permissions drift and Phase 6 placeholder audit next. - 2026-06-05: Phase 2/3 settings/inbox assignment checkpoint prepared as
feat(settings): align assignable agents payloads; audited ChatwootAssignableAgentsController#index, inboxassignable_agentsview,_agent.json.jbuilder, and reused dashboard assignable-agent API/store consumers. Assignable-agent endpoints now return Chatwoot{ payload: [...] }instead of local success/data envelopes, serialize the same agent fields used by conversation assignment UI, and source role, availability, auto-offline, and custom-role data from the account membership. Focused handler/service tests, combined handler/service/router tests, fullgo test ./..., andgit diff --checkpassed; continue account users/invitations/permissions drift and Phase 6 placeholder audit next. - 2026-06-05: Phase 2/3 settings checkpoint prepared as
feat(settings): align agents teams payloads; audited ChatwootAgentsController,TeamsController,TeamMembersController,_agent.json.jbuilder,_team.json.jbuilder, and reused dashboard agents/teams API/store code. Agents now return raw Chatwoot arrays/objects instead of local{ success, data, meta }envelopes, accept raw frontend request bodies, exposeavailability_status,available_name,confirmed,thumbnail,auto_offline, and custom-role IDs, and matchhead :okfor bulk create/delete. Teams now return rawallow_auto_assign/is_memberpayloads, accept rawallow_auto_assign, preserve explicit false values, return team-member agent arrays, and parse account-scoped:team_idroutes. Focused handler/service/repository/router tests, fullgo test ./..., andgit diff --checkpassed; continue accounts/users/invitations drift and Phase 6 placeholder audit next. - 2026-06-05: Phase 3/6 CRM attachment checkpoint prepared as
feat(crm): align shared attachment payloads; audited ChatwootContacts::AttachmentsController#index,conversations#attachments,_attachment.json.jbuilder, and the reused dashboard shared-files store/API. AddedGET /api/v1/accounts/:account_id/contacts/:contact_id/attachments, aligned conversation attachment lists to{ meta: { total_count }, payload }, serialized shared-file fields through the Chatwoot attachment shape with sender andcreated_at, returned contact attachmentconversation_idas display ID, regenerateddocs/parity/gochat_routes.txttoTOTAL: 837, and reran route parity. Focused service/handler/router tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-05: Phase 2/3 auth drift checkpoint prepared as
feat(auth): align password reset flows; audited ChatwootDeviseOverrides::PasswordsController,DeviseOverrides::ConfirmationsController,Auth::ResendConfirmationsController, and the reused v3 auth frontend. Added Chatwoot user token columns/migration, replaced password reset confirmation placeholders, alignedPOST /auth/passwordreset request message, addedPUT /auth/passwordreset-token password update with auth headers and{ data: profile }, and addedPOST /auth/confirmationconfirmation-token sign-in behavior. Focused auth service/handler tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-05: P6.5 inbox sync-template checkpoint prepared as
feat(inboxes): queue template sync jobs; audited ChatwootWhatsappHealthManagement#sync_templates,Channels::Whatsapp::TemplatesSyncJob, and the reused frontend inbox API/store action.POST /inboxes/:id/sync_templatesnow validates WhatsApp inboxes, enqueues durable low-priorityinbox:sync_templatesjobs instead of synchronously fetching provider templates, replays the existing WhatsApp template fetch/update path in the worker, and returns Chatwoot{ message: "Template sync initiated successfully" }without a localtemplatesenvelope. Focused inbox service/handler/app tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-05: P6.5 inbox placeholder/drift checkpoint prepared as
feat(inboxes): align whatsapp health endpoints; auditedreference/chatwoot/app/controllers/api/v1/accounts/concerns/whatsapp_health_management.rb,Whatsapp::HealthService,Whatsapp::WebhookSetupService, and the reused frontend inbox health client/component.GET /inboxes/:id/healthnow returns the raw WhatsApp Cloud Graph health payload withexpected_webhook_urlandbusiness_id, rejects non-WhatsApp/non-Cloud inboxes with Chatwoot's400error, and drops the generichealthy/status/detailsplaceholder shape.POST /inboxes/:id/register_webhooknow accepts an empty body, builds the default callback fromFRONTEND_URL, returnsWebhook registered successfully, and uses the same Cloud-only guard. Focused WhatsApp, inbox service, inbox handler tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-05: Phase 2/3 notification drift checkpoint prepared as
feat(notifications): align chatwoot notification payloads; auditedreference/chatwootnotifications controller,NotificationFinder, index Jbuilder, and dashboard notifications API/store consumers. Account notifications index now returns Chatwoot{ data: { meta, payload } }, filters read/snoozed items withincludes[]=read/snoozed, reportscount/current_page/unread_count, serializes frontend-required notification fields, marks all notifications or a primary actor as read, returns raw update/unread/snooze notification payloads, returns unread count as a raw number, and honorsdestroy_alltype=readfrom query or JSON body. Focused notification handler/service tests, combined handler/service tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 6 placeholder audit next. - 2026-06-05: B9.3 delayed automation action checkpoint prepared as
feat(automation): close delayed action parity; audited currentreference/chatwootautomation rule model, listener, action services, enterprise action service, and dashboard action inputs and found no configurable delayed action params. Scheduled root/background work remains covered by P5.12, while this checkpoint closes remaining automation action side effects:send_email_to_teamnow preserves Chatwoot object-shaped params and queues durableautomation:team_email_deliveryjobs through the fakeable mailer boundary, and enterpriseadd_slaattaches account-scoped SLA policies plus idempotent AppliedSLA target rows. Focused automation action/execution tests, AutomationRule handler tests, automation/handler/worker/app package tests, fullgo test ./..., andgit diff --checkpassed. B9.3 and Phase 5 move to Review; next slice is Phase 2/3 drift audit. - 2026-06-05: P5.13b scheduled/cached analytics checkpoint prepared as
feat(reports): add analytics timeseries rollups;GET /api/v1/accounts/:account_id/reportsand v2/reportsnow return Chatwoot-style metric timeseries with account/inbox/agent/team/label dimensions, day/hour/week/month/year buckets, and business-hours average support. Analytics reads lazily ensure missing daily rollups,reporting:rollup_daydurable jobs replay account/date recomputes with idempotency keys, and rollup replacement hard-deletes soft-deleted rows before refresh. Route dump is nowTOTAL: 833; tracked route parity remains270 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 277 tracked critical routes. Focused analytics service tests, analytics/live handler tests, service/handler/router/worker/app package tests, route generation/parity, fullgo test ./..., andgit diff --checkpassed. P5.13 moves to Review; B9.3 is completed byfeat(automation): close delayed action parity. - 2026-06-05: P5.13a analytics placeholder burn-down checkpoint prepared as
feat(reports): derive analytics aggregates; live report conversation metrics and grouped metrics now read persisted conversations, bot summary/metrics read reporting events plus active agent-bot inbox bindings, conversation summary reads conversations/messages/reporting events, inbox-label matrix reads inbox/tag/conversation-label rows, first-response distribution buckets reporting events by channel, and outgoing-message counts group by agent/team/inbox/label. Focused analytics service tests, analytics/live handler tests, service/handler/worker/app package tests, fullgo test ./..., andgit diff --checkpassed. Remaining P5.13 follow-up is scheduled/cached rollup freshness and/reportstimeseries index parity. - 2026-06-05: P5.11c Copilot/conversation response checkpoint prepared as
feat(captain): queue copilot response jobs; Copilot thread/message creation now enqueuescaptain:copilot_responseafter persisting the user message, worker replay persists assistant replies through a fakeable backend or the existing no-provider fallback, and backend failures surface as retryable jobs. Pending Captain-enabled incoming conversations now enqueuecaptain:conversation_response_builder, replay creates Captain outgoing replies, queues provider send-reply, and opens conversations on handoff. Focused Copilot/Captain conversation worker tests, service/worker/app package tests, fullgo test ./..., andgit diff --checkpassed. P5.11 moves to Review; next slice is P5.13 analytics aggregation. - 2026-06-05: P5.11b Captain response/embedding checkpoint prepared as
feat(captain): queue response embedding jobs; successful document sync/parser content updates now enqueuecaptain:document_response_builder, response-builder replay resets only unedited document responses, preserves edited responses, creates approvedCaptain::Documentassistant responses through a fakeable FAQ backend, and fans outcaptain:llm_update_embeddingjobs. Embedding replay reloads account-scoped responses and uses a fakeable embedding backend or configured LLM provider, with missing providers surfacing as retryable worker failures. Focused Captain document worker tests, service/worker/app package tests, fullgo test ./..., andgit diff --checkpassed. Remaining P5.11 follow-up is Copilot/conversation response jobs. - 2026-06-05: P5.11a Captain document crawl/schedule checkpoint prepared as
feat(captain): queue document crawl jobs; document create/crawl requests now enqueuecaptain:document_crawl, crawl replay fans out normalizedcaptain:document_page_crawl_parseparser jobs through fakeable crawl/parser boundaries, parser replay creates or updates account-scoped documents with synced content/fingerprints, andcaptain:documents_schedule_syncsscans stale completed documents forcaptain_document_auto_syncaccounts to enqueuecaptain:document_syncjobs with daily idempotency. Focused Captain document worker tests, service/worker/app package tests, fullgo test ./..., andgit diff --checkpassed. Remaining P5.11 follow-up is now Copilot/conversation response jobs after P5.11b. - 2026-06-05: Parity execution tracker checkpoint prepared as
docs: land parity execution tracker; the plan now recordsd756473 feat(webhook): queue incoming messagesas the current implementation baseline, adds executable contracts for P5.11a/P5.11b/P5.11c Captain/Copilot jobs, P5.13 analytics aggregation, Phase 2/3 drift audit, Phase 6 placeholder burn-down, and B12 live smoke, and makes commit/update discipline explicit for future checkpoints. Documentation-only checkpoint;git diff --checkpassed. - 2026-06-05: P5.9 inbound webhook deferral checkpoint prepared as
feat(webhook): queue incoming messages; normalized provider inbound messages now enqueuewebhook:incoming_message_persistwhen WorkerPool is configured, while worker replay creates contact inboxes, conversations, messages, and dispatches incoming events from the durable path with source-ID idempotency. Telegram, LINE, Facebook/Instagram, WhatsApp, TikTok, and Twilio persisters are wired to the WorkerPool. Focused webhook/worker tests passed. Next slice is remaining P5.11 Captain/Copilot jobs. - 2026-06-05: P5.10 provider delivery-status checkpoint prepared as
feat(messages): queue delivery statuses; provider direct status callbacks now enqueuewebhook:message_status_update, contact-scoped read receipts enqueuewebhook:contact_messages_status_update, worker replay upserts delivery status rows, records failed providerexternal_error, preserves monotonic sent/delivered/read transitions, and Twilio status callbacks now support the exact Chatwoot/twilio/delivery_statusroute with204 No Content. Focused webhook tests, route generation, fullgo test ./..., andgit diff --checkpassed; inbound persistence deferral is completed by the next P5.9 checkpoint. - 2026-06-05: P5.10 durable send-reply checkpoint prepared as
feat(messages): queue send replies; outgoing message creation now enqueuesmessage:send_replyon thehighqueue when a WorkerPool is configured, worker replay reloads account-scoped message/conversation/inbox/contact records, skips duplicate/private/voice-call/non-deliverable messages, calls the registered channel provider, persistssource_id, and records retryable provider failures as messagefailedwithcontent_attributes.external_error. Focused service/worker/app tests passed; provider delivery-status depth is completed by the next P5.10 checkpoint. - 2026-06-05: Active parity tracker checkpoint prepared as
docs: land active parity tracking board; the plan now front-loads the current authoritative remaining-work board, points the next implementation slice at P5.10 outbound message delivery, records P5.9 inbound job deferral, P5.11 remaining Captain/Copilot jobs, P5.13 analytics aggregation, Phase 2/3 drift, Phase 6 placeholder audit, and B12 optional live smoke as tracked owners, and reconciles stale B12/M6/M7 status rows. Documentation-only checkpoint;git diff --checkpassed. - 2026-06-04: Baseline stabilized and committed as
42cdab8 chore: stabilize chatwoot parity baseline;go test ./...passed and route dump reportedTOTAL: 704. - 2026-06-04: Phase 1 search foundation added: Meilisearch config/env defaults,
SearchEnginecontract, Meilisearch HTTP wrapper with bootstrap/settings, DB fallback adapter, document builders, reindex command, and no-live-Meilisearch tests. Verifiedgo test ./...in unsandboxed mode because miniredis/httptest need local sockets; route dump still reportsTOTAL: 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
SearchIndexerboundary, 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_parityrecords static Chatwoot route DSL declarations fromreference/chatwoot/config/routes.rb, consumescmd/dump_routesoutput, and writesdocs/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 remainsTOTAL: 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_idas well as legacy:id. Regenerated parity report: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 81; route dump now reportsTOTAL: 709. - 2026-06-04: Expanded Phase 2 route parity from 81 to 138 tracked frontend-critical account routes. Added account-scoped notification routes,
notification_settingsPUT alias, and Chatwoot account-levelinbox_memberscreate/show/update/destroy handlers. Regenerated parity report: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138; route dump now reportsTOTAL: 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/v2reports. Regenerated parity report: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 251; route dump now reportsTOTAL: 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/confignow returns Chatwoot-stylewebsite_channel_config, contact pubsub token, and global config;/api/v1/widget/messagesacceptsX-Auth-Tokenand nestedmessage.content, returns Chatwoot message shape, and exposes latest messages as{payload, meta};/api/v1/widget/contactand 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/widgetstubs with handlers forcampaigns,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 return204; 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/widgetstub burn-down. Message update now persists submitted email/form values and identifies the contact;contact/set_uservalidates identifier HMAC, supports verified contact identification, and returnswidget_auth_tokenwhen 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/inboxescontact/conversation/message placeholders with real Chatwoot public API handlers. API inboxes now resolve throughChannel::Apiidentifiers, public contacts create/update bysource_idwith 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. Verifiedgo test ./..., regenerateddocs/parity/gochat_routes.txt(TOTAL: 791), and regenerateddocs/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_uploadsnow accepts ActiveStorage metadata withwebsite_token+X-Auth-Token, returns the rawsigned_id/direct_uploadblob shape expected byDirectUpload, supports the follow-up PUT body upload, and attachesmessage[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 reportsTOTAL: 793, while tracked parity remains251 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/:idnow resolves the conversation UUID to theinput_csatmessage and returns the Chatwoot public survey payload (csat_survey_response, display type, inbox avatar/name, locale, conversation/message IDs). Public CSAT submit now accepts nestedmessage.submitted_values, updates the survey message content attributes, upserts a message-linked CSAT response, and enforces Chatwoot's 14-day lock with422. Public inbox message update now applies the same lock/response-builder path forinput_csatmessages. 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 remains251 exact, 0 missing. - 2026-06-04: Continued P6.7 webhook ingress parity. Instagram
/webhooks/instagramnow 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/shopifynow verifiesX-Shopify-Hmac-SHA256withSHOPIFY_CLIENT_SECRET, handlesshop/redactby 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 remainsTOTAL: 801and tracked route parity remains251 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, openconversations, and incomingmessagesinstead of only parsing/logging them. Telegram, LINE, Twilio SMS, WhatsApp, Facebook/Instagram, and TikTok are wired through the shared persister; duplicates are skipped byinbox_id + source_id. Added direct persister coverage and Telegram webhook persistence coverage. Focused webhook/channel tests and fullgo test ./...passed. - 2026-06-04: Continued P6.7 dispatch parity by persisting provider receipt/status events. Existing messages are updated from Twilio delivery callbacks, WhatsApp sent/delivered/read/failed statuses, Facebook/Instagram delivery/read receipts, and TikTok read receipts. Added tests for direct status update and Twilio delivery callback update. Focused webhook/channel tests and full
go test ./...passed. - 2026-06-05: Wired P6.7 incoming persistence into the existing dispatcher fan-out boundary. Newly persisted webhook contacts, conversations, messages, and message status updates now emit
ChannelEvents throughDispatchAsync'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 fullgo 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
messagesby 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 fullgo test ./...passed. - 2026-06-05: Added TikTok webhook signature parity.
/webhooks/tiktoknow rejects missing, invalid, or staleTiktok-Signaturevalues using the same timestamp-plus-body HMAC shape as the Chatwoot reference controller, while valid signed payloads still resolve the inbox frombiz_idand persist incoming messages. Focused webhook tests and fullgo test ./...passed. - 2026-06-05: Tightened LINE webhook signature parity.
/webhooks/line/:line_channel_idnow rejects missing signatures when achannel_secretis configured and only persists signed payloads whoseX-Line-Signaturematches the raw request body. Focused webhook tests and fullgo test ./...passed. - 2026-06-05: Completed WhatsApp route-level webhook verification coverage.
/webhooks/whatsapp/:phone_numbernow has tests for verify-token challenge echo, signed Cloud API POST persistence, and missingX-Hub-Signature-256rejection without message persistence. Focused webhook tests and fullgo 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 fullgo 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 conversationdisplay_idrouting is now generated and resolved. Verified focused conversation/message handler tests, fullgo test ./..., andgit 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 fullgo test ./..., route dumpTOTAL: 809, route parity251 exact, 0 missing, andgit diff --checkpassed. - 2026-06-05: B4 contacts/companies second checkpoint committed as
7a033e2 feat(crm): complete contact label avatar gaps; contact labels now persist throughcontact_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 isTOTAL: 817, route parity is261 exact, 0 missing, escalated fullgo test ./...passed, andgit diff --checkpassed. - 2026-06-05: B4 contact merge checkpoint committed as
1e3bf47 feat(crm): align contact merge action;/api/v1/accounts/:account_id/actions/contact_mergenow 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 isTOTAL: 818, route parity is262 exact, 0 missing, fullgo test ./...passed, andgit diff --checkpassed. - 2026-06-05: B4 contact import/data-import checkpoint committed as
8eed2f1 feat(crm): persist contact data imports; contact imports now persist Chatwoot-shapeddata_imports, missingimport_filereturns422, successful imports return empty200 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, fullgo test ./..., andgit diff --checkpassed. - 2026-06-05: B4 contact export artifact checkpoint committed as
47c2b80 feat(crm): persist contact export artifacts;POST /contacts/exportnow persistscontact_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 dumpTOTAL: 819, route parity262 exact, 0 missing, fullgo test ./..., andgit diff --checkpassed. - 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 empty200 OKand hard-deletes notes, company notes include nested user metadata, and the Rails-compatiblePUTnote update route is registered. Focused notes/API tests, fullgo test ./..., regenerated route dumpTOTAL: 820, route parity262 exact, 0 missing, andgit diff --checkpassed. - 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 bylast_activity_at DESC. Focused nested conversation tests, focused handler/service/repository/router tests, escalated fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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 whoselabelscolumn contains labels absent from the account, preserving valid rows, recording failed row counts ondata_imports, and preventing arbitrary tag creation from CSV input. Focused import/service/API tests, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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-stylecontact_export_completemailer boundary for the requesting user's email, using the export download URL and subjectYour contact's export file is available to download., while retaining the persisted notification. Focused export/service/API tests, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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, fullgo test ./..., andgit diff --checkpassed. - 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 asChannel::*, avatar delete returns empty200 OK, async delete message matches Chatwoot, andPATCH /api/v1/accounts/:account_id/inboxes/:inbox_idwas added. Focused inbox tests, handler package tests, route dumpTOTAL: 821, route parity, fullgo test ./..., andgit diff --checkpassed. - 2026-06-05: B5 documentation checkpoint committed as
1f6ec3e docs: record inbox serializer checkpoint; B5.1 is Done and B5.2 multipart/JSONchannel[...]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 fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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'sworking_hourspayload 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 fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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 includingcustom_role_id, make create add-only/idempotent, make update diff-based, and keep delete as empty200 OK. Focused inbox member handler/service/repository tests, handler package tests, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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 nestedtwilio_channelpayloads while returning the raw inbox serializer. Focused channel-depth tests, service inbox tests, handler package tests, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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, nestedusersandinbox_limitsroutes, 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_idis persisted, duplicate/wrong-account/non-negative limit checks are covered, route dump isTOTAL: 829, tracked route parity is267 exact, 7 parameter-compatible, 0 missing, focused service/handler/router tests passed, sandboxed full tests failed only on socket restrictions, escalated fullgo test ./...passed, andgit diff --checkpassed. - 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 empty200 OKfor delete.InboxService.BindChannelkeeps dedicated channel IDs/configs reflected in inbox serialization. Focused Email/Twilio/LINE channel tests, service inbox tests, handler package tests, fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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 Chatwootpayloadenvelopes, default to 15 results, acceptsince/until/fromfrontend 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 fullgo test ./..., andgit diff --checkpassed. Route dump unchanged atTOTAL: 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 unlessGOCHAT_LIVE_MEILI_HOSTis set, rejectedsearch.engine=dbin release validation, and madecmd/reindex_searchfail fast if configured with DB fallback. Focused config/reindex/search tests, fullgo test ./..., andgit diff --checkpassed. 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 fullgo test ./..., andgit diff --checkpassed. 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 empty200 OK, and the serializer is limited to Chatwoot's enterprise SLA policy fields. Focused SLA policy handler tests, SLA service tests, handler package tests, fullgo test ./..., andgit diff --checkpassed. 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 acceptssla_policy_id, validates SLA policy account ownership plus Chatwoot replacement/removal rules, creates exactly one activeapplied_slasrecord, keeps status-only updates idempotent for existing SLA conversations, and includes Chatwoot-styleapplied_slapayload fields in conversation responses. Focused conversation/SLA service and handler tests, fullgo test ./..., andgit diff --checkpassed. 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 incomingmessage_id, and resolved applied SLAs transition tohitormissed. Focused applied-SLA lifecycle tests, handler SLA/conversation tests, fullgo test ./..., andgit diff --checkpassed. 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, fullgo test ./..., route dumpTOTAL: 830, route parity270 exact, 7 parameter-compatible, 0 missing,go test ./cmd/route_parity -count=1, andgit diff --checkpassed. 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. Currentreference/chatwoottiming was verified to keeponly_during_business_hoursas a stored policy flag without changing elapsed-second thresholds, and a regression test records that behavior. Focused applied-SLA tests, service package tests, fullgo test ./..., andgit diff --checkpassed. 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 frontendsince/until,user_ids,inbox_id,team_id, andrating; metrics returnstotal_count,ratings_count, andtotal_sent_messages_count; update/review-note actions return the Chatwoot CSAT serializer. Focused CSAT handler/service tests, handler and automation package tests, fullgo test ./..., andgit diff --checkpassed. 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-shapedmessage.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 oneinput_csattemplate 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, fullgo test ./..., andgit diff --checkpassed. 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, fullgo test ./..., andgit diff --checkpassed. 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 empty200 OK, frontendattribute_key/equal_toconditions and arrayaction_paramsare accepted, responses serialize Chatwoot field names and Unixcreated_on, and get/update/delete/clone are account-scoped. Focused automation handler/service tests, handler and automation package tests, fullgo test ./..., andgit diff --checkpassed. 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 markedDoing, and B9.1b records the exact Chatwoot listener contract. The current reference listener only subscribes to conversation created/updated/opened/resolved and message created, socontact_updatedis 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 dispatchchanged_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, fullgo test ./..., andgit diff --checkpassed. 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, fullgo test ./..., andgit diff --checkpassed. 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_eventnow builds Chatwoot-style automation webhook payloads and delivers through a timeout-bound retryable HTTP boundary,send_email_transcriptparses 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, fullgo test ./..., andgit diff --checkpassed. 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, enforcesMacroPolicy-style public/personal access, deletes and executes with empty200 OK, and resolves executeconversation_idsby display ID before applying labels/status/messages/private notes. Focused macro handler/service tests, handler and automation package tests, fullgo test ./..., andgit diff --checkpassed. 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, fullgo test ./..., andgit diff --checkpassed. 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 sharedAuditService.Recordboundary 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, fullgo test ./..., andgit diff --checkpassed. 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, fullgo test ./..., andgit diff --checkpassed. 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 --checkpassed. - 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 asrole=agentwithcustom_role_id, load custom-role permissions through RBAC/AccountScope, expose profile accountcustom_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]), the402 { error: "Account limit exceeded. Upgrade to a higher plan" }response contract, the separation from capacity-policyInboxCapacityLimit, the required account schema guard, dedicated-channel precheck, no-orphan persistence tests, and updated enterprise status rows. Documentation-only checkpoint;git diff --checkpassed. - 2026-06-05: B10.4 InboxLimit checkpoint prepared as
feat(inboxes): enforce chatwoot inbox limits; account-levelinbox_limitnow gates generic inbox and dedicated channel creation with Chatwoot's exact402error 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 fullgo test ./..., andgit diff --checkpassed. 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 unchangedTOTAL: 830, andgit diff --checkpassed. 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 and202sync marking, assistant responses now support nested bodies, filters, raw serializers, edited tracking, and204delete, bulk actions now accept ChatwootAssistantResponse/AssistantDocumentresource 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 fullgo test ./..., andgit diff --checkpassed. 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 fullgo test ./..., andgit diff --checkpassed. 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, persistcaptain_models/captain_featureson accounts, merge partial preference updates, reject invalid model selections with422, 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 fullgo test ./..., andgit diff --checkpassed. 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-levelmessage_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, fullgo test ./..., andgit diff --checkpassed. 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 assync_disabled, backend failures becomesync_erroror preserve explicit backend error codes, fake success updates content/title/fingerprint/timestamps assynced, and default tests avoid external network/LLM calls. Focused Captain/Copilot tests, handler/service package tests, fullgo test ./..., andgit diff --checkpassed. 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 --checkpassed. Next slice is B11.3d implementation. - 2026-06-05: B11.3d Captain task/tool-call checkpoint prepared as
feat(captain): align task payload persistence; rewrite/summarize/reply suggestion now accept Chatwoot task payloads and return raw{ message, follow_up_context }or422 { error }, label suggestion and follow-up POST routes consume dashboardtasks.jsbodies, no-provider paths returnCaptain is disabled, task outputs persist tocopilot_suggestion_messages, andCopilotMessagevalidates reloadable tool-call keys. Focused CaptainTask/Captain/Copilot handler and service tests, Copilot/Captain repository tests, handler/service package tests, fullgo test ./...with workspaceTMPDIR, andgit diff --checkpassed. Next slice is B11.3e streaming/realtime compatibility. - 2026-06-05: Parity tracker handoff checkpoint prepared as
docs: land parity tracker handoff; the plan now has a front-loaded handoff contract, explicit open-work table for B11.3e/B12/Phase 5/Phase 2/3/Phase 6, exact Hermes source-plan mapping, and a rule that every future checkpoint updates this tracker before commit. Documentation-only checkpoint;git diff --checkpassed. - 2026-06-05: B11.3e streaming/realtime checkpoint prepared as
feat(captain): align streaming fallbacks; Captain stream task routes now share non-stream DTO/account-scope behavior, return deterministic SSE disabled/error states for missing providers and validation errors, and preserve success chunk/done shapes. Copilot message REST payloads now nest threadpush_event_data, dedicated push payload helpers match Chatwoot event data, and the legacy Copilot SSE route has a no-provider disabled guard. Focused CaptainTask/Captain/Copilot handler/service tests and Copilot/Captain repository tests passed; full verification is recorded in the B11.3e section. B11 moves to Review; next active slice is B12 reused Chatwoot frontend smoke. - 2026-06-05: B12.1 frontend smoke harness checkpoint prepared as
test(parity): add frontend smoke harness;cmd/gochatnow providesserveand deterministicseed,.gitignoreno longer hidescmd/gochat,scripts/parity_frontend_smoke.shcan print/check/boot GoChat plus the reusedreference/chatwootVite frontend with Meilisearch-first defaults, anddocs/parity/frontend_smoke_report.mdrecords commands, seed data, logs, and pending smoke matrix owners. Verification:bash -n scripts/parity_frontend_smoke.sh,scripts/parity_frontend_smoke.sh --print,scripts/parity_frontend_smoke.sh --check,go test ./cmd/gochat -count=1, fullgo test ./..., andgit diff --check. Next slice is B12.2 browser/API path assertions. - 2026-06-05: B12.2a API smoke assertion checkpoint prepared as
test(parity): add api smoke assertions; the smoke script now has--api-smokefor live GoChat API checks covering auth/profile, inbox, conversation/message list and create, contact/company show, widget config/message, and public CSAT show. Seed output now includes company, conversation display ID, and CSAT message identifiers. Default verification keeps--api-smokeoptional because it needs a running backend/DB;bash -n,--print,--check,go test ./cmd/gochat, fullgo test ./..., andgit diff --checkpassed locally. Next slice is B12.2b reused-frontend browser navigation. - 2026-06-05: B12.2b browser smoke harness checkpoint prepared as
test(parity): add browser smoke harness;scripts/parity_frontend_smoke.sh --browser-smokenow runs the live API smoke, verifies Chatwoot Vite readiness, and drives the reused Chatwoot login/dashboard entrypoints through headless Chrome DevTools without adding Playwright or frontend adapters. The smoke captures browser requests and console output in.tmp/frontend-smoke/browser-smoke-report.json, while default--checkkeeps live browser execution optional because it needs PostgreSQL, Redis, Meilisearch, GoChat, Vite, and Chrome. Verification:bash -n,node --check,--print,--check, fullgo test ./..., andgit diff --check. Next slice is B12.3 enterprise screen smoke. - 2026-06-05: B12.3a enterprise API smoke checkpoint prepared as
test(parity): add enterprise api smoke coverage;scripts/parity_frontend_smoke.sh --enterprise-smokenow composes the core API smoke with enterprise checks for SLA reports/download, CSAT public submit plus reports/download, automation/macros, audit/custom roles, capacity, Captain preferences/assistants, and Copilot thread/message payloads. Default verification keeps the live enterprise run optional because it needs PostgreSQL, Redis, Meilisearch, and GoChat. Verification:bash -n,--print, and--checkpassed locally. Next slice is B12.3b reused-frontend enterprise browser navigation. - 2026-06-05: B12.3b enterprise browser smoke checkpoint prepared as
test(parity): add enterprise browser smoke coverage;scripts/parity_frontend_smoke.sh --enterprise-browser-smokenow runs enterprise API smoke, verifies Chatwoot Vite, and drives reused Chatwoot enterprise dashboard routes through the Chrome DevTools browser harness. Browser assertions cover SLA, CSAT, automation, macros, audit logs, custom roles, agent capacity, Captain settings/assistants, and Copilot thread request paths. Seed now enables the corresponding enterprise feature flags for route guards. Verification:bash -n,node --check,--print,--check,go test ./cmd/gochat, fullgo test ./..., andgit diff --checkpassed locally. Next slice is Phase 5 job parity. - 2026-06-05: P5.1 durable worker checkpoint prepared as
feat(worker): add durable background jobs; thebackground_jobstable/model and WorkerPool core now cover enqueueing, scheduled jobs, queue filtering, idempotency keys, PostgreSQLSKIP LOCKEDclaims, handler execution, retry/backoff, dead-letter state, stale-lock recovery, and graceful start/stop. Focused worker tests cover idempotent enqueue, completion, retries, schedule/queue gating, and restart pickup. Next slice is P5.2/P5.4 integration of dispatcher and automation jobs onto the durable core. - 2026-06-05: P5.2 durable event dispatch checkpoint prepared as
feat(dispatch): queue async events durably;channel.Dispatcher.DispatchAsyncnow enqueues event replay jobs when a WorkerPool is configured, anddispatch.EventDispatcherenqueues per-listener async jobs while preserving inline sync listeners. Focused channel/dispatch/worker tests prove durable enqueue and worker replay. Next slice is P5.4/P5.5 automation webhook/transcript and CSAT job integration. - 2026-06-05: P5.4 durable automation delivery checkpoint prepared as
feat(automation): queue external action deliveries; automation and macrosend_webhook_event/send_email_transcriptactions now enqueue durable jobs when a WorkerPool is configured, worker handlers invoke the existing fakeable HTTP/SMTP delivery boundaries, and app bootstrap starts the shared durable worker for dispatcher and automation queues. Focused automation/worker/app tests passed. Next slice is P5.5/P5.6 delayed automation, macro fan-out, and CSAT job integration. - 2026-06-05: P5.5/P5.6 durable macro and CSAT checkpoint prepared as
feat(automation): queue macro and csat jobs; macro execute now enqueuesautomation:macro_executionjobs for display-ID fan-out when a WorkerPool is configured, and resolved-conversation CSAT sends now enqueue idempotentcsat:survey_sendjobs. Focused automation/worker/app tests passed. Next slice is delayed automation scheduled items and WhatsApp/Twilio CSAT template delivery. - 2026-06-05: P5.7 durable SLA scan checkpoint prepared as
feat(sla): queue applied sla scans; the Chatwoot enterprise SLA chain now runs throughsla:trigger_accounts,sla:process_account, andsla:process_appliedjobs, self-schedules five-minute root scans, queues account/applied-SLA fan-out, and reuses idempotentAppliedSlaService.Evaluatefor miss events and notifications. Focused SLA/worker/app tests passed. Next slice is delayed automation scheduled items, WhatsApp/Twilio CSAT templates, Captain/Copilot jobs, provider delivery, and conversation maintenance. - 2026-06-05: P5.8 durable contact export checkpoint prepared as
feat(crm): queue contact exports; contact export requests now enqueuecontact:exportjobs on the low queue when a WorkerPool is configured, while worker replay generates the persisted CSV artifact, completion notification, and fakeable completion email. Focused service/API/worker/app tests passed. Next slice is delayed automation scheduled items, WhatsApp/Twilio CSAT templates, Captain/Copilot jobs, provider delivery, and conversation maintenance. - 2026-06-05: P5.12 durable conversation maintenance checkpoint prepared as
feat(conversations): queue maintenance jobs; the Chatwoot scheduled-items chain now runs throughscheduled:trigger_items, due one-off campaigns queuecampaign:trigger_oneoff, snoozed conversations reopen throughconversation:reopen_snoozed, and account auto-resolution fans out throughaccount:conversations_resolution_schedulerandconversation:resolution. Focused service/worker/app tests passed. Remaining Phase 5 follow-up is Captain/Copilot jobs and analytics aggregation. - 2026-06-05: P5.6 durable CSAT template checkpoint prepared as
feat(csat): queue channel templates; inbox CSAT template creation now accepts Chatwoot nested template payloads, queuescsat:template_createjobs when a WorkerPool is configured, and worker replay records WhatsApp template IDs or Twilio content/approval SIDs through a fakeable provider boundary. Focused service/API/worker/app tests passed. Remaining Phase 5 follow-up is Captain/Copilot jobs and analytics aggregation. - 2026-06-05: P5.11 durable Captain document sync checkpoint prepared as
feat(captain): queue document syncs; Captain document sync requests now mark the document syncing and enqueuecaptain:document_syncjobs on thelowqueue, while worker replay uses the existing fakeable sync backend to persist disabled, failed, or synced document states. Focused service/API/worker/app tests passed. Remaining Phase 5 follow-up is now Copilot/conversation response jobs and analytics aggregation after P5.11a/P5.11b. - 2026-06-05: P5.12 durable message status checkpoint prepared as
feat(conversations): queue message status updates; widget and public inbox last-seen updates now enqueueconversation:update_message_statusjobs on thedeferredqueue, and worker replay marks eligible non-incomingsent/deliveredmessages asreadordeliveredup to the viewer timestamp while ignoring invalid statuses and missing conversations. Focused service/worker/app tests passed. Remaining Phase 5 follow-up is Captain/Copilot jobs and analytics aggregation. - 2026-06-05: P5.12 durable account bulk action checkpoint prepared as
feat(conversations): queue bulk actions; account conversation bulk actions now accept Chatwoot frontend payloads withoutaction_name, enqueueconversation:bulk_actionon themediumqueue, and worker replay applies display-ID-scoped status/team/assignee/snooze/label updates without crossing account boundaries. Focused service/API/worker/app tests passed. Remaining Phase 5 follow-up is Captain/Copilot jobs and analytics aggregation. - 2026-06-06: P3.8/P6.6g public help-center checkpoint prepared as
feat(help-center): expose public article lists; audited widgetgetMostReadArticles, Chatwoot public article controller/Jbuilder, and routes594/597. GoChat now exposes/hc/:slug/:locale/articles(.json)plus category article-list aliases, returns public article payloads with portal/category/author/link fields, filters to published locale-scoped non-archived portal articles, honors widgetper_page=6, and sortssort=viewsby view count. Focused article/service/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6h public category checkpoint prepared as
feat(help-center): expose public category lists; audited Chatwoot public categories controller/Jbuilder and routes595-596. GoChat now exposes/hc/:slug/:locale/categories(.json)and/hc/:slug/:locale/categories/:category_slug, returns public category arrays/raw objects without dashboard-only fields, resolves non-archived portals by slug, filters show by category slug plus locale, orders lists by position, and counts only published category articles. Focused category/service/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6i public portal checkpoint prepared as
feat(help-center): expose public portal show; audited Chatwoot public portal controller/Jbuilder and routes590/592. GoChat now exposes/hc/:slugand/hc/:slug/:locale, redirects slug-only requests to the configured default locale, returns public HC portal fields with categories, optional logo, published article counts, category counts, and default locale meta, and rejects archived portals. Focused portal/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6j public portal search checkpoint prepared as
feat(help-center): expose public portal search; audited Chatwoot public portal search controller,_search_article.json.jbuilder, and route593. GoChat now exposes/hc/:slug/:locale/search, trims query input, returns empty payloads for blank queries, filters results to published locale-scoped non-archived portal articles, paginates at 10 results, and returns public search article payloads with plain-text snippets and/hc/:slug/articles/:article_sluglinks. Focused article/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6k public article checkpoint prepared as
feat(help-center): expose public article show; audited Chatwoot public articles show/markdown/tracking controller actions, public article Jbuilder, and routes598-601. GoChat now exposes/hc/:slug/articles/:article_slug, handles.mdand.pngsuffixes through a Gin-compatible dispatcher, returns public article payloads by slug, serves raw markdown for published articles, rejects draft markdown, and increments published article views when serving the tracking pixel. Focused article/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6l public sitemap checkpoint prepared as
feat(help-center): expose public sitemap; audited Chatwoot public portal sitemap action,sitemap.xml.erb, and route591. GoChat now exposes/hc/:slug/sitemap.xml, rejects archived portals, returns sitemap XML for published articles only, uses portal custom domains withhttps://prefixing when no protocol is present, and emits public article links plus ISOlastmoddates. Focused portal/router/route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Documentation checkpoint prepared as
docs: land full parity tracking plan; worktree was clean atefceb22 feat(help-center): expose public sitemap, so this docs-only checkpoint lands the remaining Hermes task-family checklist directly into the master tracker. The plan now maps search, indexing, automation, macros, CSAT, enterprise paid features, and B12 reused-frontend smoke to concrete owner sections, current states, and reopen triggers. Verification for this docs-only checkpoint:git diff --check; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Dashboard app-shell checkpoint prepared as
feat(dashboard): expose app shell routes; audited ChatwootDashboardController#index,routes.rb:19-20,layouts/vueapp.html.erb, and mailer/push helper paths that point to/app/accounts/:account_id/conversations/:display_id. GoChat now exposes/appand/app/*params, serves a dashboard HTML shell withwindow.chatwootConfig,window.globalConfig, and the#appmount, rejects JSON dashboard requests with Chatwoot's406error, and tracks the two app shell routes in route parity. Focused router tests, route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06:
.well-knownapp association checkpoint prepared asfeat(well-known): expose app association files; audited ChatwootAndroidAppController#assetlinks,AppleAppController#site_association,MicrosoftController#identity_association, their views/specs, androutes.rb:657-659. GoChat now exposes/.well-known/assetlinks.json,/.well-known/apple-app-site-association, and/.well-known/microsoft-identity-association.jsonwith env-backed Android package/fingerprint, iOS app ID, app conversation deep-link path, and Azure application ID payloads. Focused router tests, route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Cloudflare custom-domain checkpoint prepared as
feat(custom-domains): expose cloudflare challenge; audited Chatwoot enterpriseCustomDomainsController#verify,Cloudflare::BaseCloudflareZoneService, androutes.rb:660. GoChat now exposes/.well-known/cf-custom-hostname-challenge/:id, resolvesrequest.hostagainstportals.custom_domain, checksssl_settings.cf_verification_id, returns Chatwoot plain-text 404 bodies for unknown domains and mismatched challenges, and servesssl_settings.cf_verification_bodyon success. Focused router tests, route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Twilio voice callback checkpoint prepared as
feat(twilio): expose voice callbacks; audited Chatwoot enterpriseTwilio::VoiceController, voice callback routesroutes.rb:643-646, and existing Go contact-call persistence. GoChat now exposes/twilio/voice/call/:phone,/twilio/voice/status/:phone,/twilio/voice/conference_status/:phone, and/twilio/voice/recording_status/:phone; callbacks resolveChannel::TwilioSmsphones to voice-enabled inboxes, return conference TwiML for persisted call rows, map Twilio status/conference events to call state, and persist callback payloads/recording metadata. Focused router tests, route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 callback audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Twilio root callback checkpoint prepared as
feat(twilio): expose root callback; audited ChatwootTwilio::CallbackController#create,Twilio::DeliveryStatusController#create, and routes639-640. GoChat now exposes/twilio/callback, tracks/twilio/delivery_status, acknowledges root callbacks with204 No Content, resolves inbound events byToorMessagingServiceSid, normalizes phone numbers to the stored Twilio channel rows, and persists inbound SMS/MMS through the existing Twilio incoming-message pipeline and persister. Focused webhook/router tests, route-parity tests, route dump/parity regeneration, fullgo test ./..., andgit diff --checkpassed. Continue Phase 2/3 callback audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Root integration callback checkpoint prepared as
feat(integrations): expose oauth callbacks; audited ChatwootLinear::CallbacksController,Shopify::CallbacksController,Notion::CallbacksController, Shopify account auth, and routes630/634/654. GoChat now exposes/linear/callback,/shopify/callback, and/notion/callback, verifies state, exchanges OAuth codes via configurable token endpoints, persists account hooks with Chatwoot app IDs/access tokens/settings/reference IDs, redirects to reused frontend integration settings, and returns Shopify account authredirect_url. Focused router/service/route-parity tests and route dump/parity regeneration passed; continue the remaining root channel callback audit for Twitter/Google/Microsoft/Instagram/TikTok, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Root channel callback checkpoint prepared as
feat(channels): expose oauth callbacks; audited ChatwootTwitter::CallbacksController,Google::CallbacksController,Microsoft::CallbacksController,Instagram::CallbacksController,Tiktok::CallbacksController, shared OAuth callback behavior, and routes626/649-652. GoChat now exposes/twitter/callback,/google/callback,/microsoft/callback,/instagram/callback, and/tiktok/callback; callbacks exchange OAuth tokens through configurable provider endpoints, create/update email/social channel inboxes, and redirect successful or failed flows to Chatwoot dashboard inbox agents/settings/new-inbox paths. Focused channel callback/router/route-parity tests and route dump/parity regeneration passed; continue Phase 6 placeholder audit, B12 live smoke, or the next evidence-backed route/controller/serializer drift. - 2026-06-06: Assignment policy payload checkpoint prepared as
feat(assignment-policies): align chatwoot payloads; audited Chatwoot account assignment policy controllers, nested assignment-policy inbox controllers, inbox assignment policy controllers, Jbuilder views, reused dashboard assignment policy API/store, and routes306-313. GoChat now returns raw Chatwoot assignment policy arrays/objects, accepts raw and nested request bodies, persists Chatwoot policy fields, exposes assigned inbox counts, replaces inbox policy associations, lists policy inboxes as{ inboxes: [...] }, and returns empty200 OKdeletes. Focused AssignmentPolicy tests, service/repository/router/app/migrate/route-parity packages, escalated fullgo test ./..., route dumpTOTAL: 906, route parity353 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 362, andgit diff --checkpassed; continue Phase 6 placeholder audit, B12 live smoke, or the next evidence-backed drift. - 2026-06-06: Placeholder/smoke audit checkpoint prepared as
docs: record placeholder smoke audit;docs/parity/placeholder_audit.mdnow records thechatwootParityStubaudit and classifies all remaining call sites as public webhook nil-handler guards, with no reused dashboard account/contact/conversation/message/inbox/widget/public critical-path stubs.scripts/parity_frontend_smoke.sh --checkpassed and refresheddocs/parity/frontend_smoke_report.md; live API/browser/enterprise smoke remains pending on the full external stack. Continue B12 live smoke when available or the next evidence-backed P2/P3 drift. - 2026-06-06: P3.2 agent serializer-field checkpoint prepared as
feat(agents): align serializer fields; audited ChatwootAgentsControllerJbuilder views andapp/views/api/v1/models/_agent.json.jbuilder. Account agent list/create/show/update responses now remove localinvited_byandaccount_user_idfields and keep only Chatwoot_agentfields plus enterprisecustom_role_id, preserving account-user role, availability, auto-offline, and custom-role behavior behind the serializer. Focused agent handler/service/repository/router tests, escalated fullgo test ./..., andgit diff --checkpassed; continue remaining P3.2 account users/invitations drift or B12 live smoke. - 2026-06-06: Documentation checkpoint prepared as
docs: land invitation parity tracker; worktree was clean at2acd938 feat(account-users): align inviter id, and the active plan now makes P3.2a invitation/confirmation mail parity the first executable slice. The row records Chatwoot AgentBuilder/Devise confirmation references, current Go owner files, normal confirmation vs invited workspace invitation behavior, excluded SSO/SAML/LDAP/OIDC mail variants, and focused exit gates. Verification for this docs-only checkpoint:git diff --check; next slice isfeat(profile): send confirmation invitations. - 2026-06-06: P3.2a invitation/confirmation mail checkpoint prepared as
feat(profile): send confirmation invitations; audited ChatwootAgentBuilder, Devise confirmation mail view/specs, and profile resend specs. GoChat now persists confirmation/reset tokens, sends fakeable Chatwoot-shaped confirmation or workspace invitation mail from profile resend, sends invitation mail for newly created invited agents, modelsusers.unconfirmed_email, wires the account agent handler in bootstrap, and keeps SSO/SAML/LDAP/OIDC variants excluded. Focused profile/agent tests, combined handler/service/repository/router/migrate/app tests, fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Documentation/tooling checkpoint prepared as
docs: land active parity follow-up; records682d0ab feat(profile): send confirmation invitationsas the latest implementation baseline, refreshes the B12 smoke readiness timestamp, reruns the placeholder audit classification, confirms route dump/parity commands still pass withTOTAL: 905and no route artifact changes, and leaves Phase 6 in Review with only webhook nil-handler fallback stubs. Verification for this docs checkpoint:scripts/parity_frontend_smoke.sh --check, placeholderrg,go run ./cmd/dump_routes,go run ./cmd/route_parity, andgit diff --check; continue Phase 2/3 drift audit first unless the full live smoke stack is available. - 2026-06-06: Documentation checkpoint prepared as
docs: track canned response parity; worktree was clean at75f0e80 feat(dashboard-apps): align chatwoot payloads, and fresh reference/frontend inspection is now landed as P3.14. The tracker records canned response owner files, Chatwoot controller/model/schema/frontend references, raw payload and serializer contracts,PATCH/search=/account-scope/delete-status drift, required tests, and route artifact rules. Verification for this docs-only checkpoint:git diff --check; next slice isfeat(canned-responses): align chatwoot payloads. - 2026-06-06: P3.14 canned response checkpoint prepared as
feat(canned-responses): align chatwoot payloads; audited Chatwoot canned responses controller/model/schema and reused dashboard API/store. GoChat now exposes no-trailing-slash index/create aliases andPATCHupdate, accepts raw and wrapped bodies, returns raw Chatwoot arrays/objects, supportssearch=with Chatwoot ranking, scopes mutations by account, and returns empty200 OKdelete. Focused canned handler/service tests, combined handler/canned/router/route-parity tests, route dump/parity regeneration (TOTAL: 910,358 exact,13 parameter-compatible,0 missing out of 371), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Documentation checkpoint prepared as
docs: record committed parity tracker; confirms4e0113a feat(canned-responses): align chatwoot payloadsis the current committed implementation baseline, marks this master plan as the landed tracker for known Hermes plans and user-confirmed scope, and keeps the next executable queue limited to Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. Verification for this docs-only checkpoint:git diff --check. - 2026-06-06: P3.15 notification subscription checkpoint prepared as
feat(notifications): align subscription payloads; audited Chatwoot notification subscriptions controller, builder, model/schema, and reused dashboard push helper/API. GoChat now exposes the singular user-scopedDELETE /api/v1/notification_subscriptions, tracksPOST/DELETEroute parity, accepts raw and wrapped create bodies, derives identifiers from browser endpoints or FCM device/push tokens, updates/moves duplicate identifiers to the current user, returns raw subscription JSON with string enum values, and makes destroy an empty200 OKno-op for missing matches. Focused notification subscription handler tests, service/repository/router/route-parity tests, route dump/parity regeneration (TOTAL: 912,360 exact,13 parameter-compatible,0 missing out of 373), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.16 teams frontend route checkpoint prepared as
feat(teams): align frontend update routes; audited Chatwoot teams/team-members controllers, Jbuilder serializers, routes296-300, and reused dashboard teams API/store callers. GoChat now exposes the frontendPATCH /api/v1/accounts/:account_id/teams/:team_idupdate path, registers no-trailing-slash team index/create aliases, and registers no-trailing-slash team-member list/create/update/delete aliases used by the dashboard. Focused TeamHandler PATCH test, router/route-parity tests, route dump/parity regeneration (TOTAL: 919,361 exact,13 parameter-compatible,0 missing out of 374), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.17 conversation participant checkpoint prepared as
feat(conversations): align participant payloads; audited Chatwoot participant routes/controller/Jbuilder,_agentserializer, and reused dashboard conversation API calls. GoChat now exposes singleton no-trailing-slash participant routes, tracksGET/POST/PATCH/PUT/DELETE /conversations/:conversation_id/participants, returns raw agent arrays, accepts frontenduser_ids, applies update as final-set synchronization, and returns empty200 OKdestroy responses. Focused ConversationParticipant handler/service tests, router/route-parity tests, route dump/parity regeneration (TOTAL: 925,366 exact,13 parameter-compatible,0 missing out of 379), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.18 conversation direct upload checkpoint prepared as
feat(conversations): align direct upload routes; audited Chatwoot nested direct upload route/controller and reused dashboard ActiveStorage upload callers. GoChat now exposes and tracksPOST /api/v1/accounts/:account_id/conversations/:conversation_id/direct_uploads, returns raw ActiveStoragesigned_id/direct_uploadmetadata, validates account-scoped conversations by display ID with legacy ID fallback, and supports the returned local PUT upload target for account-scoped bytes. Focused upload handler/service/router/route-parity tests, route dump/parity regeneration (TOTAL: 927,367 exact,13 parameter-compatible,0 missing out of 380), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.19 conversation inbox assistant checkpoint prepared as
feat(conversations): expose inbox assistant; audited Chatwoot enterprise conversationinbox_assistant, route165, and reused dashboard conversation API/store Copilot assistant state. GoChat now exposes and tracksGET /api/v1/accounts/:account_id/conversations/:conversation_id/inbox_assistant, resolves conversations by display ID with legacy ID fallback, looks up the account-scoped Captain assistant bound to the conversation inbox, and returns{ assistant: { id, name } }or{ assistant: null }. Focused ConversationCrud handler/service/router/route-parity tests, route dump/parity regeneration (TOTAL: 928,368 exact,13 parameter-compatible,0 missing out of 381), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.20 conversation draft message checkpoint prepared as
feat(conversations): align draft message route; audited Chatwoot singular draft message route/controller/specs. GoChat now exposes and tracksGET/PATCH/PUT/DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/draft_messageswithout a draft ID, resolves conversations by display ID with legacy ID fallback, persists one effective draft per conversation, returns raw{ has_draft, message }show payloads, accepts nesteddraft_message.message, and returns empty200 OKfor update/delete. Focused DraftMessage handler/service/repository/router/route-parity tests, route dump/parity regeneration (TOTAL: 932,372 exact,13 parameter-compatible,0 missing out of 385), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.21 conversation reporting events checkpoint prepared as
feat(conversations): expose reporting events; audited Chatwoot enterprise conversationreporting_events, route166, and_reporting_eventJbuilder serializer. GoChat now exposes and tracksGET /api/v1/accounts/:account_id/conversations/:conversation_id/reporting_events, resolves conversations by display ID with legacy ID fallback, scopes raw reporting-event rows by account and conversation, orders bycreated_at ASC, and returns the raw Chatwoot event array with nullable relation keys preserved. Focused Conversation handler tests, route dump/parity regeneration (TOTAL: 933,373 exact,13 parameter-compatible,0 missing out of 386), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.22 account reporting events checkpoint prepared as
feat(reporting): align account events; audited Chatwoot enterprise account reporting events controller/specs, route234,DateRangeHelper, and_reporting_eventJbuilder serializer. GoChat now returns{ payload, meta }, accepts optional Unixsince/until,inbox_id,user_id, andnamefilters, orders account reporting events bycreated_at DESC, uses fixed 25-row pagination, and tracks the account reporting-events route. Focused ReportingEvent handler tests and route parity regeneration (TOTAL: 933,374 exact,13 parameter-compatible,0 missing out of 387) passed; fullgo test ./...andgit diff --checkpassed. - 2026-06-06: Documentation checkpoint prepared as
docs: land contact inbox parity plan; worktree was clean at2072396 feat(reporting): align account events, and the active plan now lands P3.23 nested contact inbox creation as the next executable slice. The row records Chatwootcontacts/contact_inboxes#create,ContactInboxBuilder,HmacConcern, contact inbox Jbuilder references, current Go owner files, source-ID/HMAC/idempotency/account-scope/serializer gaps, and focused exit gates. Verification for this docs-only checkpoint:git diff --check; next slice isfeat(contacts): align contact inbox creation. - 2026-06-06: P3.23 nested contact inbox creation checkpoint prepared as
feat(contacts): align contact inbox creation; audited Chatwoot nested contact inbox controller/specs,ContactInboxBuilder,HmacConcern, route212, and contact inbox Jbuilder serializer. GoChat now account-scopes nested contact/inbox creation, accepts raw JSON/form/queryinbox_id,source_id, andhmac_verified, generates missing source IDs for API/WebWidget/Email/Sms/Whatsapp/Twilio channels, returns existing contact+inbox+source rows idempotently, persists HMAC verification on creation, and returns raw{ source_id, inbox }. Focused handler/service/repository tests passed; route parity and fullgo test ./...plusgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.24 Slack integration checkpoint prepared as
feat(integrations): align slack parity; audited Chatwoot Slack singleton routes/controller/request specs,Integrations::Slack::HookBuilder,Integrations::Slack::ChannelBuilder, app/hook Jbuilder partials, and reused dashboard integrations API/store. GoChat now exposes no-trailing and trailing Slack singleton routes, supportsPUTandPATCHupdate, creates disabled Slack hooks from OAuthcode, returns raw app payloads, fetches Slack private/public channels with cursor pagination, joins public channels, persists selected channelreference_id/settings/status, returns raw channel arrays, maps invalid channels to Chatwoot's422 { error }, and deletes with empty200 OK. Focused handler/service tests, route dump/parity regeneration (939routes;379 exact,13 parameter-compatible,0 missing out of 392), escalated fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.25 Linear integration checkpoint prepared as
feat(integrations): align linear parity; audited Chatwoot Linear controller/request specs, processor service, GraphQL client queries/mutations, activity-message service, account routes365-373, and reused dashboard Linear API. GoChat now exposes no-trailing and trailing Linear destroy plus teams/team_entities/create/link/unlink/search/linked routes, calls Linear GraphQL through fakeable clients, returns raw Chatwoot data payloads, maps provider failures to422 { error }, resolves conversations by account display ID, builds frontend conversation links, creates Linear activity messages for create/link/unlink, revokes tokens on delete, and returns empty200 OK. Focused Linear handler/service tests, route dump/parity regeneration (939routes;387 exact,13 parameter-compatible,0 missing out of 400), sandbox package tests, escalated fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.26 Shopify integration checkpoint prepared as
feat(integrations): align shopify parity; audited Chatwoot Shopify account controller/specs, integration helper, callback behavior, routes361-364, and reused dashboard integrations/Shopify APIs. GoChat now exposes no-trailing and trailing Shopify destroy, returns raw auth/order payloads, uses Chatwoot422 { error }bodies for missing shop domains and missing contact info, resolves account-scoped contacts, calls Shopify customer search and orders REST endpoints through fakeable clients, appends orderadmin_url, finds callback-created hooks byapp_id: shopify, and deletes with empty200 OK. Focused Shopify handler/service tests, route dump/parity regeneration (940routes;390 exact,13 parameter-compatible,0 missing out of 403), sandbox package tests, escalated fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.27 Notion integration checkpoint prepared as
feat(integrations): align notion parity; audited Chatwoot Notion account destroy controller, route379, Notion callback hook shape, and reused dashboard integrations delete flow. GoChat now exposes no-trailing and trailing Notion destroy, finds callback-created hooks byapp_id: notionwith legacy hook-type fallback, tracks the account route in route parity, and deletes with empty200 OK. Focused Notion handler/service tests, route dump/parity regeneration (941routes;391 exact,13 parameter-compatible,0 missing out of 404), fullgo test ./..., andgit diff --checkpassed; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke.