Files
gochat/docs/verification/SLA_ASSIGNMENT_POLICY_V2_COMPATIBILITY_REPORT.md
T
2026-06-04 15:44:48 +08:00

19 KiB

SLA Policy + Assignment Policy V2 — Chatwoot API Compatibility Verification Report

Date: 2026-05-26 Task: t_0c833d5c — Verify SLA policy + assignment strategy V2 implementation for 1:1 functional compatibility with Chatwoot's original API


1. Endpoint URL Comparison

SLA Policy

# Chatwoot Endpoint GoChat Endpoint Match? Notes
1 GET /api/v1/accounts/:account_id/sla_policies GET /api/v1/accounts/:account_id/sla_policies ✅ Identical
2 POST /api/v1/accounts/:account_id/sla_policies POST /api/v1/accounts/:account_id/sla_policies ✅ Identical
3 GET /api/v1/accounts/:account_id/sla_policies/:id GET /api/v1/accounts/:account_id/sla_policies/:id ✅ Identical
4 PUT /api/v1/accounts/:account_id/sla_policies/:id PUT /api/v1/accounts/:account_id/sla_policies/:id ✅ Identical
5 DELETE /api/v1/accounts/:account_id/sla_policies/:id DELETE /api/v1/accounts/:account_id/sla_policies/:id ✅ Identical
6 (enterprise) SLA inbox association GET /sla_policies/:id/inboxes ⚠️ Chatwoot public routes.rb has NO inbox association routes for SLA — this is enterprise-only. GoChat has ListInboxes/AddInbox/RemoveInbox. Functional intent is correct (enterprise feature), but no public reference to validate against.
7 (enterprise) SLA inbox association POST /sla_policies/:id/inboxes ⚠️ Same as above
8 (enterprise) SLA inbox association DELETE /sla_policies/:id/inboxes/:inbox_id ⚠️ Same as above
9 GET /api/v1/accounts/:account_id/applied_slas/metrics GET /api/v1/accounts/:account_id/applied_slas/metrics ✅ Chatwoot has applied_slas/metrics as collection route
10 GET /api/v1/accounts/:account_id/applied_slas/download GET /api/v1/accounts/:account_id/applied_slas/download ✅ Chatwoot has applied_slas/download as collection route

Assignment Policy

# Chatwoot Endpoint GoChat Endpoint Match? Notes
1 GET /api/v1/accounts/:account_id/assignment_policies GET /api/v1/accounts/:account_id/assignment_policies_v2 ❌ CRITICAL: GoChat uses assignment_policies_v2 instead of assignment_policies. This breaks API compatibility — Chatwoot clients calling /assignment_policies will get 404.
2 POST /api/v1/accounts/:account_id/assignment_policies POST /api/v1/accounts/:account_id/assignment_policies_v2 ❌ Same suffix mismatch
3 GET /api/v1/accounts/:account_id/assignment_policies/:id GET /api/v1/accounts/:account_id/assignment_policies_v2/:id ❌ Same suffix mismatch
4 PUT /api/v1/accounts/:account_id/assignment_policies/:id PUT /api/v1/accounts/:account_id/assignment_policies_v2/:id ❌ Same suffix mismatch
5 DELETE /api/v1/accounts/:account_id/assignment_policies/:id DELETE /api/v1/accounts/:account_id/assignment_policies_v2/:id ❌ Same suffix mismatch
6 GET /assignment_policies/:id/inboxes GET /assignment_policies_v2/:id/inboxes ❌ Same suffix mismatch
7 POST /assignment_policies/:id/inboxes POST /assignment_policies_v2/:id/inboxes ❌ Same suffix mismatch
8 DELETE /assignment_policies/:id/inboxes/:inbox_id DELETE /assignment_policies_v2/:id/inboxes/:inbox_id ❌ Same suffix mismatch
9 GET /inboxes/:inbox_id/assignment_policy GET /inboxes/:inbox_id/assignment_policy_v2 ❌ CRITICAL: Chatwoot uses singular assignment_policy; GoChat uses assignment_policy_v2. Again breaks compatibility.
10 POST /inboxes/:inbox_id/assignment_policy POST /inboxes/:inbox_id/assignment_policy_v2 ❌ Same suffix mismatch
11 DELETE /inboxes/:inbox_id/assignment_policy DELETE /inboxes/:inbox_id/assignment_policy_v2 ❌ Same suffix mismatch

2. Request Parameter Comparison

SLA Policy Create/Update

