Commit graph

22 commits

Author SHA1 Message Date
26f03ef21d Trigger-evaluering i portvokteren (oppgave 24.2)
Ved node/edge-events fra PG LISTEN/NOTIFY evaluerer portvokteren nå
om noen orchestration-noder matcher triggeren. Implementert som non-blocking
async task som ikke blokkerer WebSocket-flyten.

Ny modul orchestration_trigger.rs:
- Mapper NOTIFY-events til trigger-typer (node.created, edge.created)
- Effektiv lookup via funksjonell B-tree-indeks på metadata->trigger->event
- Evaluerer observes-edges (eksplisitt) vs conditions (implisitt)
- Betingelser: node_kind, edge_type, has_trait, has_tag (AND-logikk)
- Legger matchende orkestreringer i jobbkøen som "orchestrate"-jobb

Ny migration 021: indeks for trigger-event lookup på orchestration-noder.
Jobbkø-dispatcher håndterer "orchestrate" med placeholder (24.3 implementerer utførelse).

Verifisert: content-node trigrer matching orchestration, communication-node hoppes over.
2026-03-18 16:53:59 +00:00
5dfaeff53c Valider fase 3–4: fiks belongs_to-tilgangspropagering og mottakssortering
Validering av fase 3 (frontend) og fase 4 (tilgangskontroll) avdekket to bugs:

1. belongs_to-access-gap: Når en belongs_to-edge opprettes ETTER at
   noen allerede har tilgang til foreldrenoden, fikk ikke barnenoden
   tilgangsoppføringer i node_access-matrisen. F.eks. kunne Vegard (eier
   av en kommunikasjonsnode) ikke se innhold opprettet av Claude med
   belongs_to-edge til den noden.

   Løsning: Ny PG-funksjon propagate_belongs_to_access() som kopierer
   forelderens tilgangsrader til barnet. Kalles fra maskinrommet ved
   opprettelse av belongs_to-edges (create_node m/context, create_edge,
   create_communication m/context). Retroaktiv fiks for eksisterende data.

2. Mottaksflate-sortering: Brukte .microsSinceUnixEpoch (SpacetimeDB-
   BigInt-arv) på vanlig number-felt, ga alltid 0n → ingen sortering.
   Fikset til direkte number-sammenligning.

Verifisert: SvelteKit + maskinrommet bygger og kjører. PG-skjema, OIDC,
WebSocket/NOTIFY, RLS-policies, team-transitivitet og visibility fungerer.
2026-03-18 14:41:20 +00:00
fcc9e671a5 Backend for SpacetimeDB-migrering: berikede WS-events + mixer-API
Fase M2 (oppgave 22.2): Portvokteren sender nå full raddata i
WebSocket-events (ikke bare ID), slik at frontend kan oppdatere
stores direkte uten ekstra API-kall.

Endringer:
- ws.rs: Berik node/edge/access-events med full PG-data etter NOTIFY
- ws.rs: Ny mixer_channel_changed event-type + initial_sync inkluderer mixer
- ws.rs: Resync ved lag (broadcast overflow)
- mixer.rs: Nye HTTP-endepunkter som erstatter STDB-reducers
  (create_mixer_channel, set_gain, set_mute, toggle_effect, set_mixer_role)
- 019_mixer_channels.sql: PG-tabell + NOTIFY-trigger for mixer-tilstand
2026-03-18 12:16:36 +00:00
0ecb7104c0 WebSocket-lag i portvokteren: PG LISTEN/NOTIFY + WS-endepunkt (oppgave 22.1)
Implementerer Fase M1 av SpacetimeDB-migrasjonen:

- SQL-migrasjon 018: Triggers for notify_node_change, notify_edge_change
  og notify_access_change på nodes, edges og node_access-tabellene
- Ny ws.rs-modul i maskinrommet med:
  - PG LISTEN bakgrunnsoppgave som lytter på tre kanaler
  - Broadcast-kanal for å videresende events til alle WS-klienter
  - WebSocket-endepunkt (/ws) med JWT-autentisering
  - Initiell snapshot (initial_sync) ved tilkobling
  - Tilgangskontrollfiltrering per klient via node_access-matrisen
