Skip to main content
Petanque Life

Support Workbench

F21.10 12 features

At a glance

The full support workbench: a unified ticket inbox from email and in-app, a rich per-ticket workspace with reporter context and canned suggestions, scrubbed rrweb session replay, log deep-links, ticket-to-audit linkage, a curated known-issues library with fuzzy match-engine, per-user/tenant support notes, operator-quality metrics with peer-review, and HMAC-signed CSAT surveys.

How it works

Tickets land in `support_tickets` from three sources: SendGrid Inbound Parse for `support@petanque.life`, an in-app feedback adapter, and a Slack-forwarded webhook. All adapters require a shared-secret header and dedupe on `external_ref`. The inbox filters by status, priority, assignee, and source; `sys_finance` is denied entry.

Opening a ticket reveals the workbench: ticket body, reporter context (email/name/tenant plus last 20 audit rows covering direct and impersonation trails), tag-and-title-scored canned suggestions, and a thread timeline that interleaves inbound mail, replies, canned applies, notes, escalations, status changes, and assignments. Canned responses are `SysCannedResponse` records with declared `{name, default}` variables and `{{token}}` substitution; saving with an undeclared token is rejected and a live preview endpoint drives the editor. Escalation has two flavours: engineering tags `escalated:engineering` and bumps to `high`; incident creates a linked `SysIncident` (SEV3 default), bumps to `urgent`, tags `escalated:incident`.

Session replay is opt-in per tenant; the rrweb ingest scrubs email, phone, IBAN, credit card, Swedish personnummer, and generic national-ID before blob storage; retrieval returns a 15-minute signed URL with 30-day retention and 410 once expired. Log search returns App Insights/Loki deep-links plus an inline `ApiUsageEvent` tail. Every reply, canned, or escalation sends `x-sys-ticket-id`, so the impersonation tagging middleware ties collateral reads to the ticket — the audit log can be filtered by ticket.

The known-issues library curates `SysKnownIssue` rows (symptom keywords, affected features, resolution steps, suggested canned); a fuzzy match-engine scores inbound tickets and pushes the top three into the sidebar via SSE. Per-user and per-tenant notes live in `support_notes` with category, pin, and optional expiry. Operator-quality metrics aggregate per-operator 7- and 30-day windows over resolved/assigned, response and resolution times, impersonation sessions, canned-applied vs replies-sent, CSAT average, and review pass/fail; cross-operator queries require `sys_engineer` or `sys_security`.

Peer review samples 2 percent of each operator's audit rows weekly plus CSAT-low auto-flags; verdicts are pass/fail with notes, self-verdicts blocked, reviewer identities blinded after 90 days. CSAT is a one-shot HMAC-signed token (14-day TTL) opened at `www/support/csat/{token}`; results feed the operator-quality views.

Key capabilities

  • Unified inbox from email, in-app, Slack with idempotent `external_ref` dedup
  • Workbench: reporter context, canned suggestions, full thread timeline
  • Canned-response library with declared variables, undeclared-token rejection, live preview
  • Escalate to engineering or incident with linked `SysIncident` and tagging
  • Opt-in rrweb session replay with PII scrubbing and 15-minute signed URLs
  • Log search with App Insights/Loki deep-links and inline `ApiUsageEvent` tail
  • `x-sys-ticket-id` propagation links every collateral read to the ticket
  • Known-issues library + fuzzy match-engine pushing top-3 matches via SSE
  • Per-user and per-tenant support notes with categories, pin, expiry
  • Operator-quality metrics, peer-review with reviewer-blinding after 90 days
  • HMAC-signed CSAT (14-day TTL) feeding operator-quality dashboards

In practice