Chatwoot (from M11 spec):

  • name (required, string)
  • description (optional, string)
  • first_response_time_threshold (float, seconds)
  • next_response_time_threshold (float, seconds)
  • resolution_time_threshold (float, seconds)
  • only_during_business_hours (boolean)

GoChat:

  • name (required, string) ✅
  • description (optional, string) ✅
  • response_time (int, minutes) ❌ — Different field name AND unit
  • update_time (int, minutes) ❌ — Different field name AND unit
  • resolution_time (int, minutes) ❌ — Different unit (field name partially matches)
  • Missing: only_during_business_hours ❌

Issues:

  1. Field name mismatch: Chatwoot uses first_response_time_threshold / next_response_time_threshold / resolution_time_threshold; GoChat uses response_time / update_time / resolution_time. The names are semantically different.
  2. Unit mismatch: Chatwoot uses seconds (float); GoChat uses minutes (int). Clients sending first_response_time_threshold: 300 (5 min in seconds) would store 300 minutes = 5 hours in GoChat.
  3. Missing field: only_during_business_hours is not in GoChat's Create/Update DTO or model.

Assignment Policy Create/Update

Chatwoot permitted_params:

  • name (required, string)
  • description (optional, text)
  • assignment_order (integer, enum: round_robin:0; enterprise: balanced:1)
  • conversation_priority (integer, enum: earliest_created:0, longest_waiting:1)
  • fair_distribution_limit (integer, default 100, >0)
  • fair_distribution_window (integer, default 3600, >0)
  • enabled (boolean, default TRUE)

GoChat CreatePolicyV2Request:

  • name (required, string) ✅
  • description (optional, string) ✅
  • type (required, enum: round_robin / fair / best_skill_match) ❌ — Different field name and values

GoChat is MISSING these fields from Chatwoot:

  • assignment_order ❌ — replaced by type with different enum values
  • conversation_priority ❌ — not present in DTO
  • fair_distribution_limit ❌ — not present in DTO or model
  • fair_distribution_window ❌ — not present in DTO or model
  • enabled ❌ — not present in DTO or model

Issues:

  1. type vs assignment_order: Chatwoot uses assignment_order as an integer enum (0=round_robin, 1=balanced in enterprise). GoChat replaces this with a type string enum (round_robin, fair, best_skill_match). The values "fair" and "best_skill_match" don't exist in Chatwoot's public code — they may be enterprise-only, but the field name itself is incompatible.
  2. Missing 5 fields: conversation_priority, fair_distribution_limit, fair_distribution_window, enabled are all absent from GoChat's model and DTO. These are core assignment policy fields in Chatwoot.

Inbox Policy Association

Chatwoot inbox-level create:

  • assignment_policy_id (required, in permitted_params)

GoChat SetInboxPolicy:

  • policy_id (required, in JSON body) ⚠️ — Different field name: policy_id vs assignment_policy_id

Chatwoot assignment_policies/inboxes (AddInbox):

  • No explicit params — inbox association uses :id from route + inbox_id from nested route

GoChat AddInbox:

  • inbox_id (required, in JSON body) — GoChat uses POST body inbox_id instead of route param

3. Response JSON Structure Comparison

SLA Policy Response

Chatwoot (from M11 spec):

{
  "id": 1,
  "name": "Standard SLA",
  "description": "...",
  "first_response_time_threshold": 300.0,
  "next_response_time_threshold": 600.0,
  "resolution_time_threshold": 86400.0,
  "only_during_business_hours": false,
  "account_id": 1
}

GoChat model JSON tags:

{
  "id": 1,
  "account_id": 1,
  "name": "Standard SLA",
  "description": "...",
  "response_time": 5,         // minutes, not seconds
  "update_time": 10,          // minutes, different field name
  "resolution_time": 1440,    // minutes
  "paused_at": null,          // extra field not in Chatwoot
  "created_at": "...",
  "updated_at": "...",
  "deleted_at": null          // extra field not in Chatwoot
}

Issues:

  1. Field name mismatches: response_time vs first_response_time_threshold, update_time vs next_response_time_threshold
  2. Unit mismatch: minutes vs seconds
  3. Missing field: only_during_business_hours
  4. Extra fields: paused_at (not in Chatwoot SlaPolicy schema), deleted_at (soft delete exposed)
  5. Response wrapper: GoChat wraps all responses in {success: true, data: {...}} — Chatwoot does NOT use this wrapper pattern for individual resource responses (it renders the object directly via Jbuilder or render json:)

