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
10 KiB
Research — External Support Desk / PSA Handoff
Date: 2026-04-29
Spec: spec.md
This document records the repo-grounded decisions that make the Spec 256 plan implementation-ready without expanding into a generic helpdesk product.
Decision 1 — Extend support_requests instead of adding a second support-ticket truth
Decision: Keep App\Models\SupportRequest as the only persisted truth for this slice and add the external linkage fields directly to support_requests.
Rationale:
- The repo already has one canonical support-request model, migration, factory, and submission service.
- The operator workflow needs one durable record that still carries the internal
SR-...reference after create, link, or failure. - Constitution
PERSIST-001andPROP-001reject a second lifecycle unless it solves a distinct product problem. Spec 256 does not need one.
Evidence:
- Existing model:
apps/platform/app/Models/SupportRequest.php - Existing persistence:
apps/platform/database/migrations/2026_04_27_095518_create_support_requests_table.php - Existing write path:
apps/platform/app/Support/SupportRequests/SupportRequestSubmissionService.php - Candidate scope:
docs/product/spec-candidates.md
Alternatives considered:
- Add a new
SupportTicketorSupportRequestLinkmodel.- Rejected: creates a second truth and encourages a support register or detail page the spec explicitly forbids.
- Keep external linkage entirely derived from audit logs.
- Rejected: the current support context must show the latest linkage on revisit, which audit-only storage cannot do safely or cheaply.
Decision 2 — Persist a bounded failure summary on the same row; keep detailed provider failure out of product truth
Decision: Store external_handoff_failure_summary on support_requests and keep detailed provider payloads or raw errors out of persisted support-request truth.
Rationale:
- The spec requires explicit, revisitable failure handling in the same support context.
- A purely audited failure would satisfy compliance but fail the operator need to reopen the action and see what happened.
- A bounded human-readable summary is enough for revisit. Provider-specific payloads remain provider-owned and redaction-sensitive.
Evidence:
- Existing audit path is already separate from product truth:
apps/platform/app/Services/Audit/WorkspaceAuditLogger.php - Current support-request row has no external linkage or failure fields, so the revisit contract is impossible without row-level extension.
Alternatives considered:
- Audit failure only.
- Rejected: failure becomes invisible in the current support context.
- Persist raw provider response JSON.
- Rejected: violates the spec’s minimal neutral truth and increases leakage risk.
Decision 3 — Keep the flow synchronous, preserve internal durability, and document the one bounded finalization write
Decision: Preserve the existing synchronous submit flow, move any external create call outside the current internal-request creation transaction, enforce a five-second outbound timeout, and explicitly allow one bounded post-create finalization write on the same SupportRequest row.
Rationale:
- The current service wraps internal create plus audit in a transaction.
- Spec 256 explicitly requires the internal support request to survive external create failure.
- Spec 246 declared the row immutable after creation, so Spec 256 must make its one bounded finalization exception explicit instead of mutating the row silently.
- Holding a database transaction open across remote HTTP is unnecessary and increases latency and failure risk.
- A hard timeout budget is needed so the operator-visible submit path stays bounded and timeout behavior is testable.
- The repo truth does not require
OperationRun, queueing, or retry scheduling for this slice.
Evidence:
- Existing transaction structure in
SupportRequestSubmissionService - Existing synchronous page actions on
TenantDashboardandTenantlessOperationRunViewer - The spec’s explicit non-goal for queues, retries, and
OperationRun - Spec 246 FR-246-011 immutability contract
Alternatives considered:
- Perform external HTTP inside the current DB transaction.
- Rejected: risks long transactions and makes internal request durability harder to guarantee.
- Introduce queue work or
OperationRun.- Rejected: broader than current-release truth and not required for one synchronous target.
- Keep Spec 246 immutability unchanged and infer final handoff state only from audit.
- Rejected: the current support context must show revisitable success or failure on the canonical
SupportRequest, so the one bounded finalization write has to be explicit.
- Rejected: the current support context must show revisitable success or failure on the canonical
Decision 4 — Keep external linkage visibility inside the existing support request actions only
Decision: Show the latest linkage summary inside the existing Request support slide-overs and in submit feedback. Do not add a new support page, dashboard card, or run-detail history section.
Rationale:
- The spec says visibility must stay attached to the existing tenant and run support contexts.
- The acceptance criteria require reopening the action and seeing the latest linkage summary for that same context.
- A broader always-visible history surface would deepen support-product scope and duplicate truth.
Evidence:
- Existing support-aware surfaces:
apps/platform/app/Filament/Pages/TenantDashboard.phpandapps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php - No existing
SupportRequestresource, list, or detail page exists in the repo today.
Alternatives considered:
- Add a support-request resource or detail page.
- Rejected: explicitly out of scope.
- Add a new page-level widget or card for support linkage.
- Rejected: broader than the acceptance requirement and would create duplicate visible truth.
Decision 5 — Add one minimal application config contract; do not hide target resolution behind an undefined prerequisite
Decision: Bring one minimal application config contract into scope through apps/platform/config/support_desk.php and environment-backed values for the single supported target. Do not add workspace settings UI, a support settings domain, or provider-connection product work.
Rationale:
- The repo has no existing
supportsettings domain, so leaving target resolution as an external prerequisite would create hidden implementation work. - The product contract only needs one target for v1, so an application config contract is the narrowest explicit source of truth.
- Pulling workspace administration into Spec 256 would still expand scope from handoff to setup and administration.
Evidence:
- Existing repo truth has no support-target config seam yet, so a new app config file is the explicit minimal source of truth for one target.
Alternatives considered:
- Add a new
supportsettings domain and UI in the same spec.- Rejected: becomes a second feature slice.
- Reuse
ProviderConnectionas the support target model.- Rejected: not justified by current repo truth for one external desk handoff target.
- Leave target resolution as an undefined prerequisite.
- Rejected: the tasks and plan already depend on a concrete resolution seam, so the config contract must be explicit inside the package.
Decision 6 — Use one concrete provider-owned handoff service, not a registry or interface framework
Decision: Add one concrete provider-owned handoff service under the support-request path for the single real external target.
Rationale:
- Both existing support surfaces need the same create-or-normalize behavior.
- Constitution
ABSTR-001rejects a provider registry or interface framework before two real targets exist. - Page-local HTTP logic would duplicate failure handling, normalization, and audit shape.
Evidence:
- One configured target only in the spec and roadmap candidate
- Existing shared write path already centralizes support-request submission across both surfaces
Alternatives considered:
- Add a provider interface plus registry.
- Rejected: future-proofing without current-release variance.
- Duplicate HTTP logic inside both Filament pages.
- Rejected: immediate drift risk and weaker audit consistency.
Decision 7 — Keep queries context-scoped and avoid new search or indexing semantics
Decision: Derive the latest visible linkage from the latest support request for the same primary context, using the existing context indexes. Do not add cross-scope lookup or search by external ticket reference.
Rationale:
- Tenant summary and run summary have different scope rules in the spec.
- Existing indexes already support latest-by-tenant and latest-by-run queries.
- Cross-scope lookup by external reference is explicitly out of scope and would create a new leakage risk.
Evidence:
- Existing indexes on
support_requests(tenant_id, created_at)andsupport_requests(operation_run_id, created_at) - Context scoping in
SupportRequest::PRIMARY_CONTEXT_TENANTandSupportRequest::PRIMARY_CONTEXT_OPERATION_RUN
Alternatives considered:
- Add an index and lookup flow for external ticket reference.
- Rejected: no current surface needs it, and it would conflict with the no-cross-scope-shortcuts rule.
Decision 8 — Proof stays in Unit + Feature lanes with manual smoke only
Decision: Keep the proving strategy in focused Pest unit and feature suites, then use a narrow manual smoke path after implementation.
Rationale:
- Business truth is server-side: branching, persistence, audit, and authorization.
- Existing support-request tests already cover the same two Filament entry surfaces.
- Browser coverage would mostly duplicate the existing action-form semantics.
Evidence:
- Existing test family:
apps/platform/tests/Feature/SupportRequests/TenantSupportRequestActionTest.phpapps/platform/tests/Feature/SupportRequests/OperationRunSupportRequestActionTest.phpapps/platform/tests/Feature/SupportRequests/SupportRequestAuthorizationTest.phpapps/platform/tests/Feature/SupportRequests/SupportRequestAuditTest.php
Alternatives considered:
- Add browser tests in the first slice.
- Rejected: not required to prove the current business truth.