- Oppdatert AppState med WsBroadcast og /ws-rute

Frontend dual-tilkobling (STDB + nytt WS) kommer i neste commit.
2026-03-18 11:54:34 +00:00
b31ee59868 Ytelse: profiler PG-spørringer, optimaliser node_access-oppdatering (oppgave 12.4)
Profilert alle kritiske PG-spørringer med EXPLAIN ANALYZE.
Identifiserte at recompute_access brukte single-column index
(idx_edges_type) med lav selektivitet, og RLS-policyer manglet
composite indexes for effektive oppslag.

Endringer:

Migrasjon 017_query_performance.sql:
- 6 nye composite indexes:
  - idx_edges_target_type (target_id, edge_type) — recompute_access + belongs_to
  - idx_edges_source_type (source_id, edge_type) — alias-oppslag
  - idx_edges_target_memberof (partial, member_of) — team-propagering
  - idx_nodes_created_at_desc — ORDER BY created_at DESC
  - idx_nodes_kind_created — filtrer på kind + sorter
  - idx_na_subject_covering INCLUDE (object_id) — RLS without heap lookup
- Optimalisert recompute_access(): steg 3 og 4 kjøres nå bare
  når det er relevant (EXISTS-sjekk først). For vanlige brukere
  (ikke team) unngår dette to fulle INSERT-SELECT-operasjoner.
- via_edge oppdateres nå korrekt ved access-nivå-endring.

Slow query logging (maskinrommet):
- Forespørsler >200ms logges som WARN med tag slow_request
- PG-spørringer >100ms logges som WARN med tag slow_query
- recompute_access-kall logges med varighet for overvåking
- Nytt pg_stats-felt i /metrics med tabell- og index-statistikk,
  cache hit ratio, og node_access-telling

Dokumentasjon oppdatert i docs/infra/observerbarhet.md.
2026-03-18 11:43:19 +00:00
8bf82a78d9 Implementer message_placements (oppgave 20.1)
Plasseringsrelasjon som sporer hvor meldinger vises på tvers av
kontekster (chat, kanban, storyboard, kalender, notes). Grunnmuren
for universell overføring mellom verktøy-paneler.

Tre deler:
- PG-migrasjon 016: message_placements tabell med UNIQUE constraint
  og indekser for kontekst- og meldingsoppslag
- SpacetimeDB: MessagePlacement tabell + place_message, remove_placement,
  move_on_canvas reducers for sanntids UI-oppdatering
- Maskinrommet: STDB-klientmetoder for de tre reducerne

Avvik fra spec: FK refererer nodes(id) i stedet for messages(id) siden
meldinger er noder (node_kind = 'melding'). Spec oppdatert tilsvarende.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:59:07 +00:00
cad3f4b699 Fullfører oppgave 18.1: AI-preset node-type
Implementerer node_kind 'ai_preset' med metadata-validering og
8 standardprompter som seed-data.