Assignment Policy Response

Chatwoot (from M5 spec):

{
  "id": 1,
  "name": "Default",
  "description": "...",
  "assignment_order": "round_robin",
  "conversation_priority": "earliest_created",
  "fair_distribution_limit": 5,
  "fair_distribution_window": 300,
  "enabled": true,
  "assigned_inbox_count": 2,
  "account_id": 1,
  "created_at": "...",
  "updated_at": "..."
}

GoChat model JSON tags:

{
  "id": 1,
  "account_id": 1,
  "name": "Default",
  "description": "...",
  "type": "round_robin",        // different field name & values
  // MISSING: assignment_order, conversation_priority, fair_distribution_limit, fair_distribution_window, enabled, assigned_inbox_count
  "created_at": "...",
  "updated_at": "...",
  "deleted_at": null
}

Issues:

  1. type vs assignment_order: incompatible field name
  2. Missing 6 fields: conversation_priority, fair_distribution_limit, fair_distribution_window, enabled, assigned_inbox_count
  3. Enum rendering: Chatwoot renders enums as strings ("round_robin", "earliest_created") via Rails enum serialization; GoChat renders type as a plain string which is compatible, but the field name is wrong
  4. Response wrapper: Same {success, data} wrapper issue as SLA

4. Error Handling & Status Codes

Chatwoot Patterns (from controller source)

Scenario Chatwoot Status GoChat Status Match?
Create success 200 (implicit render) 201 (response.Created) ⚠️
Delete success 200 (head :ok) 204 (response.NoContent) for assignment; 200 (response.OK) for SLA ⚠️
Not found 404 404 (via handleServiceError) ✅
Validation error 422 (Rails default) 400 (via AbortWithStatusError) ❌
Unauthorized 403 (Pundit) 401 (account not identified) ❌
Internal error 500 (Rails default) 500 (via handleServiceError fallback) ✅

handleServiceError mapping logic:

  • Contains "not found" → 404 ✅
  • Contains "invalid"/"validation"/"required" → 400 ❌ (should be 422)
  • Everything else → 500 ✅

5. Business Logic Comparison

SLA Policy

Behavior Chatwoot GoChat Match?
Delete cascades inbox associations Yes (has_many dependent: :destroy) Yes (explicit DeleteBySlaPolicy before Delete) ✅
Soft delete vs hard delete Enterprise: uses DeleteObjectJob (async) GORM soft delete (DeletedAt) ⚠️
List returns all policies for account Current.account.sla_policies FindByAccount(accountID) ✅
Applied SLA metrics applied_slas/metrics + applied_slas/download Same routes implemented ✅
only_during_business_hours Yes (in spec) Not implemented ❌

Assignment Policy

Behavior Chatwoot GoChat Match?
Name uniqueness scoped to account validates :name, uniqueness: {scope: :account_id} uniqueIndex:idx_apv2_account_name on (account_id, name) ✅
Name presence validation validates :name, presence: true validate:"required" on DTO ✅
fair_distribution_limit > 0 validates :fair_distribution_limit, numericality: {greater_than: 0} Not present ❌
fair_distribution_window > 0 validates :fair_distribution_window, numericality: {greater_than: 0} Not present ❌
Delete cascades inbox associations has_many :inbox_assignment_policies, dependent: :destroy Explicit DeleteByAssignmentPolicy before Delete ✅
Destroy returns head :ok (200 empty) Yes GoChat returns response.NoContent (204) ❌
Inbox uniqueness constraint validates :inbox_id, uniqueness: true (single unique index) uniqueIndex:idx_apinbox_inbox on inbox_id ✅
Inbox policy create = delete old + add new Yes (remove_inbox_assignment_policy then create) Yes (DeleteByInbox then Create in SetInboxPolicy) ✅
Inbox policy show validates existence validate_assignment_policy checks @inbox.assignment_policy present Returns error if FindByInbox fails ✅

6. Critical Compatibility Issues Summary

