TenantAtlas/specs/256-external-support-desk-handoff/data-model.md
ahmido 52ebf63af1
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 2m6s
feat(specs/256): external support desk handoff (#301)
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
2026-04-29 20:16:40 +00:00

161 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 operators 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