# Feature Specification: External Support Desk / PSA Handoff **Feature Branch**: `256-external-support-desk-handoff` **Created**: 2026-04-29 **Status**: Draft **Input**: User description: "Prepare the next open candidate External Support Desk / PSA Handoff as the narrowest repo-grounded slice that extends the already-implemented in-app support request flow with one-way external ticket create or link behavior, stores the resulting external reference on the existing support-request truth, and keeps visibility on the existing tenant and operation-run support contexts only." ## Spec Candidate Check *(mandatory - SPEC-GATE-001)* - **Problem**: TenantPilot already captures support requests with internal `SR-...` references, redacted context, and audit truth, but support follow-through still breaks at the product boundary because operators must create or paste external service-desk tickets manually outside the current workflow. - **Today's failure**: A tenant or run-scoped support request can be submitted from the product, yet the product cannot tell the operator whether an external ticket was created, linked, or failed. That creates manual duplicate work, weakens audit continuity, and leaves no durable external-ticket linkage in the current support context. - **User-visible improvement**: The existing `Request support` action can create a new external desk ticket or link an already-created ticket through one configured external desk target, then show the resulting external reference or explicit failure in the same tenant or run support context. - **Smallest enterprise-capable version**: Extend the existing `SupportRequest` truth and `SupportRequestSubmissionService` so one configured external support desk target can be used during the existing tenant-dashboard and operation-run support flows, persist the resulting external ticket reference or last handoff failure on the same support request, audit create or link outcomes, and surface the latest handoff summary only on those same support contexts. - **Explicit non-goals**: No new support-request creation flow, no support-request resource/list/detail page, no support inbox or queue product, no generic helpdesk framework, no multi-provider adapter registry, no bidirectional sync, no external ticket status polling, no SLA engine, no retry scheduler, no AI support automation, and no cross-workspace or cross-tenant handoff shortcuts. - **Permanent complexity imported**: One bounded provider-owned handoff adapter for a single configured external target, a small external-handoff mode family on the existing support-request truth, a nullable persisted external ticket reference and URL on `support_requests`, a bounded persisted handoff failure summary, targeted audit action IDs, and focused unit plus feature coverage. - **Why now**: `docs/product/spec-candidates.md` and `docs/product/roadmap.md` both confirm that support-request creation is already repo-real and that the remaining commercialization gap is external handoff plus visible ticket linkage, not another internal support intake feature. - **Why not local**: Page-local ticket creation or a manual copy field on each surface would duplicate logic that already lives in `SupportRequestSubmissionService`, would drift audit and failure behavior between tenant and run contexts, and would still not create one durable support-request-to-external-ticket truth. - **Approval class**: Workflow Compression - **Red flags triggered**: New provider seam, new persisted fields on an existing truth model, and multi-surface action changes. Defense: the slice extends one existing model and one existing submission path, stays on two already-support-aware surfaces, and explicitly forbids a generic helpdesk or queue framework. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12** - **Decision**: approve ## Spec Scope Fields *(mandatory)* - **Scope**: tenant, canonical-view - **Primary Routes**: - existing tenant dashboard at `/admin/t/{tenant}` via `App\Filament\Pages\TenantDashboard` - existing canonical operation detail at `/admin/operations/{run}` via `App\Filament\Pages\Operations\TenantlessOperationRunViewer` - no new dedicated support desk or support-request route in v1 - **Data Ownership**: - `support_requests` remains the canonical tenant-owned truth and continues to carry required `workspace_id` and `tenant_id` - external handoff truth extends that same record only: external ticket reference, external ticket URL, handoff mode, and last handoff failure are stored on the existing support request rather than in a new ticket-link model or table - one configured external support desk target is treated as application-configured integration truth and is referenced during handoff, but it is not mirrored into tenant-owned support-request records beyond the neutral external linkage fields needed for operator continuity and auditability - **RBAC**: - workspace membership and tenant entitlement remain the first isolation boundaries - the existing `Capabilities::SUPPORT_REQUESTS_CREATE` capability continues to gate support-request submission and any visible create-or-link external handoff controls - non-members or actors not entitled to the workspace or tenant scope receive `404` - members inside scope who lack `Capabilities::SUPPORT_REQUESTS_CREATE` receive `403` - run-context handoff and any latest-handoff summary on the run page resolve the run's tenant first and must not reveal linkage state for a tenant the actor cannot access For canonical-view specs, the spec MUST define: - **Default filter behavior when tenant-context is active**: `N/A` - the feature does not add a canonical collection page; the operation-run surface stays bound to the currently opened run only. - **Explicit entitlement checks preventing cross-tenant leakage**: Any lookup used to show a latest external handoff summary on the operation-run support context must resolve through the current run's entitled tenant scope and the current workspace. Known internal support references or external ticket references must not bypass that scope check. ## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)* - **Cross-cutting feature?**: yes - **Interaction class(es)**: header actions, contextual support capture, success and failure notifications, support-context summaries, audit events, and external-link navigation - **Systems touched**: `App\Filament\Pages\TenantDashboard`, `App\Filament\Pages\Operations\TenantlessOperationRunViewer`, `App\Support\SupportRequests\SupportRequestSubmissionService`, `App\Support\SupportRequests\SupportRequestContextBuilder`, `App\Support\SupportRequests\SupportRequestReferenceGenerator`, `App\Services\Audit\WorkspaceAuditLogger`, `App\Support\Audit\AuditActionId`, `App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder`, and existing `UiEnforcement` capability gating on both support actions - **Existing pattern(s) to extend**: the current `Request support` slide-over actions, current support-request success feedback, current support-diagnostics context summary, current audit logging path, and current tenant/run support authorization boundaries - **Shared contract / presenter / builder / renderer to reuse**: `SupportRequestSubmissionService`, `SupportRequestContextBuilder`, `WorkspaceAuditLogger`, `UiEnforcement`, and the existing support-diagnostics bundle as the canonical redacted context source - **Why the existing shared path is sufficient or insufficient**: The current shared path already assembles support-safe context, issues the internal `SR-...` reference, and writes audit truth consistently from both existing entry surfaces. It is insufficient only because it stops at internal persistence and cannot persist or surface external desk follow-through. - **Allowed deviation and why**: One provider-owned external handoff adapter or service is allowed inside the support-request path because one configured external desk target must be called or normalized from both surfaces. No second page-local handoff client, no generic helpdesk registry, and no parallel support-summary vocabulary are allowed. - **Consistency impact**: `Request support`, `Support reference`, `External ticket`, `Create external ticket`, `Link existing ticket`, and handoff failure wording must have the same meaning on tenant and run surfaces, in success or failure notifications, and in audit summaries. - **Review focus**: Reviewers must block any page-local external desk payload builder, any second support-ticket persistence model, and any new support status language that duplicates the existing support-request truth instead of extending it. ## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)* - **Touches OperationRun start/completion/link UX?**: no - **Shared OperationRun UX contract/layer reused**: `N/A` - **Delegated start/completion UX behaviors**: `N/A` - **Local surface-owned behavior that remains**: The operation-run page continues to use the current run only as support context. External desk handoff must not create, resume, or otherwise mutate an `OperationRun`. - **Queued DB-notification policy**: `N/A` - **Terminal notification path**: `N/A` - **Exception required?**: none ## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)* - **Shared provider/platform boundary touched?**: yes - **Boundary classification**: provider-owned - **Seams affected**: outbound create request payloads, external ticket URL and reference normalization, external-target credential resolution, provider-specific response parsing, and provider-specific failure normalization - **Neutral platform terms preserved or introduced**: support request, support reference, external ticket, external ticket reference, external ticket URL, external handoff mode, external handoff failure, and latest handoff summary - **Provider-specific semantics retained and why**: Authentication, request payload shape, URL templates, provider-specific ticket IDs, and provider-specific validation rules remain inside the one configured external desk adapter because only one concrete target exists in the current release slice. - **Why this does not deepen provider coupling accidentally**: The `SupportRequest` record stores only neutral linkage truth needed for operator continuity: the external reference, optional URL, selected handoff mode, and explicit last failure summary. It does not store provider-specific fields such as assignee, queue, SLA, raw payloads, or external status history. - **Follow-up path**: `follow-up-spec` only if a second real external desk target exists or the first target proves that a provider-neutral shared boundary is genuinely needed. ## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)* | Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | |---|---|---|---|---|---|---| | Tenant dashboard `Request support` action | yes | Native Filament action + shared support primitives | header actions, support capture, support diagnostics, success or failure notifications | page, action form, bounded latest-handoff summary | yes | Existing tenant-dashboard action-surface exception remains bounded; the feature extends the current slide-over instead of adding a support page | | Operation run `Request support` action | yes | Native Filament action + shared support primitives | grouped detail actions, support capture, monitoring-state support context, success or failure notifications | detail page, action form, bounded latest-handoff summary | no | Extends an already support-aware run-detail action instead of adding a second run-support surface | ## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)* | Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction | |---|---|---|---|---|---|---|---| | Tenant dashboard `Request support` action | Secondary Context Surface | The operator decides that the current tenant issue needs external escalation or explicit desk linkage | current support summary, external handoff mode choice, latest external handoff summary when one exists, and what submitting will write | deeper support diagnostics remain on the neighboring `Open support diagnostics` action | Secondary because the operator is still primarily troubleshooting the tenant, not working in a support-desk inbox | Follows current tenant troubleshooting and support-escalation flow | Removes manual copy-paste and out-of-band ticket bookkeeping from the tenant troubleshooting path | | Operation run `Request support` action | Secondary Context Surface | The operator decides that the current run already contains enough context to hand off or link to an external desk | run identity, external handoff mode choice, latest external handoff summary when one exists, and what submitting will write | deeper run diagnostics remain on the existing support-diagnostics action and run detail sections | Secondary because the operator is still primarily inspecting one run | Follows current run drill-in workflow | Removes the need to recreate the same run context in an external desk after the operator has already drilled into it | ## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)* | Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention | |---|---|---|---|---|---|---|---| | Tenant dashboard `Request support` action | operator-MSP, support-platform | support summary, selected mutation scope, handoff mode choice, latest external ticket reference or last failure, and required form fields | redacted support diagnostics remain secondary and separately opened | raw provider payloads, external desk raw payloads, and provider-specific response bodies stay hidden | `Submit support request` | diagnostics remain capability-gated; any provider-specific fields stay inside the adapter and never appear as default-visible operator content | the slide-over states the current handoff truth once and links it to a specific internal support reference instead of duplicating support-request history blocks | | Operation run `Request support` action | operator-MSP, support-platform | run identity, mutation scope note, handoff mode choice, latest external ticket reference or last failure, and required form fields | redacted run diagnostics remain secondary and separately opened | raw provider payloads, external desk raw payloads, and provider-specific response bodies stay hidden | `Submit support request` | diagnostics remain capability-gated; run detail stays the primary evidence surface | the slide-over shows only the latest bounded linkage summary for this run context instead of becoming a support-request register | ## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)* | Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | Tenant dashboard `Request support` action | Dashboard / Overview / Actions | Tenant support escalation entry point | submit the support request with one chosen external handoff mode | explicit header action opens the existing slide-over | forbidden | `Open support diagnostics` remains the neighboring secondary action; any external link stays inside the same support context summary | none | `/admin/t/{tenant}` | `/admin/t/{tenant}` | active workspace, active tenant, and current support context summary | Support request / External ticket | whether the next submit stays internal-only, creates an external ticket, or links an existing ticket | dashboard_exception - existing tenant dashboard action-surface exception remains bounded and justified by the dashboard's role as the tenant troubleshooting hub | | Operation run `Request support` action | Record / Detail / Actions | Run-centered support escalation entry point | submit the support request with one chosen external handoff mode | explicit detail action in the existing grouped support actions | forbidden | `Open support diagnostics` remains grouped beside `Request support`; any external link stays inside the same support slide-over | none | `/admin/operations` | `/admin/operations/{run}` | workspace context, entitled tenant context, and current operation identifier | Support request / External ticket | whether the current run context already has an external ticket linkage or a visible last handoff failure | none | ## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)* | Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions | |---|---|---|---|---|---|---|---|---|---|---| | Tenant dashboard `Request support` action | Workspace manager or support-capable tenant operator | Decide whether this tenant issue should stay internal, create a new external ticket, or link an existing ticket | Dashboard action + contextual slide-over | How do I hand this tenant issue off without losing the current support-request truth? | internal support reference after submit, chosen handoff mode, latest external ticket reference or failure, summary, contact defaults, and context attachment summary | full support diagnostics remain in the neighboring diagnostics surface | external handoff mode, external linkage presence, handoff failure presence, attachment completeness | TenantPilot only or TenantPilot + external support desk, depending on the selected handoff mode | Submit support request | none | | Operation run `Request support` action | Workspace manager or support-capable operator | Decide whether this run issue should stay internal, create a new external ticket, or link an existing ticket | Detail action + contextual slide-over | How do I hand this run issue off without recreating the case outside the product? | internal support reference after submit, chosen handoff mode, latest external ticket reference or failure, summary, contact defaults, and run-context attachment summary | full run diagnostics remain in the run detail and diagnostics surface | external handoff mode, external linkage presence, handoff failure presence, attachment completeness | TenantPilot only or TenantPilot + external support desk, depending on the selected handoff mode | Submit support request | none | ## Proportionality Review *(mandatory when structural complexity is introduced)* - **New source of truth?**: no - the feature extends the existing `SupportRequest` truth rather than introducing a second ticket-link model or support queue truth - **New persisted entity/table/artifact?**: no new table; yes, the existing `support_requests` truth gains bounded external handoff fields needed for operator continuity and auditability - **New abstraction?**: yes - one provider-owned external handoff adapter or service for the single configured target - **New enum/state/reason family?**: yes - one small external handoff mode family (`create_external_ticket`, `link_existing_ticket`, `internal_only`) with direct operator and mutation consequences - **New cross-domain UI framework/taxonomy?**: no - **Current operator problem**: support requests already exist, but the product still cannot show whether an external desk ticket exists or was attempted from the same support context - **Existing structure is insufficient because**: the current submission service and UI end at internal persistence and cannot safely call or normalize an external desk, store the resulting reference, or keep failure truth visible for the current context - **Narrowest correct implementation**: extend the current `SupportRequest` submission path and current tenant or run support actions only, add one provider-owned handoff adapter for one configured target, and store only the minimal linkage truth on the same support request - **Ownership cost**: extra `support_requests` columns, one provider-owned handoff adapter or service, stable audit IDs for external handoff outcomes, slightly richer action forms, and focused unit plus feature coverage - **Alternative intentionally rejected**: a new `SupportTicket` model, a support-request detail resource, or a generic multi-provider helpdesk framework was rejected because the repo currently has only one real external desk use case and already has the support-request truth needed for v1 - **Release truth**: current-release support follow-through and commercialization gap, not future multi-provider preparation ### Compatibility posture This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec. Canonical replacement is preferred over preservation. ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Unit, Feature - **Validation lane(s)**: fast-feedback, confidence - **Why this classification and these lanes are sufficient**: Unit coverage can prove the external handoff adapter normalization rules, handoff mode branching, and latest-handoff summary derivation cheaply. Focused Filament feature coverage can prove tenant and run action behavior, `404` versus `403` boundaries, persisted linkage truth, explicit failure handling, and audit events without needing browser-only coverage. - **New or expanded test families**: one bounded `Unit/Support/SupportRequests/ExternalSupportDesk*` family and one bounded `Feature/SupportRequests/*ExternalHandoff*` family - **Fixture / helper cost impact**: moderate. Reuse existing workspace, tenant, operation run, support request, and authorization fixtures. Add only the narrow target-configuration and adapter-fake setup needed for create or link success and failure paths. - **Heavy-family visibility / justification**: none - **Special surface test profile**: standard-native-filament, monitoring-state-page - **Standard-native relief or required special coverage**: standard Filament action coverage is sufficient for the tenant dashboard action. The run-context action must also preserve the existing canonical monitoring-state-page authorization and context rules. - **Reviewer handoff**: Reviewers must confirm that no support-request resource or queue page appears, that create failures keep the internal support request, that latest external linkage stays scoped to the current entitled context, and that no provider-specific payloads leak into the persisted support-request truth. - **Budget / baseline / trend impact**: low-to-moderate increase in narrow unit plus feature coverage only - **Escalation needed**: none - **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage - **Planned validation commands**: - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/SupportRequests/ExternalSupportDeskHandoffServiceTest.php tests/Unit/Support/SupportRequests/SupportRequestLatestHandoffSummaryTest.php` - `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/SupportRequests/TenantSupportRequestExternalHandoffTest.php tests/Feature/SupportRequests/OperationRunSupportRequestExternalHandoffTest.php tests/Feature/SupportRequests/SupportRequestExternalHandoffAuthorizationTest.php tests/Feature/SupportRequests/SupportRequestExternalHandoffAuditTest.php` ## External Handoff Contract The first slice extends the existing support-request truth instead of creating a second support-ticket product model. | Handoff mode | Operator intent | External effect | Stored support-request truth | Default-visible result | |---|---|---|---|---| | `create_external_ticket` | Create a new external desk ticket from the current support context | One outbound create call through the configured external desk adapter | external ticket reference, optional external ticket URL, chosen mode, and cleared failure summary on success | success feedback shows the internal support reference plus the created external ticket reference | | `link_existing_ticket` | Record an external ticket that already exists outside the product | No outbound ticket-create call; the bounded adapter may normalize the provided reference or URL for the configured target | operator-supplied external ticket reference, optional external ticket URL, chosen mode, and no provider payload mirror | success feedback shows the internal support reference plus the linked external ticket reference | | `internal_only` | Keep the request internal-only when the operator intentionally defers external follow-through or when no target is configured for the application | No outbound call | no external ticket reference, chosen mode, and no external failure summary | the support context clearly states that no external ticket is linked yet | Additional rules for v1: - The internal `SR-...` support reference remains the canonical TenantPilot support-request identifier even when an external ticket exists. - V1 does not store external assignee, SLA, comments, status history, or raw provider payloads. - V1 does not auto-retry failed create calls. If a retry or relink path becomes necessary later, it requires a follow-up spec. ## Scope Boundaries ### In Scope - extend the existing `SupportRequest` truth and `SupportRequestSubmissionService` rather than introducing a new support domain model - offer one bounded external handoff mode selector inside the current tenant and run `Request support` actions - allow one support request to either create an external ticket, link an existing external ticket, or stay internal-only - call exactly one application-configured external support desk or PSA target when the operator chooses `create_external_ticket` - store the resulting external ticket reference and optional URL on the same support request record - store a bounded last handoff failure summary on the same support request when external create fails after the internal request exists - write explicit audit events for external ticket created, external ticket linked, and external handoff failed - show the latest external handoff summary for the current tenant or run support context without adding a broad support-product surface - keep current redacted support context attachment behavior from `SupportRequestContextBuilder` ### Non-Goals - re-specifying or replacing support-request creation from Spec 246 - creating a `SupportRequestResource`, support-request register, or support-request detail page - a generic ticketing or helpdesk framework with provider discovery or multiple adapters - bidirectional sync, external ticket status refresh, webhook ingestion, or comment sync - SLA, priority routing, assignment, support inbox, triage queue, or customer portal work - AI-generated support summaries or automation - background jobs or scheduled retries for external desk delivery - cross-workspace or cross-tenant linking shortcuts based on a known support reference or ticket reference alone ## Assumptions - Exactly one application-configured external support desk target can be resolved through a minimal config contract added in this slice. This spec does not introduce workspace settings UI, per-workspace target management, or a broader support-desk configuration product surface. - The existing `Capabilities::SUPPORT_REQUESTS_CREATE` capability is sufficient for v1. No new role family or support-only secondary capability is required. - The current redacted support context envelope produced by `SupportRequestContextBuilder` is already the canonical payload basis for external handoff. This feature does not redefine the support context contract. - Internal support-request creation remains allowed even when the external target is unavailable or an external create attempt fails, because the product must preserve the internal support truth and auditability. ## Risks - A synchronous external create call can slow the current support action if the provider-owned handoff service does not enforce the v1 five-second timeout budget and normalize timeout failures into the same bounded failure-summary path. - If a tenant or run has multiple support requests, the latest-handoff summary can mislead operators unless it also names the internal support reference it belongs to. - Provider-specific response fields can leak into the support-request truth if the adapter boundary is not enforced strictly. - The manual `link_existing_ticket` path could grow into a broader external-ticket management surface if it is allowed outside support-request submission. That growth is out of scope for v1. ## Follow-up Candidates - a second external support desk or PSA target only after a concrete second target exists and the first target proves real operator value - a bounded retry or relink flow from the same support contexts only if repeated external create failures become a proven operator pain point - a read-only support-request register only if current tenant or run context visibility is no longer sufficient - bidirectional sync or external ticket status refresh only if operators demonstrate a real need beyond stored reference continuity ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Create a new external ticket from the existing support flow (Priority: P1) As a support-capable operator, I want the existing support-request action to create an external desk ticket from the current tenant or run context so I do not have to recreate the same case manually outside TenantPilot. **Why this priority**: This is the direct commercialization gap named by the roadmap and candidate. Without outbound create, the product still stops at an internal support request only. **Independent Test**: Submit a support request from the tenant dashboard and from the operation-run viewer with `create_external_ticket`, fake the configured external desk target, and verify that the support request keeps the internal `SR-...` reference while also storing the returned external ticket reference and URL. **Acceptance Scenarios**: 1. **Given** an entitled operator opens the tenant dashboard and the application has one configured external desk target, **When** the operator submits the existing `Request support` action with `create_external_ticket`, **Then** the system creates the internal support request, creates one external ticket through the bounded adapter, stores the resulting external ticket reference on that same support request, and returns both references in success feedback. 2. **Given** an entitled operator opens an operation run that resolves to an entitled tenant, **When** the operator submits the existing `Request support` action with `create_external_ticket`, **Then** the system stores the internal support request with the run as primary context and persists the external ticket linkage on that same request. 3. **Given** the current context already has an earlier external handoff summary, **When** the operator opens the current `Request support` action again, **Then** the action shows the latest external linkage summary for that same context without turning the surface into a support-request history page. --- ### User Story 2 - Link an already-existing external ticket during support submission (Priority: P1) As a support-capable operator who already opened a desk ticket outside TenantPilot, I want to link that ticket during support-request submission so the product records the same external reference without creating a duplicate external case. **Why this priority**: The candidate explicitly requires create or link behavior, and linking an already-created external ticket is the smallest way to avoid duplicates without inventing a broader support-ticket management surface. **Independent Test**: Submit the existing tenant or run `Request support` action with `link_existing_ticket`, provide a ticket reference and optional URL, and verify that the support request stores that linkage truth without issuing an external create call. **Acceptance Scenarios**: 1. **Given** an entitled operator already has an external ticket reference, **When** the operator submits the existing tenant-context support action with `link_existing_ticket`, **Then** the system persists the provided external reference on the same support request and records an explicit audit event that the ticket was linked rather than created. 2. **Given** an entitled operator is on the operation-run support context, **When** the operator submits the action with `link_existing_ticket`, **Then** the system links the external reference to the run-scoped support request without creating a new external desk ticket. 3. **Given** the operator leaves the ticket reference blank or otherwise invalid for the bounded target format, **When** the action is submitted with `link_existing_ticket`, **Then** the system rejects the linkage input and does not create misleading external-ticket truth. --- ### User Story 3 - Keep failures explicit, scoped, and auditable (Priority: P2) As a support-capable operator, I want external handoff failures to be explicit without losing the internal support request so I can continue follow-through safely and without guessing what happened. **Why this priority**: The value of external handoff depends on failure honesty. Silent loss of the desk ticket or silent loss of the internal request would be worse than the current manual workflow. **Independent Test**: Force the external adapter to fail during `create_external_ticket`, then verify that the internal support request remains persisted, the current support context shows the latest failure summary for that same support reference, and audit truth records the failed handoff. **Acceptance Scenarios**: 1. **Given** the internal support request is created successfully but the external create call fails, **When** the action completes, **Then** the internal support request remains persisted, the operator receives explicit partial-success feedback with the internal support reference plus the handoff failure, and the failed handoff is audited. 2. **Given** a user is not entitled to the current workspace or tenant scope, **When** they attempt to access tenant or run external handoff state or submit the support action, **Then** the system returns `404` and reveals neither the internal support reference nor any external ticket reference. 3. **Given** a user is entitled to the tenant but lacks `Capabilities::SUPPORT_REQUESTS_CREATE`, **When** they attempt the same action, **Then** the system returns `403` and does not create, link, or reveal external handoff truth. ### Edge Cases - The application may not have an external desk target configured. In that case the existing support-request flow must remain available in `internal_only` mode with an explicit note that no external target is configured. - An external create call may fail after the internal support request is already committed. The request must remain the canonical support truth and must keep a bounded failure summary rather than disappearing or rolling back silently. - A tenant or run can have multiple support requests over time. The visible handoff summary on the current support context must clearly identify which internal support reference the shown external ticket reference belongs to. - An operator may know an external ticket reference but not a URL. The product may store the reference alone in v1 and must not invent a URL it cannot prove. - The operation-run viewer can only surface latest handoff state when the run resolves to an entitled tenant. Runs without an entitled tenant must continue to resolve as `404` without leaking any linkage hint. ## Requirements *(mandatory)* **Constitution alignment (required):** This feature adds a synchronous outbound create call to one configured external support desk target as part of an existing support-request mutation. It does not create a new `OperationRun`, queue, or scheduler. Successful internal request creation, external ticket creation, external ticket linking, and external handoff failure MUST all be auditable. **Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** The feature extends the existing `SupportRequest` truth instead of adding a second support-ticket model or queue. The only new semantic family is one bounded handoff mode family because operator choice and resulting mutation behavior differ materially between create, link, and internal-only paths. **Constitution alignment (XCUT-001):** Existing `Request support` actions, support-diagnostics context, `SupportRequestSubmissionService`, and `WorkspaceAuditLogger` must be reused. Any new external handoff behavior must plug into that shared path instead of creating separate tenant and run implementations. **Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** External handoff truth must stay secondary to the current tenant or run troubleshooting workflow. The current support contexts should show only the bounded latest linkage summary or failure, while diagnostics remain separately opened and raw provider details remain hidden. **Constitution alignment (PROV-001):** External desk payloads, authentication, and provider-specific identifiers remain provider-owned. The shared product truth remains the existing `SupportRequest` plus neutral external linkage fields only. **Constitution alignment (TEST-GOV-001):** Proof stays in unit plus feature lanes only. Browser and heavy-governance coverage are out of scope for the first slice. **Constitution alignment (RBAC-UX):** The affected authorization plane remains the tenant-admin `/admin` plane. Non-members and non-entitled users receive `404`. Entitled users lacking `Capabilities::SUPPORT_REQUESTS_CREATE` receive `403`. No raw capability strings or role-string checks may appear in feature code. **Constitution alignment (UI-FIL-001):** The feature must continue to use native Filament actions and action forms on the current pages. No custom standalone support desk page or local replacement shell is allowed. **Constitution alignment (UI-NAMING-001):** Primary operator-facing copy must preserve `Request support`, `Support reference`, and `External ticket`. Provider-specific product names, payload terminology, or API vocabulary must not replace those primary labels. ### Functional Requirements - **FR-256-001 Existing surfaces only**: The system MUST extend only the existing tenant dashboard and canonical operation-run `Request support` actions for v1. It MUST NOT introduce a new support-request resource, support-request detail view, or support queue page. - **FR-256-002 Bounded handoff mode choice**: When the application has a configured external desk target, the existing `Request support` action MUST let the operator choose exactly one of `create_external_ticket`, `link_existing_ticket`, or `internal_only`. When no target is configured, the action MUST remain available in `internal_only` mode and MUST explain that no external desk target is configured. - **FR-256-003 Internal request remains canonical**: Every path in this feature MUST create or preserve the existing internal `SupportRequest` truth first. The internal `SR-...` reference remains the canonical support-request identifier even when an external ticket is created or linked. - **FR-256-003A Bounded finalization exception to Spec 246 immutability**: Spec 256 explicitly narrows Spec 246 FR-246-011 in one bounded way: after internal request creation, the same `SupportRequest` 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 broader edit, reopen, merge, or status workflow is introduced. - **FR-256-004 External create path**: When the operator selects `create_external_ticket`, the system MUST call exactly one application-configured external support desk target through one bounded provider-owned adapter, apply a maximum five-second outbound timeout, store the returned external ticket reference on the same support request, and store the external ticket URL when the target returns one. - **FR-256-005 External link path**: When the operator selects `link_existing_ticket`, the system MUST store the provided external ticket reference on the same support request and MUST NOT issue an external ticket-create call for that request. - **FR-256-006 Persisted linkage truth**: The existing `support_requests` truth MUST be extended with only the neutral external linkage fields needed for operator continuity: external ticket reference, optional external ticket URL, selected handoff mode, and bounded last handoff failure summary. - **FR-256-007 No mirrored external lifecycle**: V1 MUST NOT persist or display external assignee, SLA, queue, comment stream, status history, or raw provider payloads. - **FR-256-008 Failure honesty**: If the external create path fails after the internal support request exists, the system MUST keep the internal request, persist a bounded last handoff failure summary on that same request, and show explicit feedback that the internal request succeeded but the external handoff failed. - **FR-256-009 Context-safe visibility**: The current tenant and run support contexts MUST show the latest external handoff summary for that same primary context, including the internal support reference it belongs to, without becoming a broad support-request history surface. - **FR-256-010 Audit coverage**: The system MUST write stable audit entries for support request created, external ticket created, external ticket linked, and external handoff failed, with workspace and tenant context plus the internal support reference and the external ticket reference when present. - **FR-256-011 Authorization boundaries**: Non-members and non-entitled actors MUST receive `404`. Members in scope who lack `Capabilities::SUPPORT_REQUESTS_CREATE` MUST receive `403`. Latest-handoff visibility, create, and link behavior MUST all enforce the same boundary. - **FR-256-012 Provider boundary**: Provider-specific authentication, request payload shape, response parsing, and URL normalization MUST remain inside one provider-owned adapter or service. Shared platform code MUST work only with the neutral external linkage truth stored on `SupportRequest`. - **FR-256-013 No background expansion**: V1 MUST NOT add background jobs, retry scheduling, webhook ingestion, or `OperationRun` usage for external desk delivery. - **FR-256-014 No cross-scope shortcuts**: A known internal support reference or external ticket reference MUST NOT be sufficient to reveal or mutate linkage truth outside the current entitled workspace and tenant scope. - **FR-256-015 Mutation-scope clarity**: The existing support actions MUST make it clear whether the current submission writes to `TenantPilot only` or to `TenantPilot + external support desk`, based on the selected handoff mode. - **FR-256-016 Timeout normalization**: When `create_external_ticket` exceeds the five-second outbound timeout budget or times out for any other target-level reason, the system MUST keep the internal support request, persist a bounded timeout-oriented failure summary on the same row, and route the outcome through the same explicit feedback and audit path as other external create failures. ## UI Action Matrix *(mandatory when Filament is changed)* | Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | |---|---|---|---|---|---|---|---|---|---|---| | Tenant dashboard support context | `App\Filament\Pages\TenantDashboard` | `Request support`, `Open support diagnostics` | `N/A` | `N/A` | `N/A` | `N/A` | `N/A` | one slide-over with `Submit support request` and a standard close action | yes | Existing dashboard action-surface exemption remains. The feature only extends the current `Request support` action with handoff mode choice and latest linkage summary. | | Operation-run support context | `App\Filament\Pages\Operations\TenantlessOperationRunViewer` | grouped `Open support diagnostics`, `Request support` | `N/A` | `N/A` | `N/A` | `N/A` | `N/A` | one slide-over with `Submit support request` and a standard close action | yes | No new run action group or support page. The feature only extends the existing support action with handoff mode choice and latest linkage summary. | ## Key Entities *(include if feature involves data)* - **Support Request**: Existing tenant-owned support truth with internal reference, primary context, redacted context envelope, severity, and the new bounded external linkage fields needed for external handoff continuity. - **External Support Desk Target**: The single application-configured external desk or PSA destination used for v1 handoff. It owns provider-specific authentication and payload semantics. - **External Ticket Linkage**: The bounded support-request extension that records whether the current request stayed internal-only, created an external ticket, or linked an existing one, together with the neutral external ticket reference, optional URL, and last failure summary. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: From the existing tenant dashboard or operation-run support context, an authorized operator can complete support-request submission with external create or link behavior in one flow without leaving the current page to recreate the case manually. - **SC-002**: 100% of successful external create or link submissions persist an external ticket reference on the same support request and make that reference visible again from the same entitled support context on revisit. - **SC-003**: 100% of external create failures leave the internal support request intact, produce explicit operator-visible failure feedback, and write an audit entry for the failed handoff. - **SC-004**: Authorization tests prove that operators never see or mutate external ticket linkage for a workspace or tenant they are not entitled to, even when they know an internal support reference or external ticket reference.