Validering i maskinrommet (create_node + update_node):
- prompt (påkrevd, ikke-tom streng)
- model_profile (flash | standard)
- category (standard | custom)
- default_direction (tool_to_node | node_to_tool | both)
- icon (påkrevd, ikke-tom streng)
- color (påkrevd, hex-farge #RRGGBB)

Seed-presets: Rens tekst, Korrektør, Oppsummering, Oversett,
Skriv om for publisering, Trekk ut fakta, Forenkle, Endre tone.

8 enhetstester for valideringsfunksjonen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 06:13:09 +00:00
b64f217637 Fullfører oppgave 15.5: Ressursstyring for jobbkø
Implementerer prioritetsregler, ressursgrenser og LiveKit-bevisst
resource governor for jobbkø-workeren, pluss disk-overvåking med varsling.

Hovedkomponenter:

1. Prioritetsregler (job_priority_rules-tabell):
   - Konfigurerbar base_priority, cpu_weight, max_concurrent per jobbtype
   - LiveKit-justering: livekit_priority_adj og block_during_livekit
   - Timeout per jobbtype
   - Admin-API for å endre regler uten restart

2. Ressurs-governor i worker-loopen:
   - Semaphore: maks 3 samtidige jobber
   - CPU-vektgrense: total vekt maks 8 (Whisper=5, render=1, etc.)
   - Per-type concurrency-grense
   - LiveKit-status sjekkes med 10s cache-TTL
   - Jobber utsettes/nedprioriteres ved aktive LiveKit-rom
   - Individuell timeout per jobb (default 600s)
   - Jobber kjøres i egne tokio-tasks (parallell dispatch)

3. Disk-overvåking:
   - Sjekker diskbruk hvert 60. sekund via statvfs
   - Terskler: 85% warning, 90% critical, 95% emergency
   - Logger til disk_status_log (siste 1000 målinger beholdes)
   - Admin-API: GET /admin/resources/disk med historikk

4. Admin-API:
   - GET /admin/resources — samlet ressursstatus
   - GET /admin/resources/disk — diskstatus med historikk
   - POST /admin/resources/update_rule — oppdater prioritetsregel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 04:02:11 +00:00
07fa10620b Fullfører oppgave 15.3: Jobbkø-oversikt med admin-UI
Admin kan nå se, filtrere, retrye og avbryte jobber via /admin/jobs.

Backend:
- jobs.rs: list_jobs(), count_by_status(), distinct_job_types(),
  retry_job(), cancel_job() for admin-spørringer
- intentions.rs: GET /admin/jobs, POST retry_job/cancel_job handlers
- main.rs: tre nye ruter

Frontend:
- /admin/jobs: statusoppsummering med antall per status, filter på
  type/status, paginert tabell med retry/avbryt-knapper, 5s polling
- /admin: navigasjonslenke til jobbkø

Migrasjon:
- 013_job_queue.sql: formaliserer job_queue-tabellen med admin-indekser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 03:40:56 +00:00
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
26c6a3b8d9 Dynamiske sider (oppgave 14.15): kategori, arkiv, søk, om-side
Implementerer fire nye dynamiske sidetyper for publiseringssamlinger:

- Kategori-sider: filtrert på tag-edges, paginert med cache
- Arkiv: kronologisk med månedsgruppering, paginert med cache
- Søk: PG fulltekst med tsvector/ts_rank, paginert med cache
- Om-side: statisk CAS-node (page_role: "about"), immutable cache

Teknisk:
- Ny migrasjon (011): tsvector-kolonne + GIN-indeks + trigger for søk
- Nye Tera-templates: category.html, archive.html, search.html, about.html
- DynamicPageCache for in-memory caching av dynamiske sider
- Ruter for /pub/{slug}/kategori/{tag}, arkiv, sok, om
- Custom domain-varianter for alle nye sidetyper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 02:39:06 +00:00
6d916d9860 Pruning-logikk: TTL per modalitet, signaler, disk-nødventil (oppgave 11.3)
Implementerer automatisk opprydding av CAS-filer basert på dokumentert
spec i docs/retninger/maskinrommet.md:

- TTL per modalitet: lyd 30d, bilde 30d, video 14d, tekst aldri
- Signaler som forlenger levetid: publishing-edge, siste tilgang
  (last_accessed_at), utranskribert lyd beholdes
- Tre-trinns disk-nødventil:
  - >85%: slett generert innhold (TTS osv, kan regenereres)
  - >90%: aggressiv pruning med kraftig redusert TTL
  - >95%: kritisk — alt uten publishing-edge slettes
- Periodisk bakgrunnsloop: hvert 6. time, oftere ved høy disk
- Tilgangslogging: serving oppdaterer last_accessed_at (fire-and-forget)
- Pruning-hendelser logges til resource_usage_log

Ny modul: maskinrommet/src/pruning.rs
Ny migrasjon: 010_pruning.sql (last_accessed_at kolonne + indeks)
CasStore utvidet med delete(), disk_usage_bytes(), disk_usage_percent()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:02:27 +00:00
e95b7d6663 TTS-pipeline: tekst → lyd via ElevenLabs (oppgave 10.4)
Ny jobbtype `tts_generate` som kaller ElevenLabs text-to-speech API,
lagrer MP3-lyd i CAS, og oppretter media-node med has_media-edge.

Voice-preferanse løses i tre lag: eksplisitt i payload → nodens
metadata.voice_preference → ELEVENLABS_DEFAULT_VOICE env.
Dette er "mottaker-preferanse i metadata" — en node kan sette
voice_preference i sin metadata for å styre hvilken stemme som brukes.

Ny migrasjon 009: resource_usage_log-tabell for sporing av
ressursforbruk (TTS, AI, Whisper, CAS). Ref: docs/features/ressursforbruk.md

Endringer:
- maskinrommet/src/tts.rs: TTS-handler med ElevenLabs-integrasjon
- maskinrommet/src/intentions.rs: POST /intentions/generate_tts
- maskinrommet/src/jobs.rs: Dispatcher for tts_generate
- migrations/009_resource_usage_and_tts.sql: resource_usage_log
- scripts/maskinrommet-env.sh: ELEVENLABS_* env-variabler

Krever: ELEVENLABS_API_KEY i /srv/synops/.env (placeholder lagt til)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:40:46 +00:00
01ad35557f LiteLLM AI Gateway: Docker, DB-ruting, config-generering (oppgave 10.1)
Setter opp AI Gateway med LiteLLM som sentralisert proxy for alle
AI-kall. PG eier all modellkonfigurasjon — LiteLLM er stateløs.

- Migrasjon 008: ai_model_aliases, ai_model_providers, ai_job_routing
  med seed-data for sidelinja/rutine og sidelinja/resonering
- Config-generering fra PG: scripts/generate-litellm-config.sh
  filtrerer bort providers med tomme API-nøkler
- Docker-container kjører på sidelinja-net (intern, ingen eksponert port)
- Maskinrommet har AI_GATEWAY_URL via maskinrommet-env.sh
- API-nøkkel-placeholders i .env (GEMINI, ANTHROPIC, XAI)
- Oppdatert docs/infra/ai_gateway.md med faktisk config

Verifisert: container healthy, modellaliaser eksponert, maskinrommet
har korrekt gateway-URL. Reelle API-kall krever at Vegard fyller
inn leverandør-nøkler i /srv/synops/.env.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:12:46 +00:00
33a1b44946 Implementer Claude som chat-deltaker (Fase A: MVP)
Claude er nå en agent-node i grafen som kan delta i samtaler.
Når en bruker sender melding i en kommunikasjonsnode der Claude
er deltaker, enqueues en agent_respond-jobb som kaller claude CLI
direkte og skriver svaret tilbake til chatten.

Nye filer:
- migrations/007_agent_system.sql: agent_identities, agent_permissions, ai_usage_log
- maskinrommet/src/agent.rs: agent_respond job handler
- scripts/maskinrommet.service: systemd-tjeneste for native kjøring
- scripts/maskinrommet-env.sh: genererer env med Docker container-IPs

Endringer:
- intentions.rs: trigger agent_respond ved melding i agent-chat
- jobs.rs: dispatch agent_respond til agent-handler
- frontend chat: bot-badge (🤖) og amber-farge på agent-meldinger
- LiteLLM config: resonering-modellalias via OpenRouter

Maskinrommet kjører nå direkte på hosten (ikke i Docker) for å
ha tilgang til claude CLI. Caddy peker til host.docker.internal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 19:20:17 +00:00
f81c8a96e0 Fullfør oppgave 8.2: Kontekstbasert identitet med alias
Når en bruker oppretter en node i en kommunikasjonskontekst der
brukerens alias er deltaker (owner/member_of/host_of), settes
created_by til alias-noden i stedet for brukerens hovednode.

Endringer:
- resolve_context_identity(): slår opp brukerens alias i konteksten
- create_node(): bruker alias som created_by når context_id er satt
- user_can_modify_node/edge(): gjenkjenner alias-eierskap ved endring/sletting
- 006_alias_aware_rls.sql: RLS-policies inkluderer alias-opprettede noder
- current_node_alias_ids(): PG-funksjon for alias-oppslag i RLS

Verifisert med integrasjonstest på server:
- Node i alias-kontekst → created_by = alias ✓
- Node uten kontekst → created_by = bruker ✓
- Update/delete av alias-node fungerer for hovedbruker ✓

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 19:19:36 +01:00
af14edd671 Legg til transcription_segments-migrasjon (oppgave 7.5)
Oppretter tabellen som lagrer transkripsjons-segmenter med tidsstempler.
Master-kopi for alle transkripsjoner — SRT og ren tekst er avledede formater.
Inkluderer GIN-indeks for norsk fulltekstsøk og unik constraint per kjøring.

Ref: docs/concepts/podcastfabrikken.md § 3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 18:15:01 +01:00
1355d189b2 Fullfør oppgave 4.4: RLS-policies på PG med node_access-filtrering
Implementerer Row Level Security for tunge PostgreSQL-spørringer.
Maskinrommet skriver som superuser (sidelinja), men leser med
SET LOCAL ROLE synops_reader som er underlagt RLS-policies.

Endringer:
- Migration 004: synops_reader rolle, current_node_id() funksjon,
  RLS-policies på nodes (created_by/node_access/visibility),
  edges (endepunkt-tilgang + system-edge-skjuling),
  og node_access (kun egne rader)
- queries.rs: RLS-kontekst-helper (set_rls_context) og
  GET /query/nodes endepunkt med søk, filtrering og paginering
- migration_safety.md: omskrevet fra v1 workspace-RLS til
  node_access-basert RLS med oppdaterte leak hunter-tester

Verifisert på server: hidden noder filtrert for ukjente brukere,
synlige for eiere. Edges filtrert tilsvarende.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:30:29 +01:00
e0f30bba27 Fullfør oppgave 4.2: team-transitivitet i recompute_access
Legger til steg 4 i recompute_access: når en bruker melder seg inn i
et team (member_of-edge), arver brukeren all tilgang teamet allerede
har. Tidligere håndterte funksjonen kun retningen "team får ny tilgang
→ propager til eksisterende medlemmer" (steg 3). Nå håndteres begge
retninger:

- Steg 3: Team får tilgang → alle eksisterende medlemmer arver
- Steg 4: Ny bruker melder seg inn → arver teamets eksisterende tilgang

Testet med scenario: Trond → Podcastteamet → Sidelinja → Episode 42.
Trond arver member-tilgang til alle tre noder via team-transitivitet.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:01:51 +01:00
8428fa45a0 Auth-middleware verifisert og fullført (oppgave 2.2)
- Fikser audience-validering (AUTHENTIK_CLIENT_ID som forventet aud)
- Oppdaterer seed-data med reell Authentik sub for Vegard
- Fikser DATABASE_URL i .env: peker nå til synops-database (ikke sidelinja)
- Dokumenterer maskinrommet-miljøvariabler i produksjon.md
- Markerer oppgave 2.2 som ferdig i tasks.md

Verifisert på server med fem testcaser:
1. /health (public) → 200
2. /me uten token → 401 "Mangler Authorization-header"
3. /me med ugyldig token → 401 "Ugyldig token"
4. /me med gyldig JWT, ukjent sub → 401 "Ukjent brukeridentitet"
5. /me med gyldig JWT, kjent sub → 200 med node_id

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:33:24 +01:00
89f1db63d2 Seed-data: Vegard brukernode, Sidelinja samling, owner-edge (oppgave 1.2)
Oppretter kjernedataene som resten av systemet bygger på:
- Vegards person-node med auth_identity (placeholder authentik_sub,
  oppdateres i oppgave 1.5 når Authentik konfigureres)
- Sidelinja samlings-node (tenant for podcastredaksjonen)
- Owner-edge fra Vegard til Sidelinja
- node_access-rad via recompute_access()

Kjørt og verifisert på server (sidelinja-postgres-1, synops-db).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:56:09 +01:00
a30a484076 Opprett PostgreSQL-skjema for Synops (oppgave 1.1)
Oppretter database `synops` på serveren med kjerneskjemaet:
- Enums: visibility (hidden/discoverable/readable/open),
  access_level (reader/member/admin/owner)
- Tabeller: nodes, edges, node_access, auth_identities
- Funksjon: recompute_access for tilgangsmatrise-oppdatering
- Indekser iht. docs/primitiver/nodes.md og edges.md

Migrasjonen er kjørt og verifisert på produksjonsserver
(sidelinja-postgres-1, database: synops).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:53:31 +01:00