Legger til username-kolonne i auth_identities med UNIQUE constraint.
Ved innlogging sender SvelteKit preferred_username fra Authentik til
maskinrommet POST /auth/sync, som oppdaterer kolonnen. Grunnlaget
for epost-ruting i fase 26: vegard@synops.no → username-oppslag.
Gjør synops-clip tilgjengelig i orkestreringer ved å:
1. Registrere synops-clip som cli_tool-node (migration 026) med norske
aliases (clip, klipp, hent artikkel) og args_hints for script-kompilatoren.
Orkestreringer kan nå skrive "1. clip fra event (lagre node, bruker)"
som kompileres til "synops-clip --url {event.url} --write --created-by ...".
2. Legge til clip_url som jobbtype i jobbkøen (clip.rs) — spawner
synops-clip med riktige env-variabler (DATABASE_URL, AI_GATEWAY_URL, etc).
3. Legge til POST /intentions/clip_url API-endepunkt slik at frontend
og andre klienter kan trigge URL-klipping direkte.
4. Utvide trigger-konteksten med event.url og event.created_by slik at
orkestreringer som reagerer på URL-deling kan videresende URL til
synops-clip via variabel-substitusjon.
Nytt CLI-verktøy `synops-ai` som leser cli_tool-noder fra PG, bygger
en systemprompt med tilgjengelige verktøy og script-grammatikk, og
bruker LLM til å foreslå orkestreringsscript fra fritekst-beskrivelse.
Tre moduser:
- Synkron: --description "..." → LLM genererer script → JSON output
- System prompt: --generate-system-prompt → skriver auto-generert prompt
- Eventually: --eventually → lagrer som work_item for Claude Code
Maskinrommet: nytt endepunkt POST /intentions/ai_suggest_script som
kaller synops-ai, validerer resultatet med script_compiler, og returnerer
script + kompileringsresultat til frontend.
Frontend: AI-assistent-knapp i OrchestrationTrait med fritekst-input,
generer-knapp, og feilvisning. Generert script settes direkte i editoren.
Migration: synops-ai seeded som cli_tool-node med norske verb-alias.
Ny modul script_executor.rs som tar en kompilert pipeline fra
script_compiler og kjører stegene sekvensielt:
- Substituerer {event.*}-variabler fra trigger-kontekst
- Spawner hvert CLI-verktøy som subprosess via generisk dispatch
- VED_FEIL-håndtering: steg-fallback → global fallback → stopp
- Spesialhåndtering av work_item (oppretter oppgave-node i grafen)
- Logger hvert steg i ny orchestration_log-tabell
handle_orchestrate i jobs.rs utvides: kompilerer + utfører i
samme jobb (var tidligere kun kompilering).
Migration 023: orchestration_log-tabell med indekser for
effektiv spørring per orkestrering og per jobb.
Parser menneskelig scriptspråk og kompilerer til tekniske CLI-kall.
"transkriber lydfilen (stor modell)" → "synops-transcribe --cas-hash {event.cas_hash} --model large"
Kompilatoren:
- Parser nummererte steg, ved_feil-fallbacks, og globale feilhåndterere
- Matcher verb mot cli_tool-noders aliases (case-insensitive)
- Mapper argumenter i parentes via args_hints
- Validerer variabelreferanser ({event.*}) mot kjent liste
- Fuzzy-matching med Levenshtein-avstand for forslag ved feil
- Rust-stil kompileringsrapport med ✓/✗ per linje
Integrert i jobbkøen: orchestrate-jobb kompilerer scriptet og
lagrer pipeline i metadata. Utførelse kommer i oppgave 24.5.
12 unit-tester dekker parser, kompilator, feilhåndtering og fuzzy-matching.
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.
SpacetimeDB var brukt som «instant feedback»-lag mellom portvokteren
og frontend. Nå som PG NOTIFY-triggere og WebSocket er på plass
(oppgave 22.1–22.2), er STDB-skrivestien overflødig.
Endringer:
- intentions.rs: Alle CRUD-operasjoner (create/update/delete node/edge)
skriver nå synkront til PG i stedet for STDB-først + async PG-jobbkø.
PG NOTIFY-triggere gir umiddelbar sanntidsoppdatering til klienter.
Tilgangsgivende edges (owner/admin/member_of/reader) bruker transaksjon
med recompute_access direkte i handleren.
- maintenance.rs: Fjernet StdbClient fra alle funksjoner. Varsler
opprettes/oppdateres/slettes direkte i PG.
- agent.rs, audio.rs, tts.rs, ai_process.rs: Fjernet STDB-synk etter
CLI-verktøy-kjøring. PG NOTIFY dekker sanntidsvisning.
- pg_writes.rs: Fjernet sync_node_access_to_stdb. access_changed
NOTIFY-trigger håndterer dette.
- workspace.rs: Synkrone PG-skrivinger med recompute_access.
- summarize.rs, ai_edges.rs: Fjernet StdbClient fra signaturer.
- jobs.rs: Fjernet StdbClient fra dispatch og start_worker.
- main.rs: Fjernet STDB-initialisering, warmup, stdb_monitor.
StdbClient fjernet fra AppState. stdb.rs beholdt som død kode
(fjernes i oppgave 22.4).
- health.rs: Fjernet STDB-helsesjekk fra dashboard.
- Slettet warmup.rs og stdb_monitor.rs (PG→STDB-synk ikke lenger
relevant).
- docs/retninger/datalaget.md: Markert fase M3 som fullført.
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
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.
Erstatter fire-and-forget tokio::spawn() i skrivestien med jobbkø-basert
persistering. Alle PG-skriveoperasjoner (insert/update/delete for noder
og edges) går nå gjennom den eksisterende jobbkøen som allerede har:
- Eksponentiell backoff (30s × 2^n) ved feil
- Dead letter queue (status='error' etter max_attempts=3)
- Admin-API for overvåking, manuell retry og avbryt
- Ressursstyring og prioritetsregler
Ny modul pg_writes.rs med:
- 5 enqueue-funksjoner (erstatter spawn_pg_*)
- 5 job-handlere for dispatch i worker-loopen
- Full paritet med gammel logikk: tilgangsgivende edges kjører
recompute_access i transaksjon, synker til STDB, trigger rendering
Før: PG-skrivefeil logget og glemt → data kun i STDB, tapt fra PG.
Nå: automatisk retry → admin-synlig dead letter → manuell recovery.
Legger til observerbarhetslaget i maskinrommet:
- Strukturert JSON-logging via LOG_FORMAT=json (maskinlesbart for
log-aggregering). Default er human-readable for utvikling.
- Ny metrics-modul med in-memory request latency tracking per rute
(count, avg, min, max, p50/p95/p99 fra siste 1000 forespørsler).
- Custom axum-middleware erstatter tower_http::TraceLayer — logger
method, path, status og duration_ms per request, og mater
metrikk-samleren.
- GET /metrics-endepunkt som returnerer:
- request_latency: per-rute statistikk
- queue_depth: pending/running/error/retry fra job_queue
- ai_cost: aggregert fra ai_usage_log (siste time/24h/30d)
- Default loggnivå endret fra debug til info for mindre støy.
Brukerens standard arbeidsflate med node_kind='workspace'.
Vises på /workspace når ikke koblet til en samling.
Backend:
- GET /my/workspace: finn eller opprett brukerens workspace-node
- Automatisk provisjonering ved første besøk (STDB + async PG)
- Owner-edge fra bruker til workspace
Frontend:
- /workspace rute med Canvas + BlockShell (gjenbruker spatial canvas)
- Fritt valg av verktøy-paneler fra verktøymeny
- Layout persisteres i workspace-nodens metadata via updateNode
- Tom-tilstand med verktøy-velger for nye brukere
- Responsivt: stacked tabs på mobil, spatial canvas på desktop
- Kontekst-velger i header for navigering til samlinger
Navigasjon:
- "Min flate"-knapp på mottak-siden
- "Min arbeidsflate" i ContextHeader dropdown for samlingssider
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brukere kan nå opprette egne AI-preset-noder med custom prompt,
dele dem med samlinger/team via shared_with-edges, og redigere/slette
egne presets. Modellprofil (flash/standard) er låst — kun admin
kan oppgradere fra flash til standard.
Backend:
- POST /intentions/create_ai_preset: Oppretter custom preset med
tvunget category=custom og model_profile=flash. Valgfri deling
til samling i samme kall.
- update_node: Beskytter model_profile mot endring av ikke-admin.
Forhindrer kategori-endring fra custom til standard.
Frontend:
- AiToolPanel: "+ Nytt preset"-knapp, opprett/rediger-skjema med
tittel, prompt, retning, ikon og farge. Rediger/slett/del-knapper
for egne custom presets. Egendefinerte presets markert med "egn."
- createAiPreset() i api.ts.
Dokumentasjon:
- ai_verktoy.md: Oppdatert fasestatus, ny § 10 om egendefinerte
presets med API-eksempler og modellprofil-beskyttelse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POST /intentions/ai_process med source_node_id, ai_preset_id og
direction (node_to_tool / tool_to_node).
Endepunktet validerer input, sjekker at kilde-node og AI-preset
finnes, verifiserer skrivetilgang for tool_to_node-retning, og
legger en ai_process-jobb i køen.
Jobb-handleren (ai_process.rs) henter kilde-content og preset-prompt,
mapper modellprofil → LiteLLM-alias (flash → sidelinja/rutine,
standard → sidelinja/resonering), kaller AI Gateway, og logger
forbruk i både ai_usage_log og resource_usage_log.
Direction-logikk (opprett ny node vs. oppdater eksisterende)
implementeres i oppgave 18.3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tre endringer som sammen gir brukeren innsyn i FFmpeg-feil:
1. Backend: Nytt GET /query/job_status-endepunkt i queries.rs.
Frontenden pollet allerede denne URLen, men endepunktet manglet.
Returnerer status, result og error_msg fra job_queue.
2. RenderDialog: Ny error-tilstand med formatFfmpegError() som
trekker ut lesbar feilmelding fra FFmpeg stderr-dump. Viser
kort oppsummering + ekspanderbar full feilmelding via <details>.
3. Studio-side: Sender renderError til RenderDialog som errorMessage.
Toast-varselet vises kun når dialogen er lukket (unngår duplisering).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Legger til cleanup_tmp() i CasStore som sletter orphaned .tmp-filer
eldre enn 1 time. Disse oppstår når en skriveprosess krasjer midt i
en atomisk CAS-skriveoperasjon (skriv til tmp, rename til endelig path).
Ny bakgrunnsloop start_tmp_cleanup_loop() kjører hver time og fjerner
foreldede temp-filer. Følger samme mønster som pruning- og
disk-monitor-loopene.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Legger til to nye API-endepunkter for brukersynlig ressursforbruk:
- GET /my/usage — brukerens eget forbruk (filtrert på triggered_by)
- GET /query/node_usage — forbruk for én node (kun eier/admin)
Frontend:
- /profile — profilside med grafstatistikk og forbruksoversikt
- NodeUsage-komponent integrert i samlings-detaljsiden
- Brukernavn i header lenker nå til profilsiden
Tilgangssjekk: nodeforbruk krever created_by eller owner/admin-edge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aggregert ressursforbruk-dashboard som spør mot resource_usage_log
(oppgave 15.7). Tre visninger: totaler per ressurstype, per samling,
og daglig tidsserie. AI drill-down viser forbruk per jobbtype og
modellnivå (fast/smart/deep).
Backend: GET /admin/usage med days- og collection_id-filtre.
Frontend: /admin/usage med filterbare tabeller og fargekodede kort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sentralisert logging av alle ressurskrevende operasjoner til
resource_usage_log-tabellen (opprettet i migrasjon 009).
Ny kode:
- resource_usage.rs: hjelpemodul med log() og find_collection_for_node()
- bandwidth.rs: Caddy JSON-logg-parser med nattlig batch-jobb (kl 03:00)
Logging lagt til i handlere:
- AI: summarize, ai_edges (token-telling via LiteLLM usage-felt),
agent (placeholder — claude CLI gir ikke token-info)
- Whisper: duration_seconds, model, language, mode
- TTS: refaktorert til sentralisert modul, lagt til collection_id
- CAS: logger nye filer ved upload (ikke dedup)
- LiveKit: logger join-hendelser (faktisk deltaker-minutter
krever webhook-integrasjon i fremtiden)
Caddy-config: JSON access logging aktivert for sidelinja.org og
synops.no i /srv/synops/config/caddy/Caddyfile (utenfor repo).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nytt admin-dashboard for sanntids serverhelse med fire hoveddeler:
1. Tjeneste-status: Parallelle helsesjekker for alle 7 tjenester
(PG, STDB, Caddy, Authentik, LiteLLM, Whisper, LiveKit) med
latens-måling og statusrapportering (up/down/degraded).
2. System-metrikker: CPU-load via /proc/loadavg, minne via
/proc/meminfo, disk via statvfs, oppetid via /proc/uptime.
Vises med progress-bars og fargekodede terskler.
3. PG-statistikk: Aktive tilkoblinger, maks-tilkoblinger,
databasestørrelse og aktive spørringer.
4. Logg-tilgang: Filtrerbar visning av logger fra alle tjenester.
Bruker journalctl for systemd-tjenester og docker logs for
containere. Konfigurerbart antall linjer per tjeneste.
Backend: health.rs med tokio::join! for parallelle sjekker.
Frontend: /admin/health med auto-polling hvert 10. sekund.
Backup-sjekk rapporterer ok/stale/missing (ingen backup satt opp ennå).
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>
Implementerer /admin/ai med fire faner:
- Modeller & fallback: oversikt over aliaser med fallback-kjeder,
toggle aktiv/inaktiv, legg til/fjern modeller, endre prioritet
- Ruting: jobbtype → modellalias mapping med dropdown-endring
- Forbruk: aggregert tokenforbruk per samling/alias/jobbtype
- API-nøkler: viser hvilke env-variabler som er satt (aldri verdier)
Backend (maskinrommet/src/ai_admin.rs):
- GET /admin/ai — full oversikt med aliaser, providers, ruting, forbruk
- GET /admin/ai/usage — forbruk med filtre (dager, samling)
- POST-endepunkter for CRUD på aliaser, providers og ruting-regler
PG er single source of truth. API-nøkler lagres kun som env-variabler,
admin-panelet viser bare om de er satt eller mangler.
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 koordinert nedstenging der admin setter et
vedlikeholdstidspunkt, brukere ser nedtelling, og systemet
stenger ned trygt etter at aktive jobber er ferdige.
Nye filer:
- maskinrommet/src/maintenance.rs — MaintenanceState med atomiske
flagg, shutdown-koordinator (vent på scheduled_at → blokker
nye jobber/LiveKit → vent på kjørende jobber → exit)
- frontend/src/routes/admin/+page.svelte — admin-panel for
vedlikehold med statusvisning og aktive sesjoner
Endringer:
- jobs.rs: sjekker maintenance.is_active() før dequeue
- intentions.rs: nye endepunkter (initiate/cancel/status), blokkerer
join_communication under vedlikehold
- main.rs: MaintenanceState i AppState, nye ruter
- api.ts: klientfunksjoner for maintenance-API
- adminpanelet.md: dokumenterer implementerte endepunkter
Flyt: admin → GET /admin/maintenance_status (se aktive sesjoner)
→ POST /intentions/initiate_maintenance → varsel broadcast via STDB
→ frontend nedtelling → scheduled_at nådd → active=true → jobbkø
pauset + LiveKit blokkert → vent maks 5 min → process::exit(0)
→ systemd restarter maskinrommet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implementerer systemvarsler (system_announcement-noder) som vises som
banner/toast for alle aktive klienter i sanntid via SpacetimeDB.
Backend (maskinrommet):
- POST /intentions/create_announcement: oppretter varslingsnode med
validering av announcement_type (info/warning/critical), datoer
(scheduled_at, expires_at) og blocks_new_sessions. Visibility
settes automatisk til 'open' for broadcast til alle klienter.
- POST /intentions/expire_announcement: fjerner varslingsnode med
tilgangskontroll (kun eier kan slette).
Frontend:
- SystemAnnouncements.svelte: filtrerer nodeStore for aktive
system_announcement-noder, skjuler utløpte (expires_at), viser
nedtelling for planlagte hendelser (scheduled_at). Fargekoding:
rød (critical), gul (warning), blå (info). Critical kan ikke
dismisses. Oppdaterer nedtelling hvert sekund.
- Komponent lagt til i +layout.svelte for global synlighet.
- API-funksjoner (createAnnouncement, expireAnnouncement) i api.ts.
Ref: docs/concepts/adminpanelet.md § "Systemvarsler og vedlikeholdsmodus"
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>
Publisert tittel, ingress, OG-bilde og undertittel er nå egne noder
koblet til artikler via title/subtitle/summary/og_image-edges.
Rendering bruker presentasjonselementer med fallback til artikkelfelt.
Backend:
- Ny query: GET /query/presentation_elements?article_id=...
- render_article_to_cas henter presentasjonselementer via edges
- fetch_article + fetch_index_articles bruker pres.elementer
- Batch-henting for forsideartikler (én SQL-spørring)
- ArticleData utvides med subtitle + og_image
- Alle fire temaer viser subtitle og OG-bilde
- SEO og_image-tag fylles fra presentasjonselement
Frontend:
- PresentationEditor.svelte: opprett/rediger tittel, undertittel,
ingress, OG-bilde med variantvelger (editorial/ai/social/rss)
- Integrert i PublishDialog via <details>-seksjon
- API-klient: fetchPresentationElements(), deleteNode()
Grunnlag for A/B-testing (oppgave 14.17): edge-metadata støtter
ab_status/impressions/clicks/ctr, best_of() prioriterer winner > testing.
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>
Legger til en bakgrunns-scheduler som hvert 60. sekund sjekker for
belongs_to-edges med publish_at i fortiden der artikkelen ikke er rendret.
Ved treff: oppretter render_article-jobb (og render_index for berørte
samlinger) i jobbkøen. RSS-feeden oppdateres automatisk siden den
genereres dynamisk fra DB.
Detaljer:
- find_due_articles(): SQL-spørring med deduplisering mot eksisterende jobber
- run_publish_scheduler(): orkestrerer én runde med publisering
- start_publish_scheduler(): tokio::spawn med 60s poll-intervall
- Hooks inn i main.rs ved oppstart, parallelt med jobbkø og pruning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redaktøren ser alle artikler med submitted_to-edge til samlingen,
gruppert i fire kolonner etter status. Drag-and-drop mellom kolonner
endrer status via update_edge. Siste kolonne ("Planlagt") åpner en
dialog for å sette publish_at i edge-metadata — klar for oppgave 14.12
(planlagt publisering).
Backend: GET /query/editorial_board med forfatterinfo og edge-metadata.
Frontend: /editorial/[id] med sanntidsoppdateringer via SpacetimeDB.
Lenke fra PublishingTrait når require_approval er aktivt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tre hovedkomponenter:
1. custom_domain.rs — ny modul i maskinrommet:
- GET /internal/verify-domain?domain= — Caddy on-demand TLS callback.
Returnerer 200 hvis domenet er registrert i en publishing-trait, 404 ellers.
- DNS-validering (validate_dns): sjekker at domenet peker til serverens IP
via system DNS resolver. Kalles ved oppdatering av publishing-trait.
- Domene-basert serving: /custom-domain/index, /custom-domain/{article_id},
/custom-domain/feed.xml — Caddy rewriter custom domain-forespørsler hit,
Host-header brukes til å finne samlingen.
- Re-rendering: rerender_collection_articles() enqueuer render-jobber
for alle artikler + forside når custom_domain endres.
2. Caddy on-demand TLS (Caddyfile):
- Catch-all :443-blokk med on_demand ask-callback til maskinrommet.
- Rewrite-regler: / → /custom-domain/index, /feed.xml → /custom-domain/feed.xml,
/* → /custom-domain/{uri}. Host-header bevares for domene-oppslag.
3. intentions.rs — utvidet update_node:
- DNS-validering ved setting av custom_domain i publishing-trait.
- Detekterer endring i custom_domain og trigger re-rendering av
alle artikler (canonical URL endres).
Eksisterende kode (publishing.rs, rss.rs) bruker allerede custom_domain
for base_url/canonical_url — ingen endringer nødvendig der.
Ref: docs/concepts/publisering.md § "Custom domain-mekanisme"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Personlig publiseringsflyt for samlinger med publishing-trait der
require_approval: false. Bruker kan publisere artikler fra mottak
til samlingen, og avpublisere ved å fjerne belongs_to-edge.
Backend:
- Nytt delete_edge-endepunkt i maskinrommet med tilgangskontroll
og automatisk forside-cache-invalidering ved avpublisering
Frontend:
- PublishDialog: forhåndsvisning, slug-editor, tema-info, bekreftelse
- EditorTrait: publiser/avpubliser-knapper på innholdsnoder i
publiseringssamlinger, velger for upubliserte artikler
- deleteEdge i API-klienten
Docs:
- Oppdatert api_grensesnitt.md med delete_edge, update_edge, set_slot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ny intention `POST /intentions/set_slot` for redaksjonell
kontroll over forside-slots i publiseringssamlinger.
Håndhever:
- Maks 1 hero: gammel ikke-pinned hero flyttes til strøm
- featured_max: eldste ikke-pinned featured FIFO til strøm
- pinned-flagg beskytter mot automatisk fjerning
- Krever owner/admin-tilgang til samlingen
- Trigger forside-rerendering etter slot-endring
Returnerer liste over displaced edges slik at frontend
kan vise hva som ble flyttet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
serve_index støtter nå index_mode fra publishing-trait:
- "static": render_index-jobb rendrer forsiden til CAS ved publisering,
samlingens metadata.rendered_index.index_hash peker til CAS-fil,
serveres med Cache-Control: immutable
- "dynamic" (default): in-memory cache med konfigurerbar TTL
(index_cache_ttl, default 300s), invalidert ved belongs_to-endringer
Tre separate indekserte PG-spørringer erstatter den gamle
alt-i-ett-spørringen — filtrerer på slot i edge-metadata
(hero/featured/strøm) med LIMIT, bruker GIN-indeks.
Trigger-logikk utvidet: belongs_to-edge-opprettelse legger
render_index-jobb i kø (statisk) eller invaliderer cache (dynamisk).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implementerer rendering-pipeline: metadata.document (TipTap JSON) → HTML
via Tera-templates → CAS-lagring → metadata.rendered oppdateres.
Nye moduler:
- tiptap.rs: Konverterer TipTap/ProseMirror JSON til HTML. Støtter
paragraph, heading, blockquote, lister, code_block, image, hr,
og marks (bold, italic, strike, code, link, underline).
XSS-sikker med HTML-escaping.
- render_article jobb i jobbkøen: Henter node + samling, konverterer
document → HTML, rendrer med Tera + tema, lagrer i CAS, oppdaterer
nodens metadata.rendered med html_hash og renderer_version.
Endringer:
- publishing.rs: SeoData-struct med OG-tags, canonical URL, JSON-LD.
render_article_to_cas() for full pipeline. serve_article() serverer
fra CAS (immutable cache) hvis pre-rendret, fallback til on-the-fly.
RENDERER_VERSION=1 for fremtidig bulk re-rendering.
- intentions.rs: Trigger render_article-jobb automatisk når belongs_to
edge opprettes til samling med publishing-trait.
- Alle 4 artikkel-templates: SEO-block med meta description, OG-tags
(type, title, description, url, site_name, image, published_time),
canonical URL, RSS-link, og JSON-LD structured data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Nytt endepunkt GET /pub/{slug}/feed.xml som genererer RSS 2.0 eller
Atom 1.0 feed for samlinger med rss-trait. Feeden er offentlig (ingen auth).
- Slår opp samling via publishing.slug i metadata.traits
- Henter belongs_to-edges (publiserte noder), sortert på publish_at
- Podcast-samlinger (med podcast-trait) inkluderer <enclosure>-tags
med CAS-URL, MIME-type og filstørrelse fra has_media-edges
- Støtter RSS 2.0 (default) og Atom 1.0 via rss.format config
- iTunes-namespace for podcast-feeds
- Stabile GUID-er basert på node UUID
- 5 min cache (Cache-Control: public, max-age=300)
Manuell XML-generering uten ekstra avhengigheter — enklere enn å
introdusere en RSS-crate for dette omfanget.
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>
Kobler kommunikasjonsnoder til LiveKit for sanntidslyd.
Bruker sender join_communication-intensjon, maskinrommet validerer
tilgang og returnerer signert LiveKit JWT-token + rom-URL.
Nye komponenter:
- maskinrommet/src/livekit.rs: JWT token-generering (HS256-signert
med LIVEKIT_API_SECRET, 1-times TTL, publisher/subscriber-roller)
- POST /intentions/join_communication: validerer deltaker-edge,
genererer token, oppretter rom i STDB, oppdaterer node-metadata
- POST /intentions/leave_communication: fjerner deltaker fra STDB
- POST /intentions/close_communication: stenger rom (krever owner)
- SpacetimeDB: live_room + room_participant tabeller for sanntids
deltakerliste (frontend abonnerer via WebSocket)
SpacetimeDB-modul publisert som synops-v2 (ny identitet etter
at den opprinnelige ikke lenger var tilgjengelig). .env oppdatert.
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>
Ny jobbtype `summarize_communication` som henter alle meldinger fra
en kommunikasjonsnode, sender dem til LiteLLM for oppsummering, og
oppretter en content-node med sammendraget. Sammendraget knyttes til
kommunikasjonsnoden med `belongs_to`-edge (del av samtalen) og
`summary`-edge (lett å finne sammendrag for en gitt samtale).
API-endepunkt: POST /intentions/summarize { communication_id }
Verifiserer at brukeren er deltaker i samtalen. Jobbprioritiet 3
(bakgrunn). Modell konfigurerbar via AI_SUMMARY_MODEL env-variabel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ny jobbtype `suggest_edges` som automatisk trigges ved opprettelse av
content-noder med tilstrekkelig tekst (≥20 tegn). Sender innholdet til
LiteLLM (sidelinja/rutine) via AI Gateway, parser JSON-respons med
topics og mentions, og oppretter topic-noder + mentions-edges i grafen.
Flyten:
1. create_node oppdager content-node med nok tekst → enqueue suggest_edges
2. Worker henter node-innhold og eksisterende topics fra PG
3. LLM analyserer tekst og returnerer foreslåtte topics/mentions
4. Nye topic-noder opprettes (med ai_generated-flagg i metadata)
5. mentions-edges opprettes fra innholdsnode til topic/entitet-noder
6. Deduplisering: gjenbruker eksisterende topics ved case-insensitivt match
Filer:
- maskinrommet/src/ai_edges.rs: Ny modul med LLM-kall og edge-opprettelse
- maskinrommet/src/jobs.rs: suggest_edges registrert i dispatcher
- maskinrommet/src/intentions.rs: Trigger i create_node
- docs/: Oppdatert jobbkø og AI gateway-docs med ny jobbtype
NB: Krever gyldig API-nøkkel i LiteLLM (OpenRouter/Gemini/Anthropic).
Jobben feiler gracefully med retry+backoff ved manglende nøkkel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- query_graph endpoint med rekursiv CTE-traversering (fokus + dybde)
- Filtrering på node_kind og edge_type, RLS-beskyttet
- Maks 200 noder, 1-3 hops dybde
Frontend:
- D3 force-directed graf på /graph med fargekodede noder
- Opprett topic-noder (node_kind='topic') direkte fra graf-visningen
- Opprett mentions-edges mellom vilkårlige noder
- Klikk for detaljer, dobbeltklikk for fokus, dra for å flytte
- Filter-legende for nodetyper og kanttyper
- Zoom, auto-fit, sidepanel med koblingsinfo
- Graf-knapp lagt til i mottaksflaten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implementerer kanban som noder+edges uten separate tabeller:
- Board = collection-node med metadata.board og metadata.columns
- Kort = content-noder med belongs_to-edge til board
- Status via status-edge (kort→board) med metadata.value
- Posisjon via belongs_to-edge metadata.position
Backend:
- POST /intentions/update_edge — oppdater edge-type/metadata
- GET /query/board?board_id= — hent kort med status og posisjon
Frontend:
- /board/[id] route med kolonner, drag-and-drop, kortoppretting
- Sanntid via SpacetimeDB edge-subscriptions
- Board-oppretting og navigasjon fra mottak-siden
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>
Oppgave 8.1: Alias-noder med system-edge. Nytt endepunkt
POST /intentions/create_alias oppretter en person-node og
en alias-edge (system=true) fra brukerens hovednode.
GET /query/aliases returnerer brukerens alias-noder.
Alias-edgen er usynlig for traversering via eksisterende
RLS-policy som filtrerer system-edges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>