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_timefirst_response_time_threshold, update_timenext_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_idassignment_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)