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.
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>