G7 Verification Report: SLA Policies, AppliedSLAs, Assignment Policies V2
Task: t_ec88adec — Verify G7 implementation matches Chatwoot original across 5 criteria
1. Interface Paths
SLA Policies CRUD
| Endpoint |
Chatwoot |
Gochat |
Match |
| List |
GET /api/v1/accounts/:account_id/sla_policies |
GET /api/v1/accounts/:account_id/sla_policies |
✅ PASS |
| Create |
POST /api/v1/accounts/:account_id/sla_policies |
POST /api/v1/accounts/:account_id/sla_policies |
✅ PASS |
| Show |
GET /api/v1/accounts/:account_id/sla_policies/:id |
GET /api/v1/accounts/:account_id/sla_policies/:id |
✅ PASS |
| Update |
PUT/PATCH /api/v1/accounts/:account_id/sla_policies/:id |
PUT /api/v1/accounts/:account_id/sla_policies/:id |
⚠️ MINOR — Chatwoot supports both PUT and PATCH; gochat only PUT. Rails resources auto-register both. |
| Delete |
DELETE /api/v1/accounts/:account_id/sla_policies/:id |
DELETE /api/v1/accounts/:account_id/sla_policies/:id |
✅ PASS |
| Endpoint |
Chatwoot |
Gochat |
Match |
| List Inboxes |
N/A (enterprise-only, not in open-source routes) |
GET /sla_policies/:id/inboxes |
🔵 ADDITION — not in Chatwoot open-source routes.rb |
| Add Inbox |
N/A |
POST /sla_policies/:id/inboxes |
🔵 ADDITION |
| Remove Inbox |
N/A |
DELETE /sla_policies/:id/inboxes/:inbox_id |
🔵 ADDITION |
Note: Chatwoot's open-source schema.rb does NOT have a sla_policy_inboxes table, but the enterprise edition may have one. The gochat implementation added these endpoints as a reasonable extension.
AppliedSLAs
| Endpoint |
Chatwoot |
Gochat |
Match |
| Index |
GET /api/v1/accounts/:account_id/applied_slas (index) |
N/A — not implemented |
❌ FAIL — Chatwoot has resources :applied_slas, only: [:index] but gochat does not implement the index (list) endpoint |
| Metrics |
GET /api/v1/accounts/:account_id/applied_slas/metrics |
GET /api/v1/accounts/:account_id/applied_slas/metrics |
✅ PASS |
| Download |
GET /api/v1/accounts/:account_id/applied_slas/download |
GET /api/v1/accounts/:account_id/applied_slas/download |
✅ PASS |
Assignment Policies V2 CRUD
| Endpoint |
Chatwoot |
Gochat |
Match |
| List |
GET /api/v1/accounts/:account_id/assignment_policies |
GET /api/v1/accounts/:account_id/assignment_policies_v2 |
⚠️ MISMATCH — path uses _v2 suffix |
| Create |
POST /api/v1/accounts/:account_id/assignment_policies |
POST /api/v1/accounts/:account_id/assignment_policies_v2 |
⚠️ MISMATCH — path uses _v2 suffix |
| Show |
GET /api/v1/accounts/:account_id/assignment_policies/:id |
GET /api/v1/accounts/:account_id/assignment_policies_v2/:id |
⚠️ MISMATCH |
| Update |
PUT/PATCH /assignment_policies/:id |
PUT /assignment_policies_v2/:id |
⚠️ MISMATCH |
| Delete |
DELETE /assignment_policies/:id |
DELETE /assignment_policies_v2/:id |
⚠️ MISMATCH |
Note: The _v2 suffix is intentional to avoid collision with the existing V1 assignment_policy (singular) route in gochat. Chatwoot's original uses assignment_policies (plural) for the V2 API.
Assignment Policy V2 — Nested Inboxes
| Endpoint |
Chatwoot |
Gochat |
Match |
| List Inboxes |
GET /assignment_policies/:assignment_policy_id/inboxes |
GET /assignment_policies_v2/:id/inboxes |
⚠️ MISMATCH — _v2 suffix + different param name (:id vs :assignment_policy_id) |
| Add Inbox |
POST /assignment_policies/:assignment_policy_id/inboxes |
POST /assignment_policies_v2/:id/inboxes |
⚠️ MISMATCH |
| Remove Inbox |
DELETE /assignment_policies/:assignment_policy_id/inboxes/:id |
DELETE /assignment_policies_v2/:id/inboxes/:inbox_id |
⚠️ MISMATCH — different param names (:id vs :inbox_id) |
Assignment Policy V2 — Reverse (Inbox → Policy)
| Endpoint |
Chatwoot |
Gochat |
Match |
| Show (inbox) |
GET /inboxes/:inbox_id/assignment_policy (show) |
GET /inboxes/:inbox_id/assignment_policy_v2 |
⚠️ MISMATCH — _v2 suffix |
| Create/Set (inbox) |
POST /inboxes/:inbox_id/assignment_policy (create) |
POST /inboxes/:inbox_id/assignment_policy_v2 |
⚠️ MISMATCH — Chatwoot uses POST/create, gochat uses POST/set |
| Delete (inbox) |
DELETE /inboxes/:inbox_id/assignment_policy (destroy) |
DELETE /inboxes/:inbox_id/assignment_policy_v2 |
⚠️ MISMATCH |
PATH SUMMARY: 3 PASS, 1 FAIL (missing applied_slas index), 8 MISMATCH (intentional _v2 suffix), 1 MINOR (missing PATCH support)
2. Request Parameters
SLA Policy Create/Update
| Param |
Chatwoot (schema) |
Gochat (model) |
Match |
| name |
string, not null |
string, not null |
✅ PASS |
| description |
string (text) |
string (text) |
✅ PASS |
| first_response_time_threshold |
float |
int (response_time, in minutes) |
⚠️ MISMATCH — Chatwoot uses float (seconds), gochat uses int (minutes) |
| next_response_time_threshold |
float |
int (update_time, in minutes) |
⚠️ MISMATCH — Chatwoot uses float (seconds), gochat uses int (minutes) |
| resolution_time_threshold |
float |
int (resolution_time, in minutes) |
⚠️ MISMATCH — Chatwoot uses float (seconds), gochat uses int (minutes) |
| only_during_business_hours |
boolean, default false |
N/A — not in model |
❌ FAIL — missing field |
Note: The field naming differs: Chatwoot uses first_response_time_threshold / next_response_time_threshold / resolution_time_threshold (float, in seconds), gochat uses response_time / update_time / resolution_time (int, in minutes). The JSON field names differ: first_response_time_threshold vs response_time.
AppliedSLA Metrics
| Param |
Chatwoot |
Gochat |
Match |
| conversation_id (query) |
Used as filter param |
conversation_id (query param, required) |
✅ PASS — gochat requires it explicitly |
AppliedSLA Download
| Param |
Chatwoot |
Gochat |
Match |
| No specific params |
Account-scoped list |
account_id from auth context |
✅ PASS |
Assignment Policy V2 Create/Update
| Param |
Chatwoot (schema) |
Gochat (model) |
Match |
| name |
string, not null |
string, not null |
✅ PASS |
| description |
text |
text |
✅ PASS |
| assignment_order |
integer (enum: round_robin=0) |
type (enum: round_robin/fair/best_skill_match) |
⚠️ MISMATCH — different field name and enum structure |
| conversation_priority |
integer (enum: earliest_created=0, longest_waiting=1) |
N/A |
❌ FAIL — missing field |
| fair_distribution_limit |
integer, default 100, not null |
N/A |
❌ FAIL — missing field |
| fair_distribution_window |
integer, default 3600, not null |
N/A |
❌ FAIL — missing fields |
| enabled |
boolean, default true, not null |
N/A |
❌ FAIL — missing field |
Note: Gochat's AssignmentPolicyV2 uses a simplified type field (round_robin/fair/best_skill_match) instead of Chatwoot's separate assignment_order + conversation_priority + fair_distribution fields. This is a design simplification, not a direct match.
Inbox Association (add/remove)
| Param |
Chatwoot |
Gochat |
Match |
| inbox_id |
inbox_id in body |
inbox_id (JSON body) |
✅ PASS |
PARAM SUMMARY: 5 PASS, 5 MISMATCH (field naming/units), 5 FAIL (missing fields)
3. Response Format
SLA Policy Response
| Field |
Chatwoot JSON |
Gochat JSON |
Match |
| id |
id |
id |
✅ PASS |
| name |
name |
name |
✅ PASS |
| description |
description |
description |
✅ PASS |
| first_response_time_threshold |
first_response_time_threshold (float) |
response_time (int) |
⚠️ MISMATCH — different field name + type |
| next_response_time_threshold |
next_response_time_threshold (float) |
update_time (int) |
⚠️ MISMATCH |
| resolution_time_threshold |
resolution_time_threshold (float) |
resolution_time (int) |
⚠️ MISMATCH |
| only_during_business_hours |
only_during_business_hours (boolean) |
N/A |
❌ FAIL — missing |
| account_id |
account_id |
account_id |
✅ PASS |
| created_at |
created_at |
created_at |
✅ PASS |
| updated_at |
updated_at |
updated_at |
✅ PASS |
Note: Gochat uses Gin's JSON serialization with json tags on struct fields. Rails uses active_model_serializers / jbuilder. The response structure is similar but field names differ for time thresholds.
AppliedSLA Response
| Field |
Chatwoot |
Gochat |
Match |
| id |
id |
id |
✅ PASS |
| sla_policy_id |
sla_policy_id |
sla_policy_id |
✅ PASS |
| conversation_id |
conversation_id |
conversation_id |
✅ PASS |
| account_id |
account_id |
account_id |
✅ PASS |
| sla_status |
sla_status (integer enum) |
sla_status (string enum) |
⚠️ MISMATCH — Chatwoot uses integer enum (0=active, 1=violated, 2=completed), gochat uses string enum ("active", "violated", "completed") |
| frt/nrt/rt target/actual timestamps |
N/A (not in schema) |
frt_target_at, nrt_target_at, rt_target_at, frt_actual_at, nrt_actual_at, rt_actual_at |
🔵 ADDITION — gochat tracks computed timestamps |
| created_at/updated_at |
created_at, updated_at |
created_at, updated_at |
✅ PASS |
Metrics Response
| Field |
Chatwoot |
Gochat |
Match |
| applied_sla + sla_events |
Returns applied SLA with events |
gin.H{"applied_sla": applied, "sla_events": events} |
✅ PASS — structure matches |
Assignment Policy V2 Response
| Field |
Chatwoot |
Gochat |
Match |
| id |
id |
id |
✅ PASS |
| account_id |
account_id |
account_id |
✅ PASS |
| name |
name |
name |
✅ PASS |
| description |
description |
description |
✅ PASS |
| assignment_order |
assignment_order (integer/enum) |
type (string enum) |
⚠️ MISMATCH |
| conversation_priority |
conversation_priority |
N/A |
❌ FAIL |
| fair_distribution_limit |
fair_distribution_limit |
N/A |
❌ FAIL |
| fair_distribution_window |
fair_distribution_window |
N/A |
❌ FAIL |
| enabled |
enabled |
N/A |
❌ FAIL |
RESPONSE SUMMARY: 6 PASS, 4 MISMATCH, 5 FAIL (missing fields), 1 ADDITION
4. Error Handling / Status Codes
SLA Policies
| Scenario |
Chatwoot |
Gochat |
Match |
| Unauthorized (no account) |
401 |
401 (response.ErrUnauthorized) |
✅ PASS |
| Invalid ID param |
400 |
400 (response.ErrValidation) |
✅ PASS |
| Not found |
404 |
handleServiceError → 404/500 |
⚠️ NEEDS VERIFY — depends on handleServiceError mapping |
| Validation error (create) |
422 |
handleServiceError mapping |
⚠️ NEEDS VERIFY — Chatwoot uses 422 for validation, gochat may use 400 |
| Success create |
200/201 |
response.OK → 200 |
⚠️ MINOR — Chatwoot returns 201 for create, gochat returns 200 |
| Success delete |
200/204 |
response.OK → 200 (returns deleted object) |
⚠️ MINOR — Chatwoot returns 204 No Content for destroy |
AppliedSLA Metrics
| Scenario |
Chatwoot |
Gochat |
Match |
| Missing conversation_id |
400 |
400 (response.ErrValidation) |
✅ PASS |
| Not found |
404 |
handleServiceError |
⚠️ NEEDS VERIFY |
| Unauthorized |
401 |
401 |
✅ PASS |
Assignment Policy V2
| Scenario |
Chatwoot |
Gochat |
Match |
| Unauthorized |
401 |
401 |
✅ PASS |
| Invalid ID |
400 |
400 |
✅ PASS |
| Not found |
404 |
handleServiceError |
⚠️ NEEDS VERIFY |
| Remove inbox (not associated) |
404 |
500 (fmt.Errorf from service) |
❌ FAIL — should be 404 |
| Delete success |
204 |
response.OK → 200 (returns deleted object) |
⚠️ MINOR |
ERROR HANDLING SUMMARY: 4 PASS, 4 MINOR, 2 NEEDS VERIFY, 1 FAIL
5. Business Logic Behavior
SLA Policy Account Scoping
| Behavior |
Chatwoot |
Gochat |
Match |
| All queries scoped to account_id |
Yes (Rails current_account) |
Yes (accountID from auth context, passed to service) |
✅ PASS |
| Account uniqueness on name |
N/A (no unique index on name alone) |
uniqueIndex: account_id + name |
⚠️ EXTRA — gochat adds account+name uniqueness beyond Chatwoot schema |
| Soft delete |
Yes (paranoia gem) |
Yes (gorm.DeletedAt) |
✅ PASS |
SLA Inbox Uniqueness
| Behavior |
Chatwoot |
Gochat |
Match |
| One SLA policy per inbox |
Enterprise-only |
uniqueIndex on inbox_id |
✅ PASS — gochat correctly enforces inbox uniqueness |
| AddInbox validates inbox belongs to account |
N/A |
Yes (service checks accountID) |
✅ PASS |
AppliedSLA Uniqueness
| Behavior |
Chatwoot |
Gochat |
Match |
| Unique on [account_id, sla_policy_id, conversation_id] |
Yes (schema index) |
N/A — no unique constraint enforced in model |
⚠️ MISMATCH — gochat doesn't enforce this unique constraint |
| Account scoping |
Yes |
Yes (AccountID check in GetAppliedSlaMetrics) |
✅ PASS |
SLA Time Threshold Units
| Behavior |
Chatwoot |
Gochat |
Match |
| Threshold stored as float (seconds) |
Yes |
Stored as int (minutes) |
⚠️ MISMATCH — unit conversion needed |
| only_during_business_hours flag |
Yes |
No |
❌ FAIL |
Assignment Policy V2 Assignment Strategy
| Behavior |
Chatwoot |
Gochat |
Match |
| Round robin assignment |
assignment_order: round_robin |
type: round_robin |
✅ PASS (same concept, different field) |
| Fair distribution |
fair_distribution_limit + fair_distribution_window |
type: fair (simplified) |
⚠️ MISMATCH — Chatwoot tracks limit/window, gochat just marks type |
| Best skill match |
N/A (enterprise extension) |
type: best_skill_match |
🔵 ADDITION |
| Conversation priority ordering |
conversation_priority enum (earliest_created, longest_waiting) |
N/A |
❌ FAIL — missing |
| Enabled/disabled flag |
enabled boolean |
N/A |
❌ FAIL — missing |
Assignment Policy Inbox Association
| Behavior |
Chatwoot |
Gochat |
Match |
| One policy per inbox |
Yes (unique index on inbox_id) |
Yes (uniqueIndex on inbox_id) |
✅ PASS |
| SetInboxPolicy replaces existing |
Yes (Rails creates/updates) |
Yes (DeleteByInbox then Create) |
✅ PASS |
| Account scoping on associations |
Yes (via account) |
Yes (AccountID check in GetInboxPolicy) |
✅ PASS |
Cascade/Dependent Deletes
| Behavior |
Chatwoot |
Gochat |
Match |
| Delete policy → delete inbox associations |
Yes (dependent: :destroy) |
N/A — not explicitly handled |
⚠️ NEEDS VERIFY — need to check if GORM cascades |
| Delete policy → delete applied SLAs |
Yes |
N/A — not in service |
⚠️ NEEDS VERIFY |
BUSINESS LOGIC SUMMARY: 7 PASS, 4 MISMATCH, 5 FAIL, 3 NEEDS VERIFY, 2 ADDITION
Overall Verification Summary
| Criterion |
Pass |
Mismatch |
Fail |
Needs Verify |
Addition |
| Interface Paths |
3 |
8 (intentional _v2) |
1 (missing applied_slas index) |
0 |
3 |
| Request Params |
5 |
5 (naming/units) |
5 (missing fields) |
0 |
0 |
| Response Format |
6 |
4 (naming/types) |
5 (missing fields) |
0 |
1 |
| Error Handling |
4 |
4 (minor) |
1 (wrong status for remove-inbox) |
2 |
0 |
| Business Logic |
7 |
4 (units/structure) |
5 (missing features) |
3 |
2 |
| TOTAL |
25 |
21 |
17 |
5 |
6 |
Critical Failures (must fix):
- Missing AppliedSLAs index endpoint — Chatwoot has
resources :applied_slas, only: [:index] but gochat doesn't implement GET /applied_slas (list)
- Missing
only_during_business_hours field on SlaPolicy model
- Missing AssignmentPolicy fields: conversation_priority, fair_distribution_limit, fair_distribution_window, enabled
- Wrong error status for RemoveInbox when inbox not associated — returns 500 instead of 404
- AppliedSLA unique constraint not enforced in gochat model (Chatwoot schema has unique index on [account_id, sla_policy_id, conversation_id])
- SlaEvent missing fields: conversation_id, account_id, sla_policy_id, inbox_id, meta — Chatwoot schema has these
Intentional Design Differences (acceptable):
- Path
_v2 suffix on assignment policies — avoids collision with existing V1 route
- Simplified AssignmentPolicyV2 type field — reduces Chatwoot's multi-field approach (assignment_order + conversation_priority + fair_distribution) to a single type enum
- Time threshold units — minutes (int) vs seconds (float) — functional equivalent with conversion
- SLA inbox associations — added beyond Chatwoot open-source routes (enterprise feature)
- AppliedSLA timestamp tracking — gochat adds frt/nrt/rt target and actual timestamps (enhancement)
- sla_status as string enum vs Chatwoot's integer enum — more readable but API-incompatible
Minor Issues:
- Missing PATCH support on SLA policy update — Rails resources auto-register PUT+PATCH
- Create returns 200 instead of 201 — Chatwoot convention is 201 for resource creation
- Delete returns 200 with body instead of 204 No Content — Chatwoot convention is 204