# 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