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

17 KiB

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

SLA Inbox Associations (EXTRA — not in Chatwoot routes.rb)

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):

  1. Missing AppliedSLAs index endpoint — Chatwoot has resources :applied_slas, only: [:index] but gochat doesn't implement GET /applied_slas (list)
  2. Missing only_during_business_hours field on SlaPolicy model
  3. Missing AssignmentPolicy fields: conversation_priority, fair_distribution_limit, fair_distribution_window, enabled
  4. Wrong error status for RemoveInbox when inbox not associated — returns 500 instead of 404
  5. AppliedSLA unique constraint not enforced in gochat model (Chatwoot schema has unique index on [account_id, sla_policy_id, conversation_id])
  6. SlaEvent missing fields: conversation_id, account_id, sla_policy_id, inbox_id, meta — Chatwoot schema has these

Intentional Design Differences (acceptable):

  1. Path _v2 suffix on assignment policies — avoids collision with existing V1 route
  2. Simplified AssignmentPolicyV2 type field — reduces Chatwoot's multi-field approach (assignment_order + conversation_priority + fair_distribution) to a single type enum
  3. Time threshold units — minutes (int) vs seconds (float) — functional equivalent with conversion
  4. SLA inbox associations — added beyond Chatwoot open-source routes (enterprise feature)
  5. AppliedSLA timestamp tracking — gochat adds frt/nrt/rt target and actual timestamps (enhancement)
  6. sla_status as string enum vs Chatwoot's integer enum — more readable but API-incompatible

Minor Issues:

  1. Missing PATCH support on SLA policy update — Rails resources auto-register PUT+PATCH
  2. Create returns 200 instead of 201 — Chatwoot convention is 201 for resource creation
  3. Delete returns 200 with body instead of 204 No Content — Chatwoot convention is 204