BLOCKING Issues (break API compatibility)

  1. Assignment Policy route path: /assignment_policies_v2 vs /assignment_policies. Chatwoot clients will 404 on every endpoint. Must use /assignment_policies for the account-level CRUD routes.

  2. Inbox-level assignment policy route path: /inboxes/:inbox_id/assignment_policy_v2 vs /inboxes/:inbox_id/assignment_policy. Singular resource name mismatch. Must match Chatwoot's assignment_policy (singular).

  3. SLA field name mismatches: response_time/update_time vs first_response_time_threshold/next_response_time_threshold. Clients using Chatwoot field names will silently get zero values (fields not recognized by GoChat JSON binding).

  4. SLA unit mismatch: GoChat stores/returns minutes; Chatwoot uses seconds. A client sending first_response_time_threshold: 300 (5 min in seconds) will result in 300 minutes stored in GoChat — a 60x error.

  5. Assignment Policy missing fields: assignment_order, conversation_priority, fair_distribution_limit, fair_distribution_window, enabled are all absent from GoChat's model and DTO. Chatwoot clients sending these fields will have them silently ignored; responses will lack these fields entirely.

  6. Validation error status code: GoChat returns 400; Chatwoot returns 422. Any client checking for 422 on validation errors will fail.

  7. Inbox policy request field name: policy_id vs assignment_policy_id.

SIGNIFICANT Issues (functional deviation but not breaking)

  1. Response wrapper: GoChat wraps all responses in {success, data} envelope. Chatwoot renders resources directly without envelope. This is a consistent GoChat pattern but differs from Chatwoot.

  2. SLA missing field: only_during_business_hours not implemented in GoChat.

  3. Create response status: GoChat returns 201; Chatwoot returns 200 for create. REST-correct but incompatible with Chatwoot convention.

  4. Delete response status: GoChat uses 204 (assignment) / 200 (SLA with body); Chatwoot uses 200 empty (head :ok). Inconsistent within GoChat itself.

  5. SLA type field vs assignment_order: GoChat uses a type string enum with values (round_robin, fair, best_skill_match) instead of Chatwoot's integer enum assignment_order (round_robin:0). Both "fair" and "best_skill_match" are not standard Chatwoot values — they appear to be enterprise extensions or GoChat additions.

  6. Missing assigned_inbox_count in response: Chatwoot's assignment policy response includes assigned_inbox_count (computed from inbox associations). GoChat doesn't include this.

ACCEPTABLE Deviations

  1. SLA inbox association routes: Not present in Chatwoot public routes.rb (enterprise feature). GoChat implements them — this is correct forward-looking behavior.

  2. SLA soft delete vs hard delete: Different mechanisms but functionally similar for API consumers (deleted records are no longer returned by List).

  3. Applied SLA routes: GoChat implements /applied_slas/metrics and /applied_slas/download matching Chatwoot's applied_slas collection routes.


7. Recommendations

P0 — Must fix before any integration

  1. Rename assignment policy routes: Change /assignment_policies_v2 → /assignment_policies and /assignment_policy_v2 → /assignment_policy in router.go. Keep the _v2 suffix in internal naming (handler, service, model, table) but expose Chatwoot-compatible URLs.

  2. Align SLA field names: Rename GoChat's response_time → first_response_time_threshold, update_time → next_response_time_threshold in both the model and DTO JSON tags. Also add resolution_time_threshold instead of just resolution_time.

  3. Align SLA time units: Switch from minutes to seconds (float) to match Chatwoot. Or add a conversion layer that accepts seconds from clients and stores in a compatible format.

  4. Add missing Assignment Policy fields: Add assignment_order, conversation_priority, fair_distribution_limit, fair_distribution_window, enabled to the model and DTOs. Map type to assignment_order for backward compatibility or replace type entirely.

  5. Fix validation error status code: Return 422 instead of 400 for validation errors (both in handler validation and handleServiceError).

  6. Rename policy_id → assignment_policy_id in SetInboxPolicy request.

P1 — Should fix for full compatibility

  1. Add only_during_business_hours to SLA model and DTOs.

  2. Add assigned_inbox_count to Assignment Policy response (computed field).

  3. Unify delete status codes: Use 200 with empty body (head :ok pattern) for all destroy endpoints to match Chatwoot, or consistently use 204 across both SLA and assignment policy.

  4. Consider response envelope: Evaluate whether the {success, data} wrapper should be removed for Chatwoot API compatibility, or whether it's a deliberate GoChat design choice.


8. Test Coverage Assessment

No tests were found or run for SLA Policy or Assignment Policy V2 modules. Test files should be created to verify:

  • Endpoint routing matches Chatwoot URL patterns
  • Request parameter binding (field names, types, validation)
  • Response JSON structure (field names, values, status codes)
  • Business logic (name uniqueness, inbox uniqueness, cascade deletes, upsert behavior)
  • Error handling (422 for validation, 404 for not found)