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