Skip to main content
Petanque Life
← Back to all features
09

Communication & Media

167 features · 16 subsystems

Communication tools, content management, live streaming, and media management for the petanque community.

News & Content Management

F09.01
Planned
How it works
  • F09.01.01 Shipped

    News article creation and publication (per federation level)

    ✅ PL-F0901a
  • F09.01.02 Shipped

    Content categories (results, announcements, interviews, opinions)

    ✅ PL-F0901a
  • F09.01.03 Shipped

    Multi-language content support

    ✅ PL-F0901a
  • F09.01.04 Shipped

    Editorial workflow (draft > review > publish)

    ✅ PL-F0901a
  • F09.01.05 Shipped

    Featured/pinned content

    ✅ PL-F0901b
  • F09.01.06 Shipped

    News feed aggregation (from all federation levels)

    ✅ PL-F0901b
  • F09.01.07 Shipped

    RSS feed generation

    ✅ PL-F0901b
  • F09.01.08 Shipped

    Content scheduling (publish at future date)

    ✅ PL-F0901b

Newsletter / Waitlist Signup — Slice 0 (D-GROWTH-0)

F09.10
Shipped

Publik double-opt-in-yta för väntelistan/nyhetsbrevet på www — det minimum som låser upp

  • F09.10.01 Shipped

    Publik signup (namn/e-post/roll/land + frivillig klubb) med honeypot + 5/IP/h rate-limit, neutral 202 utan adress-enumeration

    ✅ 1b61816b.m-2.t-2
  • F09.10.02 Shipped

    Double opt-in: HMAC-signerade purpose-bundna tokens (confirm 14 d · prefs 30 d · unsub 90 d), consent proof (ip/ua-hash + form-id + metod)

    ✅ 1b61816b.m-2.t-2
  • F09.10.03 Shipped

    Minimalt preferenscenter (läs/rätta namn/roll/land) + avprenumeration inkl. RFC 8058 one-click

    ✅ 1b61816b.m-2.t-2
  • F09.10.04 Shipped

    Interim EmailDistributor (Mailjet primär / ACS failover / Null dev-test), jobbköad DOI-leverans (newsletter_doi_send)

    ✅ 1b61816b.m-2.t-2
  • F09.10.05 Shipped

    Suppression i D-COMM-nyckelform (scope, channel, address), reason hard_bounce/complaint/unsubscribe; leverantörs-webhook (signaturverifierad, idempotent)

    ✅ 1b61816b.m-2.t-2
  • F09.10.06 Shipped

    UTM-attribution (source/medium/campaign) lagras på prenumeranten (D-GROWTH §12-grunden)

    ✅ 1b61816b.m-2.t-2

Notification System

F09.02
Shipped

> **Go-ombyggnadens status (D-COMM `040878a9.m-1`, 2026-08-10).** Notispipens **kärna** är

