Hoppa till huvudinnehåll
Petanque Life
← Tillbaka till alla funktioner
21

System Console

195 funktioner · 24 delsystem

The internal console used by the Petanque Life team (codename: `sys`) to operate the platform. Hosted separately from the tenant-facing admin so that operational tools, cross-tenant access, impersonation, and incident response never leak into a federation's own admin surface.

Core architecture contract

F21.00

*Bootstrap klart (PL-T120): `sys/`-scaffold, SWA `swa-petanque-sys` + custom domain, `deploy-sys.yml` med design-drift-gate, OAuth-gateway (Google; Microsoft stub), `sys_audit_entries` + `sys_sessions` collections, `@sys_audit`-dekorator, sys-JWT ES512 med namespace-isolation, första visual-regression-baselines.*

Så fungerar det

Sys Authentication & Access Control

F21.01

**Endpoints:**

Så fungerar det
  • F21.01.01 Levererad

    Microsoft OAuth or Google OAuth — no password, no OTP. Each provider is verified against its own JWKS; the token's email claim must match the allowlist (F21.01.02) regardless of provider. Work-Google accounts and Petanque Life Azure AD accounts both accepted so operators can pick the provider their device already has.

    ✅ PL-T121
  • F21.01.02 Levererad

    Email allowlist (SYS_ADMIN_EMAILS setting) — even with a valid Microsoft or Google token, off-allowlist emails are rejected at auth time. The allowlist entry may optionally pin the allowed provider per email (alice@example.com:google) when an operator must use a specific identity.

    Implemented (PL-T120)
  • F21.01.03 Levererad

    Mandatory WebAuthn/passkey as second factor — OAuth mints only a 2-min pending JWT; the full sys-JWT is only issued after a verified WebAuthn assertion. First passkey onboarded via /webauthn/register-onboard/*.

    Implemented (PL-T121)
  • F21.01.04 Levererad

    Four sys sub-roles: sys_support, sys_engineer, sys_finance, sys_security. A user may hold several. Capability matrix enforced server-side via existing ACC-003 deny-by-default framework. Self-grant blocked.

    Implemented (PL-T121)
  • F21.01.05 Levererad

    Session TTL 60 min sliding. Activity renews up to a hard ceiling of 8 h per login. hard_exp is immutable across reauth.

    Implemented (PL-T121)
  • F21.01.06 Levererad

    Fresh-auth gate (@requires_fresh_auth(300)) — returns 401 fresh_auth_required with a reauth_url. Frontend FreshAuthGate resolves transparently via WebAuthn. Gated endpoints: role changes, force-logout. Impersonation / refund / secret-rotation land in F21.02 / F21.07 / F21.17.

    Implemented (PL-T121)
  • F21.01.07 Levererad

    Force-logout any sys session from the Operator sessions panel (sys_security only). Reason is required and audited.

    Implemented (PL-T121)
  • F21.01.08 Levererad

    Break-glass account procedure documented — sealed envelope with YubiKey + runbook. Endpoint shell present (/auth/sys/break-glass returns 501 today). Full flow ships with PL-T138.

    ✅ PL-T122
  • F21.01.09 Levererad

    API-token-vy i /settings/api-token — operatören kan visa, kopiera och få curl/httpie/python-snippets för sin egen sys-JWT. REVEAL-prompt + 30 s auto-mask + audit-event sys.token.viewed. Inga nya tokens utfärdas — samma JWT som redan finns i AsyncStorage.

    ✅ PL-T317

Impersonation ("Login as")

F21.02

The feature that defined the split between admin and sys: the ability for a sys operator to log in **as another user** to reproduce bugs, assist with support, or verify that a feature works for that user's role.

Så fungerar det
  • F21.02.01 Levererad

    Start impersonation bound to a SupportAccessGrant. Contract: { grant_id, access_level_requested }. Requires fresh-auth + sys_support / sys_engineer; access_level_requested ≤ grant.access_level; operator == grant.requested_by.

    ✅ PL-T122 + PL-T142
  • F21.02.02 Levererad

    Impersonation JWT carries both actor_id (sys operator) and sub (target user), plus impersonation_id UUID, grant_id, and chosen access_level linking to the audit trail. All downstream capability checks use sub; audit uses actor_id.

    ✅ PL-T122 + PL-T142
  • F21.02.03 Levererad

    Persistent banner across the entire screen — red background, fixed top, "Impersonating {name} ({email}) — {reason} — Exit". Non-dismissable. Break-glass banner shows a dedicated badge + shorter countdown.

    ✅ PL-T122 + PL-T142
  • F21.02.04 Levererad

    Max impersonation duration: 30 min (consent-gated, capped further by grant.expires_at); 15 min hard cap for break-glass. Auto-exit with banner countdown last 5 min.

    ✅ PL-T122 + PL-T142
  • F21.02.05 Levererad

    Write guardrails (unchanged security invariant): password, MFA, email, recovery-code, self-role-grant, payment approval, legal-accept, account deletion — 403 impersonation_write_blocked at every access level. PL-T142 adds: access_level == "view" rejects all non-GET with 403 grant_view_only; Redis revocation flag yields 401 grant_revoked within 2 s.

    ✅ PL-T122 + PL-T142
  • F21.02.06 Levererad

    Read-full fidelity: impersonator sees EXACTLY what the target sees — same tenant filtering, same sensitive-field masking, same feature flags. No "elevated" view.

    Implemented (PL-T122)
  • F21.02.07 Levererad

    Exit impersonation one-click — banner button or ESC twice. Returns to sys session, captures end timestamp in audit; grant.status → used on first exit.

    ✅ PL-T122 + PL-T142
  • F21.02.08 Levererad

    Target user is notified post-hoc by email; consent-gated sessions honour the user's per-channel opt-out. Break-glass sessions force dispatch regardless of opt-out (PL-T142).

    ✅ PL-T122 + PL-T142
  • F21.02.09 Levererad

    Target user can view their own impersonation history: GET /me/impersonation-history — list of times they were impersonated, by whom, with reason.

    ✅ PL-T122
  • F21.02.10 Levererad

    Concurrent impersonation limit: 1 per sys operator, 1 per target user, and 1 per grant (PL-T142). Second attempts surface 409 impersonation_already_active / 409 target_already_impersonated / 409 grant_already_in_use.

    ✅ PL-T122 + PL-T142
  • F21.02.11 Levererad

    "Login as self in role X" sibling flow — spawn a disposable test session in sandbox tenant with selected role (player/referee/club_admin/federation_admin). Used for feature verification without touching real user data.

    ✅ PL-T122b

Test-User Factory

F21.03

Tool for spawning disposable users in a dedicated `tenant=sandbox-sys` so sys operators can validate features end-to-end without polluting real tenants.

Så fungerar det
  • F21.03.01 Levererad

    Create sandbox user with chosen role, language, nationality, license state (none/active/expired), club affiliation, 2FA state.

    ✅ PL-T123
  • F21.03.02 Levererad

    Preset scenarios — "new player, just registered", "club admin with pending approvals", "federation admin with open incidents", "retired player", "suspended license". One-click spawn.

    Implemented
  • F21.03.03 Levererad

    Nuke sandbox — delete all sandbox users + data. Throttled to once per hour, audit-logged.

    Implemented
  • F21.03.04 Levererad

    Sandbox tenant is visually marked in admin and app: amber header bar "SANDBOX — not real data".

    Implemented
  • F21.03.05 Levererad

    Sandbox emails routed to an internal catch-all inbox instead of real recipients.

    Implemented

Sys Dashboard

F21.04

Landing page. Health of the whole platform at a glance.

Så fungerar det
  • F21.04.01 Levererad

    KPI tiles: active tenants, DAU/MAU, MRR, 30-day uptime %, open incidents, queued jobs. Each tile is clickable and routes to the detail view. Sparkline where useful (DAU/MAU carries [DAU, MAU]).

    ✅ PL-T124
  • F21.04.02 Levererad

    Realtime activity feed — 100-event backfill from sys_audit_entries + live pushes via in-process ActivityBroker, served as SSE with 15-s heartbeat. Event schema: {id, action, actor_id, target_type, target_id, tenant_id, timestamp, metadata}.

    Implemented (PL-T124)
  • F21.04.03 Levererad

    "Attention needed" panel — four aggregated rows (open support tickets, unmatched payments, failed jobs 24 h, stale incidents > 24 h) with severity + resolve_href. Aggregations are fail-safe: missing collections return 0.

    Implemented (PL-T124)
  • F21.04.04 Levererad

    Top-5 lists — most active tenants (7 d DAU), highest revenue (MRR), most 5xx errors (24 h), longest job runtimes (24 h). Empty lists render the card in a "no data yet" state so the grid stays stable.

    Implemented (PL-T124)
  • F21.04.05 Levererad

    Personal shortcut pane (0–8 items) backed by SysUserPreferences.dashboard_shortcuts. Full-replace PUT semantics; hrefs must start with / (internal sys routes only) or the API returns 400 SysShortcutInvalidHref. ↑/↓ reorder in-place (RN-compatible alternative to HTML5 DnD).

    Implemented (PL-T124)
  • F21.04.06 Levererad

    System-map — tenant × service health matrix with live green/yellow/red cell colouring (incidents, 5xx rate, service status, latency vs SLA). Zoom-out mode collapses tenants to a single platform-health row. Cells drill into tenant, firehose (pre-filtered), or open incident.

    ✅ PL-T146
  • F21.04.07 Levererad

    Platform-event firehose — SSE stream over PlatformEvent (time-series, 30 d TTL). Filters by service/severity/tenant/search. Pause/resume, pin-for-later (cap 20), bounded in-process broker with drop-on-overflow. Default-paused on mount.

    ✅ PL-T146
  • F21.04.08 Levererad

    Correlation engine — rolling 60 s window per service; > 10 error/critical events emits an incident_suggest frame into the firehose with a one-click "Create incident" button that pre-populates the wizard.

    ✅ PL-T146

Tenant Management

F21.05

Cross-tenant CRUD and configuration. Replaces scattered Cosmos queries.

Så fungerar det
  • F21.05.01 Levererad

    Tenant directory — all tenants with type (federation/club/pilot/sandbox), plan, status, created date, primary contact. Filter + sort + export. Defensive iterators (_safe_iter/_safe_get/_safe_find_one) keep the list rendering even when a single legacy document fails pydantic validation (PL-T316).

    ✅ PL-T125, ✅ PL-T316
  • F21.05.02 Levererad

    Tenant detail view — config, billing profile, feature flags, OrgNode hierarchy, user count, revenue summary, recent activity. Friendly <ErrorPanel> (copy + retry) replaces the bare red banner when the API returns 5xx (PL-T316).

    ✅ PL-T125, ✅ PL-T316
  • F21.05.03 Levererad

    Create tenant — guided wizard: slug, country, hierarchy depth, initial admin email, plan, trial length. Emits seed_system_roles + sends onboarding email. Plan-tier picker is fed by GET /sys/tenants/plan-tiers and filters SKU options to the chosen tenant_type (PL-T315).

    ✅ PL-T125, ✅ PL-T315
  • F21.05.04 Levererad

    Feature-flag panel per tenant — on/off + percentage rollout. Mirrors GrowthBook but overrideable by sys operators for targeted testing.

    ✅ PL-T125
  • F21.05.05 Levererad

    "Visit tenant" — one-click impersonation as a sys operator within that tenant's scope (read-only unless explicitly elevated).

    ✅ PL-T125
  • F21.05.06 Levererad

    Tenant archive — disable writes, keep reads for 90 d, then schedule hard delete. Reversible within the 90 d window.

    ✅ PL-T125
  • F21.05.07 Levererad

    Tenant export — produces a zip with all tenant data (documents, uploads, audit log) for GDPR/contract exit.

    ✅ PL-T125

User Directory (cross-tenant)

F21.06
Så fungerar det
  • F21.06.01 Levererad

    Search all users across all tenants by email, name, license number, phone, user id. Results include tenant context + role.

    ✅ PL-T126
  • F21.06.02 Levererad

    User detail view — profile, tenant memberships, sessions, devices, audit timeline, support history, open tickets, linked OAuth identities. UI: sys/app/(dashboard)/user-directory/[id]/index.tsx. API: GET /sys/user-directory/{user_id}.

    Implemented (PL-T126)
  • F21.06.03 Levererad

    Account recovery — reset password, reset MFA, reset WebAuthn, resend verification email. Each requires reason + fresh-auth. UI: user-directory/[id]/recovery.tsx. API: POST /sys/user-directory/{user_id}/{reset-password,reset-mfa,reset-webauthn,resend-verification}.

    Implemented (PL-T126)
  • F21.06.04 Levererad

    Lock/unlock account — temporary block without delete. Shown to user at login as "contact support". API: POST /sys/user-directory/{user_id}/{lock,unlock}.

    Implemented (PL-T126)
  • F21.06.05 Levererad

    Soft-delete — marks user disabled + anonymized PII. Recoverable 30 d. Hard delete after grace period. API: POST /sys/user-directory/{user_id}/{soft-delete,restore}.

    Implemented (PL-T126)
  • F21.06.06 Levererad

    Impersonate from detail view — inherits the reason prompt + audit from F21.02. Grant-flow-knapp i user-directory/[id]/index.tsx triggar /support-access/grants + F21.02-flödet.

    Implemented (PL-T126 + PL-T122)
  • F21.06.07 Levererad

    Session panel — list active sessions for a user, per-device. Revoke individually or all-but-current. UI: user-directory/[id]/sessions.tsx. API: GET /sys/user-directory/{user_id}/sessions, DELETE /sys/user-directory/{user_id}/sessions[/{session_id}].

    Implemented (PL-T126)
  • F21.06.08 Levererad

    Merge duplicate users — consolidate two accounts (same person across tenants) into a single identity with linked tenant profiles. Audit-trail of the merge. UI: user-directory/merge.tsx. API: POST /sys/user-directory/merge/preview, POST /sys/user-directory/merge/{operation_id}/{confirm,cancel}.

    Implemented (PL-T126)
  • F21.06.09 Levererad

    Effective-state inspector — GET /sys/users/{id}/effective-state?tenant_id=… aggregates identity + sessions + tenant context + capabilities + feature flags + experiment assignments + pending ops + open support-grants + open tickets + billing + anomalies in a single 60 s-cached payload. Sys-UI sys/app/(dashboard)/user-directory/[id]/effective-state.tsx renders a three-column layout with a red-bordered anomaly banner and "Copy as text / JSON" for ticket replies. sys_support / sys_security; every read audited as sys.user.effective-state.view. Anomaly rule engine detects failed-login spikes (≥3/24 h), logins from new IPs (30 d lookback), long inactivity (≥90 d), and locked-with-active-session.

    ✅ PL-T145
  • F21.06.10 Levererad

    Per-user timeline — GET /sys/users/{id}/timeline?from&to&kinds&limit&offset aggregates a kronologisk stream from nine source collections (user_sessions, sys_failed_login_samples, sys_audit_entries, support_access_grants, impersonation_sessions, invoices, notifications, sys_incidents, legal_document_acceptances) — no new event-of-record collection. Sys-UI sys/app/(dashboard)/user-directory/[id]/timeline.tsx with filter chips per kind, jump-to-date picker, and row-level drill-in to audit / incident / invoice / grant detail. Paginated (TIMELINE_MAX_PAGE=500, default 100). sys.user.timeline.view audited.

    ✅ PL-T145

Billing & Revenue

F21.07

Financial oversight across all tenants. Complements the tenant-facing billing views (PL-T096).

Så fungerar det
  • F21.07.01 Levererad

    Revenue dashboard — MRR/ARR, net revenue, gross revenue, platform fees, per plan / country / tenant. Rolling 12-month trend, 5-minute cache.

    ✅ PL-T127
  • F21.07.02 Levererad

    Cross-tenant invoice list with status, amount, due date, dunning stage. Filter + export to CSV and PDF.

    Implemented (PL-T127)
  • F21.07.03 Levererad

    Payments list — all payments across tenants. Detail shows method, provider reference, linked invoice, refund timestamps.

    Implemented (PL-T127)
  • F21.07.04 Levererad

    Unmatched payments inbox — GET /sys/payments/unmatched returns rows from UnmatchedBankTransfer decorated with up to 5 fuzzy invoice suggestions (OCR/amount/tenant/due-window/counterparty). POST /sys/payments/unmatched/{id}/assign posts the matching Payment + AccountingEntry and recomputes invoice status; /ignore parks the row. Both mutations require sys_finance + fresh-auth + reason.

    ✅ PL-T127b
  • F21.07.05 Levererad

    Refund workflow — two-step POST /sys/payments/{id}/refund/preview then POST /sys/payments/{id}/refund. Idempotent on (payment_id, amount_cents, actor) (replays return original refund). Orchestrates gateway call → counter AccountingEntry → invoice status transition (partially_refunded / refunded) → optional notify_tenant job. sys_finance + fresh-auth + reason.

    Implemented (PL-T127b)
  • F21.07.06 Levererad

    Subscription management — GET /sys/subscriptions cross-tenant list + detail with plan-history timeline; POST /sys/subscriptions/{id}/{change-plan,pause,cancel,extend-trial} for tier/amount/proration, until-date pause, now/period_end cancel, ≤30-day trial extension. Plan history persisted as audit-derived timeline + per-subscription plan_history log. All mutations sys_finance + fresh-auth + reason.

    Implemented (PL-T127b)
  • F21.07.07 Levererad

    Churn report — GET /sys/billing/churn?from&to aggregates SubscriptionCancellation + legacy Subscription(status=CANCELLED) rows in a window, bucketed by reason / plan / country. Revenue impact locked at cancellation via Subscription.amount_eur_cents. sys_finance.

    ✅ PL-T127c
  • F21.07.08 Levererad

    Dunning override — GET /sys/tenants/{id}/dunning + POST .../dunning/override + POST .../dunning/resume. Persists override_path (freeze / custom) + override_custom_stages directly on DunningCase; worker reads this as its single source of truth. sys_finance + fresh-auth + reason.

    Implemented (PL-T127c)
  • F21.07.09 Levererad

    Platform-fee report — GET /sys/billing/platform-fees?from&to&group_by=tenant\

    country sums Invoice(issuer_type=platform) into gross/paid/outstanding totals per group + invoice rows. GET /sys/billing/platform-fees.csv exports the same payload. sys_finance. | Implemented (PL-T127c)
  • F21.07.10 Levererad

    Reconciliation view — GET /sys/billing/reconciliation?provider=stripe\

    bankgirot&from&to compares provider-ledger totals (Stripe Balance / Bankgirot settlement) to internal AccountingEntry sums, flags drift above tolerance (100 cents). 1 h in-process cache, 503 when provider credentials are missing. sys_finance. | Implemented (PL-T127c)

Operations

F21.08

Day-to-day operational tools.

Så fungerar det
  • F21.08.01 Levererad

    Job inventory — every batch job registered in craft-easy-jobs is listed with its schedule (cron or on-demand), owner, description, last run, last status, next scheduled run. Cross-tenant scope. Builds on the PL-T096 admin job view; sys version spans all tenants.

    ✅ PL-T128
  • F21.08.02 Levererad

    Per-job status dashboard — drill into one job: last N runs with duration, exit code, log tail, triggering user (if manual), affected tenant (if tenant-scoped). Failure trend chart over 30 d.

    Implemented
  • F21.08.03 Levererad

    Manual run-now — one-click trigger of a registered job with optional parameters (target tenant, dry-run flag, time range). Requires reason + audit entry. Disabled for jobs marked manual_run=false in the registry (e.g. daily-rollup jobs that are not safe to double-fire).

    Implemented
  • F21.08.04 Levererad

    Cancel / retry — abort an in-flight job (if the job implements a cooperative cancel hook) and retry a failed run with the same or adjusted parameters. Both actions audit-logged.

    Implemented
  • F21.08.05 Levererad

    Job queue depth + SLA widget — flags jobs running beyond target duration or piling up in the queue.

    Implemented
  • F21.08.06 Levererad

    Webhook failures inbox — failed outbound webhooks across all tenants. Replay, suppress, or mark handled.

    Implemented
  • F21.08.07 Levererad

    Rate-limit overrides — temporarily lift limits for a specific tenant or IP (during migration, backfill, or incident). Auto-expire.

    Implemented
  • F21.08.08 Levererad

    Feature flags (global) — toggle platform-wide flags with audit.

    Implemented
  • F21.08.09 Levererad

    Database migrations status — list Beanie init state, pending index changes, last migration run timestamp.

    Implemented
  • F21.08.10 Levererad

    Cache invalidation — targeted Redis key/pattern purge. Throttled.

    Implemented
  • F21.08.11 Levererad

    Scheduled maintenance mode — toggle a tenant into read-only or full-offline, with user-facing banner.

    Implemented

Incident Management

F21.09

**Endpoints:**

Så fungerar det
  • F21.09.01 Levererad

    Active incident list — severity, affected tenants, responder, started-at, ETA to resolution.

    ✅ PL-T129
  • F21.09.02 Levererad

    Create incident — severity (SEV1/2/3/4), affected services, affected tenants, initial summary. Auto-pages on-call (PagerDuty Events API v2 / OpsGenie Alerts API v2, dedup_key = sys-incident:<id>).

    ✅ PL-T129
  • F21.09.03 Levererad

    Incident timeline — append-only SysIncidentUpdateEntry with author, status transitions and comms-sent summary.

    ✅ PL-T129
  • F21.09.04 Levererad

    Public status-page sync — POST /sys/incidents/{id}/public toggles visibility; GET /public/status/active returns the customer-safe projection for status.petanque.life, cached 60 s.

    ✅ PL-T129
  • F21.09.05 Levererad

    Post-mortem link — POST /sys/incidents/{id}/post-mortem stores an external URL (Notion/Confluence/Google Docs).

    ✅ PL-T129
  • F21.09.06 Levererad

    Affected-users impact estimate — GET /sys/incidents/{id}/affected-users scans ApiUsageEvent for the outage window.

    ✅ PL-T129
  • F21.09.07 Levererad

    SLA impact report — GET /sys/incidents/{id}/sla-impact emits one credit row per affected tenant (credit tiers 0 / 10 / 25 / 50 % at 0 / 45 / 240 / 720 outage minutes, 99.9 % target).

    ✅ PL-T129

Support Workbench

F21.10
Så fungerar det
  • F21.10.01 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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 Levererad

    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

Audit Log

F21.11
Så fungerar det
  • F21.11.01 Levererad

    Immutable audit collection sys_audit_entries — append-only, WORM-stored after 24 h via SHA-256 hash chain. Hourly audit-worm-seal cron, mutation guard rejects sealed-row writes with 409 audit_worm_sealed.

    ✅ PL-T131
  • F21.11.02 Levererad

    Entry schema — actor_id, actual_user_id (nullable, set during impersonation), action, target_type, target_id, tenant_id, impersonation_id, reason, ip, ua, request_id, sys_session_id, metadata (action-specific JSON), timestamp plus seal columns (worm_sealed, worm_hash, prev_hash, seal_sequence).

    Implemented (PL-T131)
  • F21.11.03 Levererad

    Filter + search UI — by actor, target, action, tenant, time range, reason text. Cursor pagination (timestamp DESC, _id DESC, base64url JSON cursor). Per-operator saved searches with owner isolation.

    Implemented (PL-T131)
  • F21.11.04 Levererad

    Export — CSV and JSON. Sync stream for ≤ 100 k rows; larger sets queue a SysAuditExportJob, persist the artifact via AuditExportSink (Azure Blob SAS in prod, in-memory in dev/test), return a signed URL with 7-day expiry.

    Implemented (PL-T131)
  • F21.11.05 Levererad

    Impersonation sub-view — distinct sessions aggregated by impersonation_id (start/end/target/reason/reads/writes), clickable to drill into the audit list filtered by that session.

    Implemented (PL-T131)
  • F21.11.06 Levererad

    Integrity check — daily audit-integrity-check cron walks the chain, persists a SysAuditIntegrityDigest, opens a SEV1 SysIncident on tamper detection. Dashboard surfaces latest_ok digest. Manual run gated on sys_security + fresh-auth.

    Implemented (PL-T131)
  • F21.11.07 Levererad

    SIEM export — per-tenant SysSiemConfig (Azure Monitor / Splunk / Datadog / mock). 15-minute audit-siem-push cron ships the delta since last_pushed_at. Pluggable transport, test endpoint pushes a probe payload through the configured sink.

    Implemented (PL-T131)

Sys-Originated Communications

F21.12
Så fungerar det
  • F21.12.01 Levererad

    Broadcast composer — reach all tenants, selected tenants, selected roles, or a custom filter. Channels: in-app banner, email (SendGrid batch, 10 k/batch, 100 rps), push (FCM/Expo, 100/batch). Test-send mandatory ≤ 30 min before /send.

    ✅ PL-T132
  • F21.12.02 Levererad

    Banner library — reusable SysBannerTemplate (maintenance, incident, feature) spawned into SysBannerActivation rows with start/end windows and tenant/role audience. Consumed by admin/app via GET /me/banners.

    Implemented (PL-T132)
  • F21.12.03 Levererad

    Release-notes publisher — SysReleaseNote with internal \

    external visibility, sections, linked PR URLs. Public subset exposed at /public/changelog and rendered on www/ via Changelog.astro. | Implemented (PL-T132)
  • F21.12.04 Levererad

    Sys operator inbox — SendGrid-inbound webhook routes broadcast replies + inbound support mail to SysSupportInboxMessage. Assign/close workflow surfaced in sys/comms/inbox.

    ✅ PL-T132
  • F21.12.05 Levererad

    Test-send gate — /send returns 412 SysBroadcastTestSendRequired unless the current operator has a test_sent_at ≤ 30 min old. Gate is per-operator so handovers force a fresh test-send.

    Implemented (PL-T132)
  • F21.12.06 Levererad

    1:1 direct message to user — POST /sys/users/{id}/messages for proactive sys-to-user DM (channels: email/push/in-app). Fresh-auth + reason (≥ 10 chars) gate, rate-limit 5 DM/operator/user/24 h, mandatory self-test-send before real delivery, user opt-out via GET

    PUT /me/notifications/preferences.dm_muted. Opt-out suppresses channels (recorded in receipt). Every send audited as sys.user.direct_message.{test_send,sent,failed}. | ✅ PL-T148

Compliance & Legal

F21.13
Så fungerar det
  • F21.13.01 Levererad

    GDPR request tracker — user access / export / erasure requests with deadlines, assignee, status.

    ✅ PL-T133
  • F21.13.02 Levererad

    Execute request — triggers gdpr service endpoints and packages output. SLA timer on dashboard.

    ✅ PL-T133
  • F21.13.03 Levererad

    Legal document publisher — ToS, DPA, Privacy Policy, SLA. Builds on PL-T094 infrastructure. Diff viewer between versions.

    ✅ PL-T133
  • F21.13.04 Levererad

    Re-acceptance campaigns — mark a version as requiring re-accept, track acceptance rate per tenant.

    ✅ PL-T133
  • F21.13.05 Levererad

    Security posture — snapshot of CIS benchmarks, SBOM age, penetration test findings.

    ✅ PL-T133
  • F21.13.06 Levererad

    DPIA register — log DPIAs per feature; dashboard flags features needing review.

    ✅ PL-T133
  • F21.13.07 Levererad

    Subprocessor list — public /legal/subprocessors page, changelog, tenant notification on change.

    ✅ PL-T133
  • F21.13.08 Levererad

    Breach notification workflow — SEV1 incident + personal data impact → guided notification workflow under GDPR Article 33 (72 h).

    ✅ PL-T133
  • F21.13.09 Levererad

    Retention policy overview — per collection, configured TTL vs observed age, flags anomalies.

    ✅ PL-T133
  • F21.13.10 Levererad

    GDPR snapshot (read-only) — GET /sys/users/{id}/gdpr-snapshot aggregates 13 categories (identity, profiles, tenant memberships, licenses, payments sent/received, matches, tournaments, communications, audit-on-user, linked OAuth, sessions summary, consent history) with sensitive payloads redacted (password hashes, session fingerprints, M2M secrets). Internal support tool — not a replacement for the formal Article 15 export (F21.13.02). Downloadable JSON, audited as sys.user.gdpr_snapshot.{read,download}.

    ✅ PL-T148

Product Analytics

F21.14
Så fungerar det
  • F21.14.01 Levererad

    Product usage dashboard — feature adoption rates across tenants, per plan, per country.

    ✅ PL-T134
  • F21.14.02 Levererad

    Funnel analysis — signup → first match → active user. Per tenant and aggregate.

    PL-T134
  • F21.14.03 Levererad

    Retention cohorts — weekly/monthly, sliced by signup source, plan, country.

    PL-T134
  • F21.14.04 Levererad

    Feature-flag experiment readout — conversion lift, confidence interval, sample size per variant.

    PL-T134
  • F21.14.05 Levererad

    Custom query — read-only MongoDB playground with saved queries, 10 k row cap, sys_engineer-gated.

    PL-T134
  • F21.14.06 Levererad

    Product-metric catalog — executable definitions for DAU/WAU/MAU/stickiness/retention-m1/active-tenants/signups. Single source of truth referenced from dashboards.

    PL-T134

Infrastructure & Costs

F21.15
Så fungerar det
  • F21.15.01 Levererad

    Azure cost breakdown — per service (Cosmos, ACR, SWA, Container Apps, AI Services) per month, with trend. Data from Azure Cost Management API. Results cached 6 h; MTD window + per-bucket amounts; pluggable AzureProvider with stub fallback when sp-petanque-sys-cost-reader is missing.

    ✅ PL-T135
  • F21.15.02 Levererad

    Container app metrics — CPU, memory, replicas, request rate, P50/P95/P99 latency per service. Hard-coded allowlist SYS_INFRA_CONTAINER_APPS (api/admin/app/sys/web/www); 60 s cache; 404 SysInfraContainerAppUnknown for anything outside the list.

    Implemented (PL-T135)
  • F21.15.03 Levererad

    Error-rate dashboard — 5xx by endpoint over 1/6/24/72 h, top offenders, per-row App Insights KQL deeplink. Aggregated from ApiUsageEvent; backend caps at 20 000 rows per window (follow-up in the backlog for aggregation pipeline).

    Implemented (PL-T135)
  • F21.15.04 Levererad

    Uptime log — 30-day rolling availability per service, derived from SysIncident.affected_services overlapped with the window. Services outside any incident surface 100 %.

    Implemented (PL-T135)
  • F21.15.05 Levererad

    Budget alerts — create/activate/deactivate per-service thresholds with notify list + optional PagerDuty. sys_engineer or sys_finance for writes; sys_support rejected by the capability gate. Daily sys_infra_budget_alert job probes Azure MTD and fires the pluggable BudgetNotifier.

    Implemented (PL-T135)
  • F21.15.06 Levererad

    Cosmos RU/s dashboard — provisioned vs consumed (P95), throttled requests, top hot partitions per collection. Collection + window-hours query params; per-collection cache keyed by window.

    Implemented (PL-T135)
  • F21.15.07 Levererad

    External-dependency health — latest probe per upstream (SendGrid/PayPal/Azure/…). Scheduled sys_dep_health job probes every 5 min via the pluggable DepProbe; endpoint returns the most recent SysDepHealthSample per service with status badge (up/degraded/down/unknown).

    Implemented (PL-T135)
  • F21.15.08 Levererad

    Service dependency graph — static service-graph.yaml enriched with live health + open incidents, rendered as a force-directed SVG at /dependencies. Regional-scope badges (SE-only, FR-only). Four GET /sys/dependencies[/…] endpoints (list, detail, impact, historical incidents); impact cached 5 min via InfraCache. See specs/api/endpoints/sys-dependencies.md + specs/sys/views/dependencies.md.

    ✅ PL-T147
  • F21.15.09 Levererad

    Impact analysis — GET /sys/dependencies/{id}/impact returns exposed MRR, affected tenant count + top-5 by MRR, affected features / endpoints, mitigation. Regional-aware filter excludes non-matching tenants (Bankgirot = SE-only).

    ✅ PL-T147
  • F21.15.10 Levererad

    Incident impact-preview — GET /sys/incidents/{id}/impact-preview aggregates per-service impact across affected_services; rendered in /incidents/{id} above the timeline. Deduplicated tenant union + top-10 by aggregated MRR. Runbook: docs/engineering/operations/incident-impact-analysis.md.

    ✅ PL-T147
  • F21.15.11 Levererad

    Historical-incidents scorecard — GET /sys/dependencies/{id}/incidents?from=&to= lists every incident that affected the service during the window. downtime_minutes clamped to to_date so still-open incidents don't inflate the number. Feeds the upstream-SLA conversation.

    ✅ PL-T147

Developer Tools

F21.16

*Levererat (PL-T136): sju sys-vyer under `/(dashboard)/dev/*` med

Så fungerar det
  • F21.16.01 Levererad

    ERD viewer — visual graph of all collections + relationships. Iframe over the API container's /erd endpoints; manifest from GET /sys/dev/erd.

    ✅ PL-T136
  • F21.16.02 Levererad

    API explorer — Swagger UI med pre-injected sys-JWT (preauthorizeApiKey) och valbar X-Tenant-ID. Manuell probe-loggning via POST /sys/dev/explorer/audit.

    Implemented (PL-T136)
  • F21.16.03 Levererad

    Webhook tester — POST /sys/dev/webhooks/test skickar HMAC-SHA-256-signerad payload till abonnemangets callback med 10 s-timeout; visar status, latens, signatur och svar.

    Implemented (PL-T136)
  • F21.16.04 Levererad

    Seed-data generator — tre presets (minimal_realistic, load_test, stress) med valbara override-counts. Async körning + SSE (GET /sys/dev/seed/{id}/events) + 4 s-poll. Sandbox-by-default.

    Implemented (PL-T136)
  • F21.16.05 Levererad

    Query playground (write) — två-personers godkännande med hash-pinned pipeline (SHA-256), 5 min TTL, self-approval blockad, run re-validerar hashen. Read-mode finns sedan tidigare via /sys/audit/search.

    Implemented (PL-T136)
  • F21.16.06 Levererad

    Schema-diff — GET /sys/dev/schema-diff jämför Beanie-deklarerade index mot live MongoDB; missing/extra/changed per collection + flat drift_count.

    Implemented (PL-T136)
  • F21.16.07 Levererad

    Logs search — GET /sys/dev/logs med åtta strukturerade filter; App Insights-källa när konfigurerad, annars in-process ringbuffer (maxlen=5000).

    Implemented (PL-T136)

Security Operations

F21.17
Så fungerar det
  • F21.17.01 Levererad

    Active user-sessions panel — cross-tenant end-user sessions with filter (tenant, role, origin, suspicious) and bulk-revoke. GET /sys/security/user-sessions + DELETE /sys/security/user-sessions/{id} + POST /sys/security/user-sessions/bulk-revoke; fresh-auth on mutations.

    ✅ PL-T137
  • F21.17.02 Levererad

    Suspicious-activity queue — three rules (impossible_travel, new_device_hva, brute_force) persist SysSuspiciousLoginEvent rows; triage via POST /sys/security/suspicious/{id}/triage with action none

    lock_user|revoke_sessions. | Implemented (PL-T137)
  • F21.17.03 Levererad

    Failed-login heatmap — GET /sys/security/failed-logins?group_by=ip

    email&window=24h aggregates SysFailedLoginSample for abuse detection. | Implemented (PL-T137)
  • F21.17.04 Levererad

    API-key & OAuth-client management — cross-tenant list + rotate + revoke for ApiToken and M2MClient; raw secret returned once after rotation.

    Implemented (PL-T137)
  • F21.17.05 Levererad

    Service Principal management — SysServicePrincipal inventory with expiry, last-used, rotation scheduling; schedule-rotation + rotate endpoints mirror the manual Azure procedure in docs/engineering/security/service-principals.md.

    Implemented (PL-T137)
  • F21.17.06 Levererad

    Secret-rotation runbook launcher — idempotent stepwise rotation for six secret classes (db, jwt_keys, webhook, stripe, sendgrid, bankgirot) with advance/retry/cancel verbs; pluggable RotationStepRunner.

    Implemented (PL-T137)
  • F21.17.07 Levererad

    Per-tenant IP allowlist — GET/PUT /sys/tenants/{id}/ip-allowlist with CIDR list; enforced during tenant authentication.

    Implemented (PL-T137)
  • F21.17.08 Levererad

    Emergency kill-switch — two-person armed via single-use SysKillSwitchApprovalCode (10 min TTL); middleware returns 503 tenant_kill_switch_armed for every non-sys tenant-scoped request until disarmed.

    Implemented (PL-T137)

Release & Deploy

F21.18
Så fungerar det
  • F21.18.01 Levererad

    Deploy status dashboard — current revision, build-id, git-sha, commit message, and health per app (api, admin, app, sys, web, www). GET /sys/releases/status; pluggable DeployStatusProvider with stub fallback.

    ✅ PL-T138
  • F21.18.02 Levererad

    Canary metrics — per-rollout error-rate + P95/P99 latency comparing canary vs stable revisions. GET /sys/releases/canary/{deployment_id}; SSE-streamed samples at SYS_RELEASES_CANARY_SAMPLE_SECONDS; threshold deltas configurable.

    Implemented (PL-T138)
  • F21.18.03 Levererad

    Rollback trigger — Container App revision switch, POST /sys/releases/{app}/rollback. sys_engineer + fresh-auth; every attempt persists a SysRollbackRecord regardless of outcome; failed executor calls return 502 with the record id.

    Implemented (PL-T138)
  • F21.18.04 Levererad

    DR drill history — GET /sys/releases/dr-drills list + POST /sys/releases/dr-drills create + POST /sys/releases/dr-drills/{id}/remediation two-step remediation attach; sys_engineer + fresh-auth for writes. Extends PL-T081.

    Implemented (PL-T138)
  • F21.18.05 Levererad

    Release-notes drafter — POST /sys/releases/notes/draft?from_tag=<>&to_tag=<>; walks PRs between tags via GitHub GraphQL, groups by label into Features/Improvements/Bug fixes/Security/Other markdown sections; stub fallback when SYS_RELEASES_GITHUB_TOKEN is absent.

    Implemented (PL-T138)

Data Export & Backups

F21.19
Så fungerar det
  • F21.19.01 Levererad

    Tenant export on demand — queued async job, produces signed download URL, expires in 7 d.

    ✅ PL-T139
  • F21.19.02 Levererad

    Platform-wide snapshot — scheduled daily full backup, retained 30 d.

    Implemented (PL-T139)
  • F21.19.03 Levererad

    Point-in-time restore (tenant scope) — restore a single tenant to a timestamp, preserving cross-tenant integrity.

    Implemented (PL-T139)
  • F21.19.04 Levererad

    Backup verification — automated weekly restore-to-sandbox test.

    Implemented (PL-T139)
  • F21.19.05 Levererad

    Data-residency summary — which data lives in which region, for regulatory reviews.

    Implemented (PL-T139)
  • F21.19.06 Levererad

    Continuity export pipeline — daily snapshot + hourly delta to tenant-controlled S3/SFTP, PGP/age-encrypted with tenant-managed keys, weekly fetch-back verify, stop-on-cancellation.

    ✅ PL-T227
  • F21.19.07 Levererad

    Source-code escrow pipeline — semver-release-trigger bygger deterministisk deployment-bundle (api/admin/app/web/www/sys/iac/runbooks), sealar med age multi-recipient, levererar via SFTP till NCC Group + Iron Mountain, append-only EscrowDeposit-ledger via HMAC-callback. Manual-deposit-vy för out-of-band hot-fixes (sys_security + fresh-auth). 90-day gap → SEV2-incident, kvartals-drill via SEV3-incident. Composite /sys/escrow/status-vy med latest-deposit, per-provider summary, kvartalsvis täckning, federation-tier-tenants, trigger-events-checklist.

    ✅ PL-T228
  • F21.19.08 Levererad

    Wind-down commitment + funded reserve — 4-språkig (sv/en/fr/es) klausul-versionering med SHA-256 audit-hash chain (kanonisk engelsk källa), per-tenant WindDownReserve (deposit eller insurance) med target = 12 × monthly_cost + auto-flip till short < 80 %, 2-of-N co-sign + fresh-auth för release/return, kvartalsdrill (tabletop/partial_dry_run/full_dry_run) med outcome+findings, composite /sys/wind-down/readiness-vy som aggregerar reserve-status + continuity-export-fräschhet (PL-T227) + escrow-drift (PL-T228) + klausul-attachment + drill-status till green/yellow/red. Stripe-webhook payment_intent.succeeded med metadata.purpose=wind_down_deposit triggar mark_funded. 5 cron-jobb: target-recompute (dygnsvis), verification-reminder (30d), readiness-monthly-report, drill-reminder-quarterly (SEV3 om > 90d), clause-audit-hash-check (SEV1 vid mismatch).

    ✅ PL-T229

Onboarding Workbench

F21.20

## F21.20.QA — Smoke-protocol & route inventory

Så fungerar det
  • F21.20.01 Levererad

    New-tenant checklist — SysOnboardingChecklist with eight default steps (contract → go-live), per-step status + assignee + blocking reason; PATCH /sys/onboarding/{tenant_id}/steps/{step_id} auto-flips status and fills completed_at when all steps pass.

    ✅ PL-T140
  • F21.20.02 Levererad

    Per-tenant onboarding status — GET /sys/onboarding dashboard with active/completed filter, days-open, current step, assignee.

    Implemented (PL-T140)
  • F21.20.03 Levererad

    Data-import wizard — POST /sys/onboarding/{tenant_id}/imports queues SysOnboardingImport (csv/sie/fipjp/xml/json), surfaces validation preview + progress; wraps PL-T056 importer.

    Implemented (PL-T140)
  • F21.20.04 Levererad

    Tenant-health check — POST /sys/onboarding/{tenant_id}/health-check runs three pluggable probes (sample login, sample transaction, sample member report) via set_health_probes(); aggregates to pass/warn/fail.

    Implemented (PL-T140)
  • F21.20.05 Levererad

    Onboarding-timeline retrospective — GET /sys/onboarding/retrospective?group_by=quarter

    country cohort summaries (time-to-first-transaction / first-member / go-live). | Implemented (PL-T140)
  • F21.20.06 Levererad

    Sys-admin onboarding-kurs — 30-dagars-progression med 4 veckokapitel, 20 sandbox-uppgifter, 5 checkpoints + mentor-guide under docs/engineering/operator-manual/onboarding/. In-konsol-tracker /onboarding-progress med checkbox per uppgift, mentor-signoff och inbäddad markdown-detaljvy via /help/[slug]. Routen försvinner ur sidebaren när autonomous=true.

    ✅ PL-T299

Growth tools: Webinars

F21.21
Så fungerar det
  • F21.21.01 Levererad

    Sys /webinars list + create + detail + registrations + interest views; all mutations require sys_support

    sys_engineer + fresh-auth and write to write_sys_audit (sys.webinar.* actions). | ✅ PL-T151
  • F21.21.02 Levererad

    POST /sys/webinars — auto-provisions Google Calendar event + Meet URL via Service Account + domain-wide delegation impersonating host_email; on failure persists DRAFT and returns meet_provision_error so the operator can retry via PATCH.

    Implemented (PL-T151)
  • F21.21.03 Levererad

    PATCH /sys/webinars/{id} — patches Calendar event with sendUpdates="all" when timing/host/title/description changes; DB update is not rolled back on Calendar failure (surfaced as meet_provision_error).

    Implemented (PL-T151)
  • F21.21.04 Levererad

    Lifecycle actions — publish (DRAFT → SCHEDULED, refuses without meet_url), cancel (emails attendees, deletes Calendar event with sendUpdates="all", flips promote_on_homepage=false), recording (ENDED → PUBLISHED + promote_on_homepage=false), delete (DRAFT only).

    Implemented (PL-T151)
  • F21.21.05 Levererad

    Attendee management — GET /registrations with full attendee doc, GET /registrations.csv UTF-8 BOM export, POST /send-reminder (idempotent 24h/1h horizons via reminder_*_sent_at), POST /mark-attendance bulk toggle.

    Implemented (PL-T151)
  • F21.21.06 Levererad

    Interest-lead preview — GET /sys/webinars/{id}/interest shows MarketingWebinarInterest rows whose topics overlap this webinar's tags and who are not unsubscribed (i.e. audience for webinar-post-event-dispatch fan-out).

    Implemented (PL-T151)

Newsletter management (sys-scope)

F21.23
Så fungerar det
  • F21.23.01 Levererad

    Sys /newsletter overview + lists + segments + templates + campaigns + automations + ai-assist + deliverability + suppressions + analytics-overview. All mutations gated by sys_support+ (subset sys_engineer), subset requires fresh-auth (schedule/send-now/send-now/cancel, subscriber DELETE, suppression DELETE, CSV import attestation, automation enable/disable). All actions write to write_sys_audit (newsletter.*).

    ✅ PL-T152
  • F21.23.02 Levererad

    List CRUD — scope (platform/tenant) + from-domain verification + archival + subscriber CSV export/import with operator-attested consent; GDPR subscriber delete cascades events + enrolments, preserves suppression row.

    ✅ PL-T152
  • F21.23.03 Levererad

    MJML template editor — Monaco split-pane with live iframe preview (mobile/desktop), sample-var injection, server-side MJML→HTML/text render with cache regen on save, 12+ premium starter templates in /templates/gallery.

    ✅ PL-T152
  • F21.23.04 Levererad

    Visual segment-builder — drag AND/OR groups with allow-listed fields + ops, live recipient-count via POST /sys/newsletter/segments/preview, sparbara segments återanvändbara i campaign-wizard.

    ✅ PL-T152
  • F21.23.05 Levererad

    Campaign 7-step wizard — audience→template→content per språk (AI-assist)→A/B-variants→schedule (absolut eller send-time-optimization)→Litmus cross-client-preview+spam-score→review. Test-send obligatorisk inom 24h före schedule/send-now.

    ✅ PL-T152
  • F21.23.06 Levererad

    Campaign lifecycle actions — test-send, schedule, send-now, pause, resume, cancel. Fresh-auth på schedule/send-now/cancel. Paused state persisterad; resume fortsätter där dispatcher stannade.

    ✅ PL-T152
  • F21.23.07 Levererad

    Campaign analytics — timeline (sent/delivered/opened/clicked), top-clicked links, per-variant bars med Wilson-CI på open/click-rate, winner-banner när newsletter-ab-winner-job kört.

    ✅ PL-T152
  • F21.23.08 Levererad

    Heatmap-vy — click-density-overlay på rendered HTML, tooltip per <a> med link_id + click-count + unique-count via click-koordinat-aggregation.

    ✅ PL-T152
  • F21.23.09 Levererad

    Cohort-analytics — open/click/unsub-rate per tenant/role/language/source; CSV-export.

    ✅ PL-T152
  • F21.23.10 Levererad

    Visual automation-flow-builder (react-flow) — trigger→wait→send→branch→exit, drag-palette, step-config i side-panel, 3 preset-flows (7-day onboarding, 30-day re-engagement, 90-day win-back) seed-installed disabled. Enable kräver fresh-auth.

    ✅ PL-T152
  • F21.23.11 Levererad

    AI-assist playground — tre flikar (subject-lines, content-polish, audience-reaction) via Gemini 2.5 Pro, quota 50/operator/24h via newsletter_ai_quota-counter, quota-visning i UI. Alla calls audit-loggade med prompt+response-hash.

    ✅ PL-T152
  • F21.23.12 Levererad

    Deliverability-dashboard per from-domain — reputation 0–100 + 30d trend, SPF/DKIM/DMARC-badges, bounce/complaint/unsub 30d-rater, spam-score-history, halt-sends-action vid threshold-breach (bounce > 2 %, complaint > 0.1 %).

    ✅ PL-T152
  • F21.23.13 Levererad

    Suppressions-vy — global DO-NOT-SEND-lista med reason + source_event_id, search+filter, add (manual) / remove (fresh-auth + reason).

    ✅ PL-T152
  • F21.23.14 Levererad

    Analytics-overview — cross-campaign list growth, churn rate, engagement trend, at-risk-subscriber-count med länk till segment.

    ✅ PL-T152
  • F21.23.15 Levererad

    Design-system-efterlevnad — inga råa hex-literaler, inga inline shadowOpacity, inga ad-hoc fontFamily; keyboard-nav + focus-rings; WCAG AA i både ljus/mörk mode; ARIA-roller på segment/flow-builder.

    ✅ PL-T152

Premium UX polish

F21.24

## Cross-cutting non-functional requirements

Så fungerar det
  • F21.24.01 Levererad

    Dual-mode layout — useDeviceClass({ cutoff: 640 }) väljer desktop-shell (sidebar + topbar) eller mobil-shell (bottom-tab med fem slottar + drawer + komprimerad topbar). Ingen route-switch, ingen remount av datahooks när breakpoint korsas.

    ✅ PL-T155
  • F21.24.02 Levererad

    Reaktiv ThemeProvider — sys/app/_layout.tsx wrappar hela appen i <ThemeProvider defaultMode="auto"> från shared-paketet. InkModeToggle i topbaren togglar light/dark/auto live utan reload. auto följer prefers-color-scheme. Persistens i localStorage via petanque-life:theme-mode.

    ✅ PL-T155
  • F21.24.03 Levererad

    Command palette — ⌘K / Ctrl+K öppnar fuzzy-sökbar lista över alla routes. Token-baserad match + label-length-tiebreak. Enter navigerar, Esc stänger. Registry i sys/src/lib/command-registry.ts — 28 kommandon per sektion (Navigering, Kundbas, Drift, Ekonomi, Styrning, Insikt, Utveckling, Kommunikation).

    ✅ PL-T155
  • F21.24.04 Levererad

    Tangentbordsgenvägar (g-prefix) — g t → /tenants, g i → /incidents, g b → /billing, g a → /audit, g u → /user-directory, g d → /, m.fl. 1.5 s-buffer, ignorerar inputs/textareas. ? öppnar cheatsheet-overlay.

    ✅ PL-T155
  • F21.24.05 Levererad

    Konsekvent <PageHeader> — alla detail-, refund-, recovery-, sessions-, merge-, sla-impact-, affected-users-, mail-inbox-, firehose-, dependencies-, system-map-vyer använder samma header-primitive (title + subtitle + eyebrow + actions). Ingen inline-<Text style={styles.title}>-duplicering kvarstår.

    ✅ PL-T155
  • F21.24.06 Levererad

    Komplett theme-reaktivitet i UI-primitiver — AppShell, Topbar, Sidebar-overlay, MobileBottomTabs, InkModeToggle, CommandPalette, ShortcutHelpOverlay läser useTheme().colors. Kvarvarande primitiver (Card, KpiCard, Button, Text, Banner, EmptyState, StatusDot, Skeleton, PageHeader, SectionHub, Sidebar) läser fortfarande statiska token-imports — se tasks/OWNER_TODO.md.

    ✅ PL-T292
  • F21.24.07 Levererad

    Mobil list→card-omvandling — tenants, incidents, tickets, user-directory, audit scrollar fortfarande horisontellt på mobil. Card-variant är nästa steg, se tasks/OWNER_TODO.md.

    ✅ PL-T292
  • F21.24.08 Levererad

    Perf-budget — bundle ≤ 3.5 MB, per-icon lucide-imports, route-lazy-loading för ≥ 500-rad-vyer, P95 TTI < 2 s. Initial instrumentering klar; full audit + Lighthouse-körning återstår.

    ✅ PL-T292
  • F21.24.09 Levererad

    In-app help-system — kontextuell ?-modal i topbaren (sys/src/components/HelpModal.tsx) plus /help-katalog (sys/app/(help)/) som surfar docs/engineering/operator-manual/ + docs/engineering/runbooks/ direkt i konsolen. Markdown bundlas vid build via sys/scripts/build-manual-data.mjs; route → runbook-mappning sker mot sys/src/help/route-mapping.yaml och frontmatter routes:. Tangentbord: ? / Cmd+/ togglar modalen, Esc stänger; katalogen har sub­strängs­sök, role-filter och utskriftsvänlig detaljvy.

    ✅ PL-T295
  • F21.24.10 Levererad

    Public operator handbook — help.petanque.life (Astro-statiska SWA swa-petanque-help) speglar docs/engineering/operator-manual/ + docs/engineering/runbooks/ som en sökbar, indexerbar publik sajt. Pipeline: help/scripts/sync-from-docs.mjs → Astro build → Pagefind in-browser-sökindex → SWA-deploy via .github/workflows/deploy-help.yml. Per-roll-landningssidor, ordlista, ändringsfeed (auto från git log), ToC, mörkt läge, OG-bilder per sida, Schema.org TechArticle, sitemap.xml. Sekretess-policy enforced av tools/audit/verify_help_no_secrets.py som CI-gate (help-secrets-scan.yml); sync-skriptet redaktar dessutom personliga e-postadresser automatiskt.

    ✅ PL-T298