Commit graph

8 commits

Author SHA1 Message Date
1425a82cdd Fullfører oppgave 14.17: A/B-testing for presentasjonselementer
Implementerer automatisk A/B-testing for forside-varianter:

- PG-migrasjon 012: ab_events-tabell for impression/klikk-logging
  med hour_of_week (0-167) for tidspunkt-normalisering
- Variant-rotasjon: ab_select() velger tilfeldig blant testing-varianter
  ved forside-rendering, winner prioriteres, retired filtreres bort
- Impression-logging: asynkron fire-and-forget ved forside-serve
  (både cache-hit og -miss), lagres i ab_events
- Klikk-attribusjon: artikkelbesøk sjekker forside-cache for aktive
  AB-varianter og logger klikk. Eksplisitt tracking via
  GET /pub/{slug}/t/{article_id}?v={edge_id}
- Periodisk evaluator (300s intervall): z-test for proporsjoner
  (p < 0.05), minimum 100 impressions per variant, oppdaterer
  edge-metadata (ab_status, impressions, clicks, ctr)
- Redaktør-overstyring: POST /intentions/ab_override markerer
  valgt variant som winner, andre som retired (krever owner/admin)
- Auto-initialisering: maybe_start_ab_test() setter ab_status=testing
  automatisk når >1 variant av samme type opprettes

Alle 42 tester passerer inkludert 3 nye z-test-tester.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:13:39 +00:00
4b9f520eab Tera-templates: innebygde temaer for publisering (oppgave 14.1)
Implementerer publiseringsmotoren med fire innebygde temaer:
- Avis: multi-kolonne, informasjonstung, hero+sidebar+rutenett
- Magasin: store bilder, luft, editorial, cards-layout
- Blogg: enkel, én kolonne, kronologisk liste
- Tidsskrift: akademisk, tekstdrevet, nummerert innholdsfortegnelse

Hvert tema har artikkelmal + forside-mal som Tera-templates (Jinja2-like).
CSS-variabler for theme_config-overstyring fra publishing-traiten —
fungerer meningsfullt med bare "theme": "magasin" (null konfigurasjon).

Teknisk:
- publishing.rs: Tera engine, render-funksjoner, DB-spørringer, HTTP-handlers
- Templates innebygd via include_str! (kompilert inn i binæren)
- Ruter: GET /pub/{slug} (forside), /pub/{slug}/{id} (artikkel),
  /pub/{slug}/preview/{theme} (forhåndsvisning med testdata)
- 6 enhetstester for CSS-variabler, rendering og tema-fallback

Ref: docs/concepts/publisering.md § "Temaer"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:41:54 +00:00
9768a24693 Fullfør oppgave 7.2: Transkripsjons-pipeline (CAS → Whisper → content)
Implementerer komplett pipeline for automatisk transkripsjon av lydfiler:

- PostgreSQL jobbkø (job_queue-tabell med status, retry, backoff)
- Worker-loop i maskinrommet som poller hvert 2. sekund
- Whisper-integrasjon: leser CAS-fil, sender multipart til faster-whisper API
- Postprosessering: filtrerer hallusinerte segmenter (no_speech_prob > 0.6)
- Oppdaterer media-nodens content-felt med transkripsjon og metadata
- Automatisk trigger: upload_media enqueuer jobb for audio/*-filer

Testet ende-til-ende på server: jobb plukkes opp, Whisper prosesserer,
node oppdateres. Retry med eksponentiell backoff ved feil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 17:44:54 +01:00
9566ba8dfe Fullfør oppgave 6.3: CAS-serving med GET /cas/{hash}
Nytt endepunkt streamer CAS-filer fra disk med riktig Content-Type
(oppslått fra media-nodens metadata i PG) og Cache-Control: immutable.
Hash-validering (64 hex-tegn) hindrer path traversal.
Tokio-streaming for effektiv håndtering av store filer.

Docker-compose oppdatert med CAS-volum for maskinrommet-containeren.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:58:21 +01:00
924ac1b6d0 Fullfør oppgave 6.2: Upload-endepunkt for mediefiler
POST /intentions/upload_media mottar multipart form data, lagrer filen
i CAS med SHA-256 hashing, og oppretter en media-node. Valgfri
source_id oppretter en has_media-edge fra kildenoden til media-noden.

Endepunktet følger etablert skrivestimønster: STDB først (instant),
async PG-persistering i bakgrunnen. Maks filstørrelse 100 MB.
Deduplisering via CAS — identiske filer gir ingen ekstra diskbruk.

Verifisert med curl mot produksjonsserver: upload uten og med
source_id, deduplisering, og PG-persistering fungerer korrekt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:46:54 +01:00
b7a6f8e7ef Fullfør oppgave 6.1: CAS-lagring med SHA-256 og deduplisering
Implementerer content-addressable store som Rust-modul i maskinrommet:

- CasStore med SHA-256 hashing (sha2 + hex crates)
- Katalogstruktur: {root}/{hash[0..2]}/{hash[2..4]}/{hash}
- Atomisk skriving via temp-fil + rename (hindrer korrupt read)
- Innebygd deduplisering — identisk hash = ingen skriving
- Konfigurerbar rot via CAS_ROOT env (default: /srv/synops/media/cas)
- 5 enhetstester: store, dedup, exists, ulike filer, path-struktur

Neste steg: 6.2 (upload-endepunkt) bruker CasStore for filmottak.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:31:23 +01:00
854ed27797 Auth-middleware: JWT-validering og auth_identities-oppslag (oppgave 2.2)
Legger til Authentik JWT-validering i maskinrommet:
- Henter JWKS fra Authentik ved oppstart
- Validerer RS256-signatur, issuer og utløpstid
- Slår opp sub-claim i auth_identities → node_id
- AuthUser axum-extractor for beskyttede endepunkter
- /me test-endepunkt som krever gyldig token
- /health forblir offentlig

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:26:34 +01:00
9f2667ba87 Maskinrommet Rust-skjelett med Dockerfile (oppgave 2.1)
Axum-server med health-endepunkt, PostgreSQL-tilkobling via sqlx,
strukturert logging med tracing. Flertrinns Dockerfile for produksjon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:20:39 +01:00