How it works
  • F09.02.01 Shipped

    Email notifications (SendGrid)

    ✅ PL-F0902a
  • F09.02.02 Shipped

    SMS notifications (46elks)

    ✅ PL-F0902a
  • F09.02.03 Shipped

    Push notifications (FCM)

    ✅ PL-F0902a · Go-ombyggnad: transportkärnan LEVERERAD i 4776ad23.m-1 (D-PUSH) — enhetsregister, APNs/FCM v1-providers, synkron + asynkron fan-out (push.fan_out), token-självläkning, push_dispatch_log + DeliveryEventSink, nattlig push_stale_token_purge. 4776ad23.m-2 la den mallstyrda vägen POST /v1/push/dispatch/by-template + per-tenant-konfigurationen (GET/PUT /v1/push/config: default-ikon/ljud, anti-spam-taket max_push_per_user_per_hour, branded-build-bindning). 4776ad23.m-3 slöt de två återstående ytorna: app-sidans behörighets-onboarding/registrering/mottagning (se F15.01.07) och sys-operatörsytan — Push-hälsa (/halsa/push: härledd providerhälsa, invalid_token-andel, fan-out-dead-letter, purge-status och credential-rotationens testsändning) samt Transport-loggen (/halsa/push/logg: sökbar push_dispatch_log med Notistyp och keyset-paginering) över GET /v1/sys/push/health, GET /v1/sys/push/dispatch-log och POST /v1/sys/push/test-send (specs/api/endpoints/push-sys.md). Orkestreringen (kanalval, preferenser, quiet hours, digest, leveranshistorik) är D-COMM
  • F09.02.04 Shipped

    Notification templates (customizable per federation, per language)

    ✅ PL-F0902a · Push-delen ombyggd i Go: LEVERERAD i 4776ad23.m-2 (D-PUSH) — push_template per (förbund, notistyp, språk) med append-only versionshistorik, strikt {{variabel}}-rendering (saknad platshållare ⇒ 422 missing_variables), åtgärdsknappar/deep-link/bild/badge-policy, sidoeffektsfri preview av BÅDA nyttolasterna (APNs + FCM v1) och admin-editorn /kommunikation/push med serverdriven live-preview (specs/admin/views/push-mallar.md). E-post-/SMS-/in-app-mallmotorn LEVERERAD i 040878a9.m-2 (D-COMM) — notification_template per (förbund, notistyp, kanal, språk) med deklarerade variabler, append-only notification_template_version med revert/clone, sidoeffektsfri preview/validate, HTML-wrapper för e-post ({{content}}) och fallback-mall med exakt ett hopp; capability notification:template:manage, ETag/If-Match på varje mutation (specs/api/endpoints/notifications.md Del A₂). Admin-editorn för de kanalerna ligger i 040878a9.m-2.t-2. Push-mallen förblir D-PUSH:s: notification_template.channel utesluter push (§5d.1)
  • F09.02.05 Shipped

    Notification preferences per user (channels, frequency, quiet hours)

    ✅ PL-F0902a · Go-ombyggnad: API:t LEVERERAT i 040878a9.m-2 (D-COMM) — GET|PATCH /v1/me/notification-preferences i EFFEKTIV form (systemets + förbundets defaults, version=0 utan egen rad, notistypskatalogen med), ägar-skopat ur token-contexten (ingen rutt tar en användarreferens), ETag/If-Match, tysta tider validerade mot binärens inbyggda IANA-databas och tolkade i personens → förbundets default_timezone. Skärmen ligger i 040878a9.m-2.t-2
  • F09.02.06 Shipped

    Competition-specific notifications (draw published, next match, results)

    ✅ PL-F0902b
  • F09.02.07 Shipped

    License renewal reminders (notification type)

    ✅ PL-F0902b
  • F09.02.08 Shipped

    Ranking change notifications (notification type)

    ✅ PL-F0902b
  • F09.02.09 Shipped

    Federation circular distribution (bulk send)

    ✅ PL-F0902b · Go-ombyggnad: LEVERERAD i 040878a9.m-4 (D-COMM) — POST /v1/bulk-notifications (utkast/schemalagt/skickat, obligatorisk Idempotency-Key), outboxen GET /v1/bulk-notifications med mätta sent_count/failed_count (aggregat över bulk_id, aldrig lagrade räknare), PATCH med If-Match medan körningen är utkast/schemalagd, :send/:cancel, sidoeffektsfri :preview och per-mottagare-resultat via GET /v1/notifications/delivery-log?bulk_id=. Fan-out:en är asynkron (notification.bulk_fanout + notification_bulk_tick), chunkad om 500 och återupptagbar
  • F09.02.10 Shipped

    Notification delivery log (history per user)

    ✅ PL-F0902b · Go-ombyggnad: LEVERERAD i 040878a9.m-3 (D-COMM) — GET /v1/notifications/delivery-log (keyset, tenant-skopad, server-side fritext på namn eller adress, filter kanal/status/notistyp/from, to)), radens tidslinje GET .../delivery-log/{notification_id} med leverantörens kvitton + adressens effektiva spärrstatus, och räknarstatistiken GET /v1/notification-statistics (flyttalsfri; ingen öppnings-/klickmetrik — transactional spårar inte). Capability notification:delivery:read; admin-ytan /kommunikation/leveranslogg ([specs/admin/views/kommunikation-leveranslogg.md). Rader utan inkopplad kvittensväg redovisas som "Sänd — inget leveranskvitto", aldrig som levererade
  • F09.02.13 Shipped

    Delivery webhooks per provider — leverantörsneutral mottagare POST /v1/notifications/webhooks/{provider} + adapterregister (Mailjet wire:ad)

    ✅ 040878a9.m-3 (D-COMM) — fail-closed HMAC-verifiering i konstant tid (X-PL-Webhook-Signature, hemlighet ur PL_COMM_WEBHOOK_SECRET_<PROVIDER>, aldrig ur DB), 1 MiB-tak, okänd leverantör ⇒ 404 (registret är inget orakel), idempotens som databas-constraint, batch tillämpad per event. Speglas i notification_delivery_event (append-only) och lyfter radens status till delivered/failed (monotont). O-korrelerade kvitton lagras utan tenant och gallras av notification_delivery_event_purge
  • F09.02.14 Shipped

    Delad suppression i notispipen — spärren prövas vid dispatch och igen vid sändningen

    ✅ 040878a9.m-3 (D-COMM) — en notification_suppression, en beslutsfunktion (internal/suppression), delad med marketing. critical (otp/urgent) trumfar tysta tider men aldrig en spärr. Ny skip-orsak suppressed i dispatch-svaret
  • F09.02.11 Shipped

    Engagement attribution capture — EngagementAttribution-events (email_opened/clicked/bounced, webinar_attended, resource_downloaded, signup) konsumeras från notification-pipen och webinar/resource-event-bus — PL-T223

    ✅ PL-T223
  • F09.02.12 Shipped

    Engagement snapshot builder — idempotent aggregat per (federation_id, club_id, period_type, period_start) med viktad engagement_score 0–100 — PL-T223

    ✅ PL-T223
  • F09.02.15 Shipped

    Sys-plattformsbroadcast — operatörens utskick till förbundens användare (utkast → testskick → granskat → fan-out) med svarsinkorg

    ✅ 040878a9.m-9 (D-COMM) — det tenant-lösa aggregatet platform_broadcast + platform_broadcast_reply (migration 00156), sys-familjen /v1/sys/comm/broadcasts med monoton FSM (409 bär current_status), testskick direkt på MessageSender-lagret till operatörerna (noll notisrader), :mark-reviewed, :fan-out med fresh-auth + armering för schemalagd tid, sidoeffektsfri :preview och den datadrivna rollkatalogen. Fan-outen går genom m-4:s befintliga bulkmaskin — ingen andra väg runt suppression/opt-out/tysta timmar. Capability sys.broadcasts.manage. Sys-ytan /utskick i 040878a9.m-9.t-2 (specs/sys/utskick.md)
  • F09.02.16 Shipped

    Tvärs-tenant leverans- och spärrhälsa för support/drift (operatörsvy)

    ✅ 040878a9.m-9 (D-COMM) — GET /v1/sys/comm/health: 24 h-fönster räknat ur m-1/m-3:s riktiga rader (sända, kvitterade, hårda studsar, klagomål, retry-kö, spärrar per förbund) med serverberäknad ok|warning|no_data ur deklarerade tröskelkonstanter. Integritetslinjen är kontrakt: aggregat per förbund — aldrig en mottagare, adress, ämnesrad eller kropp; spärrar redovisas som antal. Ytan härleder procenten ur räknarna och renderar en tom mängd som tomt tillstånd, aldrig som kvot. Hävning av plattformsspärrar ingår inte (öppen skuld, kräver eget mockup-delta)
  • F09.02.17 Shipped

    Broadcaster-feedarnas latensöversikt tvärs förbund (operatörsvy)

    ✅ 040878a9.m-9 (D-COMM) — GET /v1/sys/comm/feed-latency exponerar m-7:s mätning ur API-processens glidande fönster: p99_ms/max_ms mot feedens budget_ms, samples och subscribers. samples == 0 ⇒ null och ytan skriver *"Ingen mätning"* — aldrig 0 ms; kolumnen etiketteras med den kvantil som faktiskt mäts (p99). Mätningen kan inte seedas — den framkallas med trafik (:test-publish)

In-App Messaging

F09.03
Planned
How it works
  • F09.03.01 Shipped

    Direct messaging (player to player, official to player)

    ✅ PL-F0903
  • F09.03.02 Shipped

    Group messaging (team, club, commission)

    ✅ PL-F0903
  • F09.03.03 Shipped

    Federation announcements (broadcast to all members)

    ✅ PL-F0903 · Go-ombyggnad: LEVERERAD i 040878a9.m-4 (D-COMM) — enkelriktad, org-nod-scopad broadcast (POST /v1/announcements, capability announcement:broadcast i tenant- eller org-nod-scope), publik som deklarativt filter upplöst server-side med hård scope-grind, asynkron fan-out genom notispipen, mottagarens GET /v1/me/announcements och avsändarens kvittensöversikt. Tvåvägs-DM/grupper ägs av D-SOCIAL (§5d)
  • F09.03.04 Shipped

    Message read receipts

    ✅ PL-F0903 · För announcements ombyggt i Go: LEVERERAT i 040878a9.m-4 (D-COMM) — POST /v1/announcements/{id}/read är idempotent (andra anropet lämnar read_at orört; icke-mottagare ⇒ 404) och markerar inkorgsraden läst i samma skrivning; GET /v1/announcements/{id}/read-receipts ger {total, read, unread} och — med båda mandaten — per-mottagare-listan. Kvittensen väcker inkorgens SSE-ström så en andra enhet följer med
  • F09.03.05 Shipped

    File sharing in messages

    ✅ PL-F0903
  • F09.03.06 Shipped

    Message search

    ✅ PL-F0903

Match Recording & Live Production Studio

F09.04
Planned

Turn any phone/tablet into a camera and produce professional-looking match broadcasts with minimal equipment.

How it works
  • F09.04.01 Shipped

    Record a match directly from the app (one phone = one camera)

    ✅ PL-F0904a
  • F09.04.02 Shipped

    Multi-device camera setup (2-4 phones/tablets as camera sources for same match)

    ✅ PL-F0904a
  • F09.04.03 Shipped

    Camera role assignment (wide court view, close-up, cochonnet cam, player cam)

    ✅ PL-F0904a
  • F09.04.04 Shipped

    Automatic camera switching (AI-based or rule-based: switch to active end of court)

    ✅ PL-F0904a
  • F09.04.05 Shipped

    Manual camera switching (director/commentator selects which camera is live)

    ✅ PL-F0904a
  • F09.04.06 Shipped

    Picture-in-picture mode (main feed + small secondary camera)

    ✅ PL-F0904a
  • F09.04.07 Shipped

    Camera sync (all devices synchronized to same timeline)

    ✅ PL-F0904b
  • F09.04.08 Shipped

    Audio from any camera device (or separate microphone device)

    ✅ PL-F0904b
  • F09.04.09 Shipped

    Live score overlay (auto-synced from match scoring in app)

    ✅ PL-F0904b
  • F09.04.10 Shipped

    Player name/photo overlays when camera focuses on them

    ✅ PL-F0904b
  • F09.04.11 Shipped

    Mène counter and match progress bar

    ✅ PL-F0904b
  • F09.04.12 Shipped

    Boule count indicator (how many boules each team has left)

    ✅ PL-F0904b
  • F09.04.13 Shipped

    Tournament bracket/standings overlay

    ✅ PL-F0904c
  • F09.04.14 Shipped

    Sponsor logo overlay (configurable per competition)

    ✅ PL-F0904c
  • F09.04.15 Shipped

    "Lower third" graphics (player stats, ELO, ranking)

    ✅ PL-F0904c
  • F09.04.16 Shipped

    Custom text overlay (commentator can add text messages)

    ✅ PL-F0904c
  • F09.04.17 Shipped

    Commentator mode (separate device for commentary audio)

    ✅ PL-F0904c
  • F09.04.18 Shipped

    Director mode (separate device to switch cameras and control overlays)

    ✅ PL-F0904c
  • F09.04.19 Shipped

    Drawing tools on screen (draw arrows, circles to demonstrate tactics)

    ✅ PL-F0904d
  • F09.04.20 Shipped

    Telestrator (draw on paused/live video like sports TV)

    ✅ PL-F0904d
  • F09.04.21 Shipped

    Instant replay (mark moments, replay from any camera angle)

    ✅ PL-F0904d
  • F09.04.22 Shipped

    Slow-motion replay

    ✅ PL-F0904d
  • F09.04.23 Shipped

    Split-screen comparison (show two angles simultaneously)

    ✅ PL-F0904d
  • F09.04.24 Shipped

    Pre-match player cards (show stats, ELO, recent form before match)

    ✅ PL-F0904d
  • F09.04.25 Shipped

    Post-mène summary graphic (auto-generated after each end)

    ✅ PL-F0904e
  • F09.04.26 Shipped

    Direct stream to YouTube Live (RTMP integration)

    ✅ PL-F0904e
  • F09.04.27 Shipped

    Direct stream to Twitch

    ✅ PL-F0904e
  • F09.04.28 Shipped

    Direct stream to Facebook Live

    ✅ PL-F0904e
  • F09.04.29 Shipped

    Multi-platform simultaneous streaming

    ✅ PL-F0904e
  • F09.04.30 Shipped

    In-app live viewing (watch directly in Petanque Life)

    ✅ PL-F0904e
  • F09.04.31 Shipped

    Stream quality settings (adapt to available bandwidth)

    ✅ PL-F0904f
  • F09.04.32 Shipped

    Stream schedule and notification (followers get alerted)

    ✅ PL-F0904f
  • F09.04.33 Shipped

    Embeddable live player for federation/club websites

    ✅ PL-F0904f
  • F09.04.34 Shipped

    Live viewer count and chat integration

    ✅ PL-F0904f
  • F09.04.35 Shipped

    Local recording (save to device even without streaming)

    ✅ PL-F0904f
  • F09.04.36 Shipped

    Cloud recording (save all camera feeds to cloud storage)

    ✅ PL-F0904f
  • F09.04.37 Shipped

    Automatic highlight generation (key moments based on score changes)

    ✅ PL-F0904g
  • F09.04.38 Shipped

    Manual highlight clipping (mark start/end of interesting moments)

    ✅ PL-F0904g
  • F09.04.39 Shipped

    Post-match edit: re-cut with different camera angles

    ✅ PL-F0904g
  • F09.04.40 Shipped

    Automatic match summary video (condensed highlights + score)

    ✅ PL-F0904g
  • F09.04.41 Shipped

    VOD archive (all recorded matches searchable by competition, player, date)

    ✅ PL-F0904g
  • F09.04.42 Shipped

    Share match recording (link to specific match video)

    ✅ PL-F0904g
  • F09.04.43 Shipped

    Video linked to match record (watch the video from the match detail page)

    ✅ PL-F0904h
  • F09.04.44 Shipped

    Audio-only mode (commentary without video for low bandwidth)

    ✅ PL-F0904h
  • F09.04.45 Shipped

    Score-only live feed (text updates when video isn't possible)

    ✅ PL-F0904h
  • F09.04.46 Shipped

    Offline recording with later upload (record locally, upload when connected)

    ✅ PL-F0904h
  • F09.04.47 Shipped

    Adaptive bitrate streaming (auto-adjust quality)

    ✅ PL-F0904h

Social Media Integration

F09.05
Planned
How it works
  • F09.05.01 Shipped

    Auto-post results to social media (Facebook, Instagram, X)

    ✅ PL-F0905
  • F09.05.02 Shipped

    Social media content templates

    ✅ PL-F0905
  • F09.05.03 Shipped

    Hashtag management

    ✅ PL-F0905
  • F09.05.04 Shipped

    Social media analytics

    ✅ PL-F0905
  • F09.05.05 Shipped

    User-generated content curation

    ✅ PL-F0905

Photo & Video Management

F09.06
Planned

> **Mediabiblioteket (D-COMM `040878a9.m-5`, backend, 2026-08-11).** Plattformen kunde tala

How it works
  • F09.06.01 Shipped

    Photo gallery per competition/event

    ✅ PL-F0906 · Go-ombyggnad: LEVERERAD i 040878a9.m-5 (D-COMM) — POST|GET /v1/photo-galleries, statusmaskinen draft → open_for_uploads → published som CHECK i DDL:en, idempotent bifogning, :publish med mätta räknare och det publika, grindade GET /public/photo-galleries/{id}
  • F09.06.02 Shipped

    Photographer accreditation management

    ✅ PL-F0906 · Go-ombyggnad: LEVERERAD i 040878a9.m-6 (D-COMM) — media_credential med FSM draft → submitted → in_review → approved|denied, approved → revoked (terminala beslut), zoner, valid-fönster, QR-kort med kid-rotation vid revoke och entry-scan-logg där även varje nekat försök blir en rad. Se F09.15
  • F09.06.03 Shipped

    Photo consent management (GDPR)

    ✅ PL-F0906 · Go-ombyggnad: LEVERERAD i 040878a9.m-5 (D-COMM) — media_consent med FSM pending → granted|denied, granted → withdrawn (terminalt), bevisfälten (source, ip, text_version, recorded_by ur claimen), dubbel audit (comm_audit_event + audit_log) i samma transaktion, och den härledda publiceringsgrinden inklusive art. 8 genom D-GDPR:s port. Självbetjäningsutskicket (*"Send request"*) ligger i m-6
  • F09.06.04 Shipped

    Video highlight creation tools

    ✅ PL-F0906 · Inte ombyggt: klippkomposition/transcoding är ett eget tungt delsystem (F09.04). video/mp4 lagras i biblioteket inom taket, men mäts inte (duration_sec = null)
  • F09.06.05 Shipped

    Media library with tagging and search

    ✅ PL-F0906 · Go-ombyggnad: LEVERERAD i 040878a9.m-5 (D-COMM) — media_item med GIN-indexerade tags, fritext + typ-/synlighets-/galleri-/samtyckesfilter, serverns mätning av mime/size/sha256/mått, mjuk radering med retention-jobb, ETag/If-Match och org-nod-skopad media:manage. Miniatyrer genereras inte
  • F09.06.06 Shipped

    Press kit generation per event

    ✅ PL-F0906 · Go-ombyggnad: LEVERERAD i 040878a9.m-6 (D-COMM) — press_kit (utkast) + append-only press_kit_version, flerspråkigt innehåll, publicering, preview och publik render med embargo + samtyckesgrind. Se F09.12

Document Management

F09.07
Shipped
How it works
  • F09.07.01 Shipped

    Federation document repository (statutes, regulations, templates)

    ✅ PL-F0907
  • F09.07.02 Shipped

    Document versioning and history

    ✅ PL-F0907
  • F09.07.03 Shipped

    Document access control (public, members, officials only)

    ✅ PL-F0907
  • F09.07.04 Shipped

    Form templates (license application, transfer request, etc.)

    ✅ PL-F0907
  • F09.07.05 Shipped

    Digital signature integration

    ✅ PL-F0907

Sociala medier-integration

F09.08
Planned

Platform-specific auto-publishing of match results, player cards, and highlight videos to Facebook, Instagram, X/Twitter, and TikTok. Result graphic generator, player card templates, competition trailers, and social media calendar.

How it works
  • F09.08.01 Shipped

    Auto-post to Facebook after match result

    ✅ PL-F0908a
  • F09.08.02 Shipped

    Auto-post to Instagram with player card

    ✅ PL-F0908a
  • F09.08.03 Shipped

    Twitter/X integration for match results

    ✅ PL-F0908a
  • F09.08.04 Shipped

    TikTok publishing of highlights

    ✅ PL-F0908a
  • F09.08.05 Shipped

    Resultat-grafikgenerator (Open Graph match-result image cards)

    ✅ PL-F0908b
  • F09.08.06 Shipped

    Player card-generator med anpassningsbar mall

    ✅ PL-F0908b
  • F09.08.07 Shipped

    Tävlings-trailer auto-genererad från schema

    ✅ PL-F0908b
  • F09.08.08 Shipped

    Social media calendar per klubb/federation

    ✅ PL-F0908b

Marketing vs transactional emails (gränsdragning)

F09.09
Shipped

*Introducerad: PL-T152. Se `docs/engineering/integrations/email-distributor.md` och `docs/engineering/operations/newsletter-playbook.md`.*

How it works
  • F09.09.01 Shipped

    Delad EmailDistributor-abstraktion — send_transactional vs send_bulk-metoder i samma interface, samma backend (Mailjet/ACS/Null)

    ✅ PL-T152
  • F09.09.02 Shipped

    Delad suppression-lista — hard-bounce/complaint från transactional blockerar även marketing till samma adress och vice versa

    ✅ PL-T152 · Go-ombyggnad: LEVERERAD i 040878a9.m-3 (D-COMM) — en tabell notification_suppression och en beslutsfunktion api/internal/suppression, delad av notispipen och nyhetsbrevet. Interimstabellen newsletter_suppression (D-GROWTH-0 00114) migrerades in som plattformsrader och droppades av 00150. Asymmetrin är kontrakt: hard_bounce/complaint adress-globala, unsubscribe scope-lokal
  • F09.09.03 Shipped

    Separata quota/rate-limits — transactional har ingen throttle, marketing cap:ar via campaign throttle_per_hour

    ✅ PL-T152
  • F09.09.04 Shipped

    Quiet-hours-policy — marketing-sends pausar 22:00–07:00 recipient-local; transactional oberörda; override kräver fresh-auth

    ✅ PL-T152
  • F09.09.05 Shipped

    Separata audit-actions i sys_audit_entries — notification.* för transactional, newsletter.* för marketing; gör det trivialt att revidera varje typ för sig

    ✅ PL-T152

Broadcaster realtidsfeed

F09.10
Planned

Per-tenant realtidsfeed för broadcasters, regi-team och stats-providers

  • F09.10.01 Shipped

    BroadcasterFeed-dokument med slug, event_id, scopes, latency_budget_ms

    ✅ PL-T216 · m-7: tabellen broadcaster_feed (slug, event_ref utan FK över domängräns, scopes, event_types ur den frysta taxonomin, latency_target_ms, revoked_kids) + GET|POST /v1/broadcaster-feeds och GET|PATCH /v1/broadcaster-feeds/{slug} med ETag/If-Match under broadcast:manage
  • F09.10.02 Shipped

    Token-utfärdande JWT ES512 med kid + scopes + broadcaster_id; HS512-fallback för tester

    ✅ PL-T216 · m-7 (ny form): POST /v1/broadcaster-feeds/{slug}/tokens utfärdar ett statlöst b64url(payload).b64url(HMAC-SHA256)-token med härledd nyckel (pinnat domänprefix), strikt base64, sluten claim-mängd utan persondata och TTL 6 h default / 24 h tak; saknad master ⇒ 503, aldrig ett osignerat token. Tokenet lagras aldrig — därför är "aktiva tokens" omätbart och ytan visar i stället *utfärdade sedan senaste kid-rotation*
  • F09.10.03 Shipped

    SSE-streaming /broadcast/public/feeds/{slug}/stream med scope- + event-validering

    ✅ PL-T216 · m-7: samma adress; handshake (token, scope, kid, aktiv feed) före text/event-stream — 403 utan giltig scope, snapshot som första event, heartbeat, Cache-Control: no-cache (F1-8-husmönstret)
  • F09.10.04 Shipped

    In-memory pub/sub-broker med 60-sekunders replay-fönster

    ✅ PL-T216 · m-7: in-process-broker (api/internal/broadcast/channel.go) med 60 s ringbuffert per feed — Redis är avvecklat (architecture-spec §3), och brokern kan bytas utan att kontraktet ändras
  • F09.10.05 Shipped

    Last-Event-ID-reconnect spelar upp missade events efter avbrott

    ✅ PL-T216 · m-7: Last-Event-ID på strömmen spelar upp bufferten, och GET /broadcast/public/feeds/{slug}/replay?last_event_id= är HTTP-fallbacken bakom samma token
  • F09.10.06 Shipped

    Token-revoke roterar kid → äldre tokens fail:ar med token_revoked

    ✅ PL-T216 · m-7: POST /v1/broadcaster-feeds/{slug}/tokens:revoke roterar feedens kid och lägger det gamla i revoked_kids — varje utfärdat token dör omedelbart och öppna strömmar stängs vid nästa heartbeat; en kid-miss svarar token_revoked, skilt från token_invalid
  • F09.10.07 Shipped

    Publish-endpoint validerar event_type mot whitelist (422 om okänd)

    ✅ PL-T216 · m-7: POST /v1/broadcaster-feeds/{slug}:test-publish — event utanför feedens event_types ⇒ 422; test-publiceringen gör feeden bevisbar utan D-COMP, medan skarpa event projiceras ur D-COMP:s live-ström (D-COMM äger ingen scoring-data)
  • F09.10.08 Shipped

    Replay-endpoint /broadcast/feeds/{feed_id}/replay för admin debug

    ✅ PL-T216 · m-7 (ny adress): GET /broadcast/public/feeds/{slug}/replay — token-grindad som strömmen; legacy-adressen är referens, inte kontrakt
  • F09.10.09 Planned

    Feed-detaljens mätta fält: subscribers, latest_sequence och latens mot latency_target_ms

    m-7 — serverns egna mätvärden (aldrig ytans gissning); den tvärs-tenant latenspanelen för operatör LEVERERAD i 040878a9.m-9 (GET /v1/sys/comm/feed-latency + sys-ytan /utskick — se F09.02.17)

Broadcast graphics package

F09.11
Planned

Versionerad grafik-paket för OBS / vMix / Wirecast browser-sources:

  • F09.11.01 Shipped

    BroadcastGraphicsPackage-dokument med theme + templates[]

    ✅ PL-T216 · m-7: broadcast_graphics_package (theme jsonb, templates, version, is_default) + GET|POST /v1/broadcast-graphics och GET|PATCH /v1/broadcast-graphics/{id} med ETag/If-Match under broadcast:manage
  • F09.11.02 Shipped

    Färgvalidering (#rrggbb) på theme-fält

    ✅ PL-T216 · m-7: serverns 422 bär fältet och klartexten *"Färgen måste anges som #rrggbb (t.ex. #1c3626)"* — adminytan visar serverns fel och förhandsdömer aldrig själv
  • F09.11.03 Shipped

    Mall-uppdatering bumpar paketets version automatiskt

    ✅ PL-T216 · m-7: varje sparning bumpar version, och ytan visar bumpen synligt (*"Save bumps version"*)
  • F09.11.04 Shipped

    Embed-endpoint /embed/scorebug/{template_slug} returnerar HTML/JS för OBS

    ✅ PL-T216 · m-7: GET /embed/scorebug/{slug} — fristående HTML/JS ur Go-API:t (fungerar utan Next.js, krav för browser source); adressen byggs av API:ts embed_path + utfärdat token, aldrig av en handbyggd sträng i klienten. Bevisad i Chromium: snapshotställningen visas och uppdateras synligt efter en test-publish
  • F09.11.05 Shipped

    Browser-source-token validerar mot browser scope och package-binding

    ✅ PL-T216 · m-7: samma statlösa, kid-bärande token som strömmen, prövat mot scope och feed/paket — efter revoke svarar embedden 401 token_revoked
  • F09.11.06 Shipped

    "Set default"-paket per tenant; en åt gången

    ✅ PL-T216 · m-7: POST /v1/broadcast-graphics/{id}:set-default — "en default per tenant" är en databasinvariant, inte en applikationsregel
  • F09.11.07 Shipped

    Admin-preview-endpoint renderar mall som standalone HTML för QA

    ✅ PL-T216 · m-7: GET /v1/broadcast-graphics/{id}/preview/{kind} — sidoeffektsfri och genom samma renderare som embedden

Press-kit generator (utbyggd)

F09.12
Planned

Versionerad, flerspråkig press-kit per event eller säsong. Embargo-

  • F09.12.01 Shipped

    BroadcastPressKit-dokument med locales[], content{}, assets[], version

    ✅ PL-T216 · m-6: press_kit (locales text[], content jsonb per locale, assets jsonb, kit_version) + append-only press_kit_version — den publika renderingen läser alltid versionen, aldrig utkastet
  • F09.12.02 Shipped

    Locale-content-validering (varje content-key måste finnas i locales[])

    ✅ PL-T216 · m-6: 422 press_kit_locale_not_declared med lokalen i felet; taggen kanoniseras av i18nlocale.Canonicalize, och PATCH prövar innehåll mot språkmängd i båda riktningarna
  • F09.12.03 Shipped

    Publish bumpar version, sätter published_at, audit-loggar publish

    ✅ PL-T216 · m-6: :publish kräver title+summary i varje deklarerad locale (annars 422 press_kit_locale_incomplete, inga spår), bumpar kit_version, stämplar published_at, skriver en ny versionsrad och dubbel audit i samma transaktion
  • F09.12.04 Shipped

    Public render-endpoint /broadcast/public/press-kits/{event_id}/{lang}

    ✅ PL-T216 · m-6 (ny adress): GET /v1/public/press-kits/{id}/{locale} — legacy-rutten är referens, inte kontrakt
  • F09.12.05 Shipped

    Embargo blockerar anonyma viewers (403) tills embargo_until passerat

    ✅ PL-T216 · m-6: 403 press_kit_embargoed med embargo_until, härlett per anrop — ingen release-tick, ingen flagga som kan glida isär från databasen
  • F09.12.06 Shipped

    Approved press-credentials med broadcaster/press outlet-typ bypassar embargo

    ✅ PL-T216 · m-6: huvudet X-Media-Credential-Token, prövat server-auktoritativt mot kortets rad; photo väntar, och ett ogiltigt/återkallat/främmande kort är identiskt med inget kort (aldrig 401)
  • F09.12.07 Shipped

    Saknad locale returnerar 404

    ✅ PL-T216 · m-6: okänd locale, ej publicerad locale, opublicerat kit och okänt id svarar samma 404
  • F09.12.08 Shipped

    Releases, approved-quotes, fact-sheets som strukturerade fält per locale

    ✅ PL-T216 · m-6: typade i internal/media (KitContent/KitQuote/KitFact) och renderade av en renderare som previewen delar
  • F09.12.09 Planned

    Samtyckesgrinden gäller genom kitet: en bilaga utan giltigt samtycke utelämnas helt och räknas i omitted_asset_count

    m-6 — härlett per anrop; en återkallelse släcker bilden i pressrummet vid nästa anrop, utan ompublicering och utan jobb
  • F09.12.10 Planned

    Preview av utkastet genom samma renderare som den publika rutten, sidoeffektsfritt

    m-6 — GET /v1/press-kits/{id}/preview/{locale}; mockupens *"Preview public render"* visar det publiken får se

Content syndication (RSS/Atom/JSON Feed)

F09.13
Planned

Per-tenant syndication-feeds för downstream-republishers. Tre format:

  • F09.13.01 Shipped

    ContentSyndication-dokument med slug, formats[], whitelist, embargo

    ✅ PL-T216 · m-8: tabellen content_syndication (unik (tenant_id, slug), den frysta formatmängden som DB-invariant, outlet_whitelist uuid[], embargo_minutes >= 0, locale_filter, rate_limit_per_hour) + GET|POST /v1/content-syndication och GET|PATCH /v1/content-syndication/{slug} med ETag/If-Match under syndication:manage; svaret bär de serverbyggda feed_urls per aktivt format (adminytan bygger aldrig adressen)
  • F09.13.02 Shipped

    RSS 2.0-renderer i application/rss+xml

    ✅ PL-T216 · m-8: GET /feeds/{tenant_slug}/{slug}.rss — välformad XML med <guid isPermaLink="false"> = itemets uuid och pubDate i RFC 1123, stark ETag + 304, byte-stabilt svar
  • F09.13.03 Shipped

    Atom 1.0-renderer i application/atom+xml

    ✅ PL-T216 · m-8: …/{slug}.atom — feed/id/title/updated och entry/id som urn:uuid:… i RFC 3339; samma items i samma ordning som de andra två formaten
  • F09.13.04 Shipped

    JSON Feed 1.1-renderer i application/feed+json

    ✅ PL-T216 · m-8: …/{slug}.json — version-URL:en 1.1, items ALLTID en lista (ett tomt dokument är ett giltigt dokument); ett format utanför feedens formats svarar 404, precis som okänt förbund/okänd feed
  • F09.13.05 Shipped

    Outlet-whitelist tomt = öppen feed; satt = anonym GET returnerar tom body

    ✅ PL-T216 · m-8 (preciserad form, A-3): satt whitelist ⇒ 200 med ett giltigt men tomt dokument; nyckeln bärs av X-Api-Key eller ?api_key= (feed-läsare kan bara URL:er), och en ogiltig, återkallad eller icke-whitelistad nyckel beter sig exakt som anonym (anti-enumeration, NFR-2)
  • F09.13.06 Shipped

    Per-outlet rate-limit OutletRateLimiter returnerar 429 + Retry-After: 60

    ✅ PL-T216 · m-8: räknaren är persistent i Postgres (syndication_fetch_counter, window_start = date_trunc('hour', now()) — huset har ingen Redis) och per (outlet, feed, timfönster): outlet B påverkas aldrig av outlet A, 304 räknas som hämtning, anonyma hämtningar räknas aldrig
  • F09.13.07 Shipped

    Embargo-minutes filtrerar bort items publicerade senaste N minuter

    ✅ PL-T216 · m-8: ett per-anrop-predikat som gäller ALLA konsumenter, även nyckelburna — ingen release-tick och ingen flagga som kan glida isär från databasen
  • F09.13.08 Shipped

    Locale-filter på item-nivå för flerspråkiga publicister

    ✅ PL-T216 · m-8: kommaseparerad BCP-47-lista med prefixmatch (sv täcker sv-SE, aldrig en); tom lista = alla språk
  • F09.13.09 Planned

    Innehållskällan: D-COMM:s publicerings-projektion syndication_item + den interna ingestion-sömmen UpsertSyndicationItem (idempotent på (tenant_id, source_domain, source_ref)), med DB-CHECK:en publication_scope in ('tenant','global') så private/orgnode inte ens KAN skrivas

    m-8 — D-CMS äger redaktionen (§5d) och skriver senare genom sömmen; inget publikt eller autentiserat skriv-API för items byggs
  • F09.13.10 Planned

    Enclosures genom m-5/m-6:s befintliga publika media-gate (RSS <enclosure> / Atom <link rel="enclosure"> / JSON Feed attachments); ett media som samtyckesgrinden blockerar utelämnas tyst

    m-8 — feeden bygger aldrig en egen utlämningsväg, och blob-referensen avslöjas aldrig
  • F09.13.11 Planned

    Adminytan: sidan /nyheter fliken Syndication — feeds-tabellen med serverns rader, formuläret där serverns 422 visas i klartext vid fältet och de serverbyggda publika adresserna per format med kopiering

    m-8 t-2 — vy-spec specs/admin/views/nyheter.md, L4-bevis tools/gate/dcomm-m8-admin-e2e.mjs (descriptor 870-dcomm-m8-admin)

Syndication outlet onboarding

F09.14
Planned

Self-service ansökningsflöde för broadcasters / sportjournalister /

  • F09.14.01 Shipped

    SyndicationOutlet-dokument med name, contact, api_key_ref (sha256), is_active

    ✅ PL-T216 · m-8 (ny form): tabellen syndication_outlet med sluten status-CHECK (applied|approved|rejected|deactivated) i stället för is_active, partiellt unikt api_key_sha256, per-outlet rate_limit_per_hour (trumfar feedens), decided_by/at, decision_note och last_active_at
  • F09.14.02 Shipped

    Public apply-endpoint /broadcast/public/syndication/outlets/apply

    ✅ PL-T216 · m-8: samma adress, oautentiserad; 202 med ett kvitto utan identifierare, och en okänd förbundskod ger exakt samma 422 validation_failed som ett trasigt fält (anti-enumeration). Dubblettansökan (case-okänslig e-post i samma förbund) ⇒ 409
  • F09.14.03 Shipped

    Approve returnerar api_key exakt en gång + sätter is_active=true

    ✅ PL-T216 · m-8: POST /v1/syndication-outlets/{id}:approve mintar plk_live_ + ≥32 tecken ur crypto/rand, lagrar bara sha256 och returnerar klartexten i det svaret — aldrig i DB, logg eller audit (auditen bär den maskerade referensen). Adminytans 🔑-panel visar den exakt en gång och persisterar den aldrig klient-side
  • F09.14.04 Shipped

    Double-approve blockeras med 409

    ✅ PL-T216 · m-8: varje FSM-övergång från fel utgångsläge är 409 (dubbel-approve, approve av avslagen, deactivate av icke-godkänd); :reject kräver dessutom decision_note (422 utan) — ett avslag utan skäl är inte ett beslut
  • F09.14.05 Shipped

    API-key-verifiering matchar sha256(api_key) mot api_key_ref

    ✅ PL-T216 · m-8: uppslaget kräver status='approved', så :deactivate släcker nyckeln omedelbart (nästa hämtning beter sig som anonym); nyckeln loggas aldrig, och last_active_at uppdateras vid nyckelburen hämtning
  • F09.14.06 Planned

    Adminytan: sidan /nyheter fliken Outlets — onboarding-tabellen med serverns maskerade api_key_reference, radåtgärder per FSM-status (Godkänn · Avslå med obligatorisk motivering · Avaktivera) och 🔑-panelen *"Ny API-nyckel — visas bara nu"*

    m-8 t-2 — deny-by-default på syndication:manage; L4-bevis tools/gate/dcomm-m8-admin-e2e.mjs (descriptor 870-dcomm-m8-admin) driver hela kedjan publik ansökan → godkännande → nyckelburen hämtning → avaktivering

Media accreditation (utbyggd)

F09.15
Planned

Per-event accrediterings-kort med QR + entry-scan-logg + intervju-

  • F09.15.01 Shipped

    MediaCredential-dokument med journalist + outlet + zones + valid_from/to

    ✅ PL-T216 · m-6: media_credential med FSM:en som guardad chokepoint i Go (409 credential_transition_invalid bär nuvarande status); denied/revoked är terminala
  • F09.15.02 Shipped

    Public apply-endpoint /broadcast/public/credentials/apply

    ✅ PL-T216 · m-6 (ny adress): POST /v1/public/media-credentials/apply med tre mjuka missbruksgrindar (dedup 10 min → honeypot → per-IP-tak 429); ansökarens token levereras exakt en gång, endast sha256 lagras
  • F09.15.03 Shipped

    Admin review-endpoint stänger till approved eller denied med reason

    ✅ PL-T216 · m-6: POST /v1/media-credentials/{id}:review (If-Match) — godkännande kräver zon och komplett valid-fönster, avslag kräver motivering; beslutsnotisen går genom notispipen med ärligt notified:false när ansökaren saknar plattformsidentitet
  • F09.15.04 Shipped

    QR-endpoint utfärdar JWT med credential_id + kid + valid_from/to claims

    ✅ PL-T216 · m-6 (ny form): husets kompakta b64url(payload).b64url(HMAC-SHA256) med nyckeln härledd ur PL_MEDIA_SIGNING_KEY (ingen ny runtime-secret, inga persondata i nyttolasten); mint är sidoeffektsfri, saknad nyckel ⇒ 503
  • F09.15.05 Shipped

    Scan-endpoint /broadcast/media/scan returnerar 7 stabila detail-strings vid 401

    ✅ PL-T216 · m-6 (ny adress): POST /v1/media/scan med den slutna utfallsmängden (allowed + sex nekanden som stabila code) — grindpersonalen måste kunna skilja "utgånget" från "återkallat" från "falskt"
  • F09.15.06 Shipped

    Revoke roterar qr_payload_kid → gamla QR fail:ar med token_revoked

    ✅ PL-T216 · m-6: :revoke roterar qr_kid (idempotent — ett dubbelklick roterar inte igen), och varje utskrivet kort dör i samma ögonblick utan att ett enda token behöver spåras
  • F09.15.07 Shipped

    Entry-scans loggas med gate, riktning, zon, scanned_by

    ✅ PL-T216 · m-6: media_entry_scan — varje försök loggas, även de nekade; ett okopplat token får credential_id = null i stället för att försvinna
  • F09.15.08 Shipped

    MediaInterviewSlot-dokument för bokningsbara mixed-zone-fönster

    ✅ PL-T216 · m-6: media_interview_slot med max_journalists och mätta booked_count
  • F09.15.09 Shipped

    Slot-booking validerar approved-status; double-book → 409; full slot → 409

    ✅ PL-T216 · m-6: kapaciteten räknas bakom select … for update (två samtidiga bokningar mot ett fönster om 1 ⇒ exakt en 201 + en 409 interview_slot_full), och dubbelbokning är en databasinvariant (unique (slot_id, credential_id))
  • F09.15.10 Shipped

    Lyckad booking länkar slot_id till credential.interview_slot_ids

    ✅ PL-T216 · m-6 (annan form, avsiktligt): bokningen är en egen rad i media_interview_booking, inte en array på kortet — avbokning blir då en statusändring, inte en listmutation
  • F09.15.11 Planned

    Press-identiteten: ett godkänt kort öppnar visibility='press' i mediabiblioteket och passerar press-kitets embargo

    m-6 — huvudet X-Media-Credential-Token; members och mjukraderade poster förblir stängda även med kort