Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 2m6s
Implement external support desk handoff (spec 256). Created and pushed branch `256-external-support-desk-handoff`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #301
161 lines
6.6 KiB
Markdown
161 lines
6.6 KiB
Markdown
# Data Model — External Support Desk / PSA Handoff
|
||
|
||
**Spec**: [spec.md](spec.md)
|
||
|
||
Spec 256 extends the existing support-request truth. No new support-ticket table, resource, or queue artifact is introduced.
|
||
|
||
## Existing Canonical Entity Extended
|
||
|
||
### SupportRequest (`support_requests`)
|
||
|
||
**Purpose**: Canonical tenant-owned support-request truth. Spec 256 extends it so the same row can carry one-way external handoff continuity.
|
||
|
||
**Existing key fields (already in repo)**:
|
||
- `id`
|
||
- `workspace_id`
|
||
- `tenant_id`
|
||
- `operation_run_id`
|
||
- `initiated_by_user_id`
|
||
- `internal_reference`
|
||
- `primary_context_type`
|
||
- `attachment_mode`
|
||
- `severity`
|
||
- `summary`
|
||
- `reproduction_notes`
|
||
- `contact_name`
|
||
- `contact_email`
|
||
- `context_envelope`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
**New fields (planned)**:
|
||
- `external_handoff_mode`
|
||
- type: string
|
||
- required: yes
|
||
- default: `internal_only`
|
||
- allowed values:
|
||
- `internal_only`
|
||
- `create_external_ticket`
|
||
- `link_existing_ticket`
|
||
- `external_ticket_reference`
|
||
- type: nullable string
|
||
- stored when an external ticket was created or linked successfully
|
||
- `external_ticket_url`
|
||
- type: nullable text
|
||
- stored only when the target returns or the operator provides a valid URL
|
||
- `external_handoff_failure_summary`
|
||
- type: nullable text
|
||
- bounded human-readable failure summary for the current request only
|
||
|
||
**Relationships (unchanged)**:
|
||
- belongs to `Workspace`
|
||
- belongs to `Tenant`
|
||
- optionally belongs to `OperationRun`
|
||
- optionally belongs to initiator `User`
|
||
|
||
**Behavioral rules**:
|
||
- `internal_reference` remains the canonical TenantPilot support identifier even when an external ticket exists.
|
||
- `external_handoff_mode` records the operator’s chosen path and replaces the need for a second persisted status family.
|
||
- Spec 256 explicitly narrows Spec 246 immutability in one bounded way: after the internal request is created, the same row may receive exactly one synchronous finalization write limited to `external_handoff_mode`, `external_ticket_reference`, `external_ticket_url`, and `external_handoff_failure_summary`. No later edit, reopen, merge, or status workflow is introduced.
|
||
- `external_ticket_reference` and `external_ticket_url` remain null for `internal_only` and for failed create attempts.
|
||
- `external_handoff_failure_summary` remains null on successful create, successful link, and internal-only submissions.
|
||
- On a failed external create, the row persists with:
|
||
- `external_handoff_mode = create_external_ticket`
|
||
- `external_ticket_reference = null`
|
||
- `external_ticket_url = null`
|
||
- `external_handoff_failure_summary` populated
|
||
- When the failed external create was caused by timeout, `external_handoff_failure_summary` stores the same bounded timeout-oriented message that the UI and audit path use. Raw transport detail is never persisted.
|
||
|
||
**Latest-summary query rules**:
|
||
- Tenant dashboard summary queries the latest support request for the current entitled tenant where `primary_context_type = tenant`.
|
||
- Operation-run summary queries the latest support request for the current run where `primary_context_type = operation_run` and `operation_run_id` matches the viewed run.
|
||
- Existing indexes on `(tenant_id, created_at)` and `(operation_run_id, created_at)` are sufficient. No new lookup path by external reference is planned.
|
||
|
||
**Validation rules**:
|
||
- `external_handoff_mode` must be one of the three allowed values.
|
||
- `external_ticket_reference` is required when `external_handoff_mode = link_existing_ticket`.
|
||
- `external_ticket_url` is optional but must be a valid URL when present.
|
||
- When no external target is configured for the application, the form must force or constrain the effective mode to `internal_only`.
|
||
|
||
## Application-Configured External Target (Config Contract In Scope, Not New Persisted Truth)
|
||
|
||
### External Support Desk Target
|
||
|
||
**Purpose**: Supplies the one configured outbound target for create or link normalization.
|
||
|
||
**Status in Spec 256**:
|
||
- minimal application config contract in scope
|
||
- not a new persisted entity in this slice
|
||
- not a workspace settings domain or UI surface in this slice
|
||
|
||
**Repo-grounded note**:
|
||
- The repo has no existing `support` settings domain, so Spec 256 makes the target seam explicit through one application config file: `apps/platform/config/support_desk.php` with environment-backed values for the single supported target.
|
||
- This config contract may define availability, create endpoint settings, reference-link normalization defaults, and the five-second outbound timeout budget.
|
||
- Per-workspace target selection, settings UI, or a second target remain follow-up scope.
|
||
|
||
## Derived Runtime Entities
|
||
|
||
### SupportRequestHandoffOutcome (computed, not persisted)
|
||
|
||
**Purpose**: Gives the Filament page actions one normalized outcome for notification copy and tests after submission completes.
|
||
|
||
**Expected shape**:
|
||
- `support_request_id`
|
||
- `internal_reference`
|
||
- `primary_context_type`
|
||
- `handoff_mode`
|
||
- `handoff_outcome`
|
||
- `internal_only`
|
||
- `external_ticket_created`
|
||
- `external_ticket_linked`
|
||
- `external_handoff_failed`
|
||
- `external_ticket_reference`
|
||
- `external_ticket_url`
|
||
- `failure_summary`
|
||
|
||
**Why derived only**:
|
||
- The outcome is an execution summary for one request cycle.
|
||
- Persisting it separately would duplicate the support-request truth and audit log.
|
||
- The bounded synchronous finalization write on `SupportRequest` remains the only allowed post-create mutation for this slice.
|
||
|
||
### LatestSupportRequestHandoffSummary (computed, not persisted)
|
||
|
||
**Purpose**: Supplies the existing tenant and run support actions with one scoped summary of the latest linkage for the current primary context.
|
||
|
||
**Expected shape**:
|
||
- `internal_reference`
|
||
- `primary_context_type`
|
||
- `primary_context_id`
|
||
- `submitted_at`
|
||
- `external_handoff_mode`
|
||
- `external_ticket_reference`
|
||
- `external_ticket_url`
|
||
- `external_handoff_failure_summary`
|
||
- `has_external_link`
|
||
- `has_failure`
|
||
|
||
**Why derived only**:
|
||
- It is a read model over the latest `support_requests` row for one context.
|
||
- A separate table or persisted summary would violate `PERSIST-001` without solving a distinct lifecycle problem.
|
||
|
||
## Audit Events (Persistent Audit Truth, Not Product Truth)
|
||
|
||
The implementation should add these stable audit actions in addition to the existing `support_request.created` event:
|
||
|
||
- `support_request.external_ticket_created`
|
||
- `support_request.external_ticket_linked`
|
||
- `support_request.external_handoff_failed`
|
||
|
||
**Audit context should include**:
|
||
- `workspace_id`
|
||
- `tenant_id`
|
||
- `internal_reference`
|
||
- `primary_context_type`
|
||
- `primary_context_id`
|
||
- `external_handoff_mode`
|
||
- `external_ticket_reference` when present
|
||
|
||
**Audit context should not include**:
|
||
- raw provider request payloads
|
||
- secrets or credentials
|
||
- unrestricted provider response bodies |