A ticket arrives from a club admin: `licenses page is empty`. The match-engine pushes three known-issue suggestions; the top one is `cache stale after club merge — apply canned `merge-cache-flush`. The operator clicks the suggestion, applies the canned with the player's name auto-filled, and runs the fix-up job from F21.08.

He pins a support note on the tenant: `merged 2026-04-26, watch for 7 days`. The ticket resolves; CSAT enqueues, the player rates 5/5 the next day, and the operator's metric dashboard updates. A week later a peer-review sample lands in the queue; another operator marks it pass with notes, and after 90 days the reviewer's identity is blinded.

Features in this subsystem

12
ID Status Features
F21.10.01 Shipped Ticket inbox — unified view of support tickets (email ingest from support@petanque.life, in-app feedback, Slack-forwarded). SendGrid Inbound Parse + Slack adapters behind shared-secret header; idempotent on external_ref. Filters by status/priority/assignee/source, sys_finance denied. ✅ PL-T130
F21.10.02 Shipped Per-ticket workbench — ticket body, reporter context (email/name/tenant + last 20 audit rows covering direct target + impersonation trails), canned suggestions scored by tag + title overlap, thread timeline (inbound/reply/canned/note/escalation/status_change/assignment). Implemented (PL-T130)
F21.10.03 Shipped Canned responses — SysCannedResponse library with declared {name, default} variables and {{token}} substitution. Undeclared tokens rejected at save time (SysCannedResponseUndeclaredVariables). Live POST /preview drives the workbench preview panel. Implemented (PL-T130)
F21.10.04 Shipped Escalate ticket — engineering bumps priority to high and tags escalated:engineering; incident creates a linked SysIncident (default SEV3), bumps to urgent, tags escalated:incident. Thread carries an escalation entry with metadata. Implemented (PL-T130)
F21.10.05 Shipped Session replay — opt-in per tenant via SysTenantFeatureOverride.session_replay_enabled. rrweb ingest POST /_replay/ingest (shared-secret header) passes through scrub_events for email/phone/IBAN/credit-card/Swedish-personnummer/generic national-ID redaction before blob storage. Retrieval returns signed URL (15 min TTL), 30-day retention, 403 when not opted in, 410 on expiry. ✅ PL-T130
F21.10.06 Shipped Log search from ticket — GET /sys/tickets/{id}/logs returns App Insights / Loki deep-link URLs plus an inline_tail read of ApiUsageEvent for the reporter + tenant window. ✅ PL-T130
F21.10.07 Shipped Ticket-to-audit-log link — every mutation writes metadata.ticket_id, and the sys console sends x-sys-ticket-id on reply / canned-apply / escalate so the impersonation tagging middleware attaches the same tag to collateral reads. Implemented (PL-T130)
F21.10.08 Shipped Known-issues library + match-engine — curated SysKnownIssue entries (status draft/active/resolved/archived, symptom keywords, affected features, resolution steps, optional suggested canned response). Tenant-free library maintained by sys_engineer/sys_security; readable by all four sys roles. Fuzzy match-engine scores inbound tickets (keywords ×3, title ×5, description ×2, features ×1) and pushes the top 3 into the workbench sidebar via SSE sys.tickets → ticket.known_issue_matches. One-click "apply suggested canned response" bridges into F21.10.03. ✅ PL-T148
F21.10.09 Shipped Per-user + per-tenant support-notes — SupportNote Beanie-Document (collection support_notes, mutually-exclusive user_id / tenant_id, author_email, body_md ≤ 1 000, category ∈ {preference, vip, compliance, watch, context}, pinned, optional expires_at). CRUD via GET POST /sys/users/{id}/notes + PUT|DELETE /sys/users/{id}/notes/{note_id} and the mirrored sys/tenants/{id}/notes surface (all four sys roles, fresh-auth on mutation). NotesSidebar renders on user-detail + tenant-detail with pinned-first ordering and category chips. support-note-expiry-sweep cron prunes rows past expires_at daily; notes never surface to the target user except via a GDPR subject-access snapshot (F21.13.10). | ✅ PL-T149
F21.10.10 Shipped Operator-quality metrics — GET /sys/quality/metrics returns per-operator 7 d + 30 d windows over tickets resolved/assigned, first-response + resolution time, impersonation sessions + duration, canned-applied / replies-sent, CSAT average, review pass/fail counts. GET /sys/quality/metrics/leaderboard ranks operators on a chosen metric. Self-query always allowed; cross-operator query requires sys_engineer / sys_security, otherwise 403 SysQualityCrossOperatorForbidden. ✅ PL-T149
F21.10.11 Shipped Peer-review queue — SysOperatorAuditReview rows produced by the weekly operator-audit-sampling job (2 % of each operator's SysAuditEntry rows) plus CSAT-low auto-flags. GET /sys/quality/reviews lists pending items (sys_support, sys_security); POST /sys/quality/reviews/{id}/verdict records pass/fail + notes, blocks self-verdicts and duplicates. Nightly operator-review-blind job clears reviewer_email + sets reviewer_blinded=true after 90 d to protect reviewers from permanent peer-bias. ✅ PL-T149
F21.10.12 Shipped CSAT — resolution triggers a one-shot HMAC-signed token (14 d TTL) through csat-invite-enqueue, SendGrid email, and SysCsatInvite fingerprint record. Reporter opens www/support/csat/{token} which calls GET POST /public/support/csat/{token} (10 hits/60 s per IP, single-use). Rating ≤ 2 auto-flags the resolving operator through a SysOperatorAuditReview with flag_reason="csat_low". GET /sys/quality/csat renders the dashboard: every sys-operator sees their own aggregate; sys_security additionally sees the per-operator breakdown (self_only=false). | ✅ PL-T149

Stakeholders who need this subsystem

Surfaces in 1 stakeholder analyses