# Maskinrommet

**Status: Besluttet.**

> Én Rust-tjeneste med et fast grensesnitt. Alt som krever tunge
> ressurser eller eksterne tjenester går gjennom dette laget.
> Fang, prosesser, lever. Maskinrommet er det eneste som skriver
> noder og edges.

## Tre operasjoner

### 1. Fang (input-absorpsjon)
Ta imot råmateriale i alle modaliteter:
- Tekst (melding, URL, dokument)
- Lyd (voice memo, live stream, filopplasting)
- Bilde (foto, skjermbilde, tegning)
- Video (stream, opptak)
- Strukturert data (JSON, metadata, edges)

### 2. Prosesser (transformasjon)
Analyser, transformer, berik og systematiser:
- **STT** — lyd → tekst (Whisper)
- **TTS** — tekst → lyd (ElevenLabs / lokal modell)
- **AI-analyse** — oppsummering, klassifisering, edge-forslag
- **Beriking** — URL → metadata, bilde → beskrivelse
- **Søk** — fulltekst, semantisk (pgvector), graftraversering
- **Mediaprosessering** — transcode, thumbnail, waveform

### 3. Lever (output-distribusjon)
Lever resultat i riktig modalitet til riktig mottaker:
- Tekst (melding, notifikasjon, digest)
- Lyd (TTS-opplesning, lydstream)
- Video/bilde (stream, thumbnail)
- Strukturert data (noder, edges tilbake i grafen)
- Push (WebSocket via PG LISTEN/NOTIFY)

## Maskinrommet eier all skriving

Frontend sender intensjoner. Maskinrommet utfører.

```
Frontend: "legg til Trond i møtet"
  → Maskinrommet validerer
  → Skriver edge til PG
  → Oppdaterer tilgangsmatrise
  → PG NOTIFY → WebSocket → frontend oppdateres i sanntid
  → Reagerer på konsekvensene (koble inn LiveKit, starte transkripsjon)
```

Alt i én operasjon. Maskinrommet er ikke reaktivt i en pub/sub-
forstand — det orkestrerer hele sekvensen. Enklere å forstå,
enklere å debugge.

Skrivestien: validering → PG → NOTIFY → WebSocket (sanntid).

## Edge-drevet ressursorkestrering

Maskinrommet leser edges for å vite hva det skal gjøre. Noden
er alltid enkel. Edges bestemmer hvilke ressurser som spinnes opp.

### Privat er default
Input uten mottaker-edge er automatisk privat. Ingen ressurser
kobles inn utover det grunnleggende (fang + transkriber).

### Ressurser er proporsjonale med edges

```
Dagboknotat (privat voice memo):
  node → fang lyd → transkriber (Whisper) → lagre
  Ressurser: minimal

Samtale med Trond:
  node + mottaker-edge(Trond)
  → fang lyd → transkriber → lever tekst/lyd til Trond
  Ressurser: STT + levering til én

Redaksjonsmøte (5 deltakere):
  node + mottaker-edges(5) + rolle-edges
  → fang lyd fra alle → transkriber → lever til alle → AI-referent
  Ressurser: STT + levering til 5 + LLM

Livesending (1000 lyttere):
  node + mottaker-edges(∞) + stream-edge
  → fang lyd → transkriber → stream via LiveKit → distribuer
  → generer segmenter → kjør live AI → publiser
  Ressurser: STT + LiveKit + LLM + mediaprosessering
```

### Naturlig eskalering
Du starter en privat voice-note. Deler den med Trond → legg til
edge, maskinrommet begynner å levere. Trond foreslår møte → flere
edges, maskinrommet kobler inn sanntidsstrømming. Møtet blir
innspilling → publiserings-edge, maskinrommet aktiverer
produksjonspipeline. Hvert steg er bare å legge til edges.

## Grensesnittet

```
fang(input: RåInput) → NodeId
prosesser(node: NodeId, operasjon: Operasjon) → Resultat
lever(node: NodeId, mottaker: Mottaker, format: Format) → Status
```

I praksis er mye av dette implisitt: maskinrommet ser hvilke edges
som ble skrevet og handler deretter. Primitivene manipulerer edges
via maskinrommet, og maskinrommet kobler inn riktige ressurser.

## CAS og intelligent pruning

### CAS som lagringsprimitiv
All binærdata (lyd, bilde, video) lagres content-addressable.
CAS-noder i grafen bærer metadata (`cas_hash`, `mime`, `size`).
Selve biten lever på disk.

- **Deduplisering gratis** — samme fil delt i tre kontekster = én kopi
- **Separasjon** — "innholdet eksisterer" er adskilt fra "innholdet
  er tilgjengelig"
- **Enkel opprydning** — slett filen fra CAS, noden beholder metadata

### Lagringsregler per modalitet

| Modalitet | Default levetid | Begrunnelse |
|-----------|----------------|-------------|
| Tekst | Evig | Billig, essensen av innholdet |
| Transkripsjon | Evig | Tekstlig representasjon — bevarer meningen |
| Lyd | 30 dager | Transkripsjon bevarer innholdet |
| Bilde | 30 dager | Beskrivelse/metadata bevarer kontekst |
| Video | 14 dager | Dyrest, transkripsjon + thumbnail bevarer det meste |

### Signaler som forlenger levetid

- **Edges.** Lydfil med publiserings-edge = beholdes. Privat
  voice-memo uten edges = 30-dagers TTL.
- **Aksesslog.** Avspilt i løpet av TTL-perioden = forlenges.
- **Transkripsjonsstatus.** Utranskribert lyd kan trenge lengre TTL.
- **Edge-type.** Publisert = behold. Arkivert møte = transkripsjon
  holder.

### Generert innhold er en cache
TTS, thumbnails, AI-oppsummeringer, waveforms — alt som kan
regenereres er en cache i CAS med samme TTL-mekanisme.

### Samlings-node-styrt aggressivitet
Hver samlings-node kan justere sin pruning-profil:
- **Konservativt** — behold alt lenge (arkiv-node)
- **Aggressivt** — tekst bevares, binærdata prunes raskt
- **Tilpasset** — egne regler per modalitet og edge-type

### Disk-nødventil
Maskinrommet overvåker diskbruk:
- **>85 %:** Genererte filer slettes (kan regenereres)
- **>90 %:** Aggressiv pruning for alle samlings-noder
- **>95 %:** Kritisk alarm. Alt uten publiserings-edge slettes.
  Tekst og transkripsjoner bevares alltid.

## Isolasjon og observerbarhet

### Isolasjon
Bytt Whisper med noe annet? Endre maskinrommet. Frontend vet
ingenting. Legg til bildegenerering? Ny operasjon. Primitivene
kaller den uten å vite hva som skjer under.

### Observerbarhet
Alt går gjennom ett punkt. Logging, metrikker, kostnadsrapportering
— alt på ett sted. "Hva bruker vi AI-ressurser på?" har ett svar.

### Kapasitetsstyring
Prioritering, kø, rate limiting, fallback mellom leverandører.
Live transkripsjon prioriteres over bakgrunns-oppsummering.

## Compute-separasjon

Maskinrommet orkestrerer — tunge jobber trenger ikke kjøre på
samme maskin.

- **Nå:** Alt på én VPS. Jobbkøen prioriterer sanntid over batch.
- **Snart:** Trekk ut tunge workers til separat node (billig
  ARM-instans) som poller jobbkøen. Maskinrommet ruter transparent.
- **Kildevern-modus:** Lokal LLM krever dedikert compute med
  fysisk isolasjon.

Compute-separasjon er en konfigurasjon, ikke en arkitekturendring.

## Evolusjon: Maskinrommet → Portvokteren

Maskinrommet ble bygget som en monolitt — auth, validering,
prosessering, jobbkø i én binær. Med unix-filosofi-
retningen (se `docs/retninger/unix_filosofi.md`) flyttes all
prosessering til CLI-verktøy. Det som blir igjen er:

1. **Auth** — JWT-validering, "hvem er denne requesten?"
2. **HTTP-ruting** — frontend → riktig CLI-verktøy
3. **Sanntid** — PG LISTEN/NOTIFY → WebSocket til frontend
4. **Jobbkø-dispatch** — poll PG, spawn CLI-verktøy

Dette er en **portvokter**, ikke et maskinrom. Når uttynningen er
ferdig, renames `maskinrommet/` til `portvokteren/` og systemd-
tjenesten oppdateres. Navnet skal reflektere rollen: vokter porten,
gjør ikke jobben.

**Hva dette gir:**
- Portvokteren dør → frontend stopper, men CLI-verktøy fungerer.
  Claude jobber videre, scripts kjører, terminalen funker.
- Et CLI-verktøy dør → bare den funksjonen stopper. Alt annet
  er upåvirket.
- Portvokteren blir så enkel at den nesten aldri feiler.

**Edge-validering og tilgangskontroll** flyttes til `synops-common`
(delt lib) — brukes av både portvokteren og CLI-verktøy.

## Forhold til andre retninger

Maskinrommet er infrastrukturen *under* de tre primitivene i
[universell input og mottak](universell_input.md):
- Input-primitiven → `fang()` + `prosesser()`
- Mottak-primitiven → `lever()`
- Kommunikasjonsnoden → alle tre

- [Noder er sentrum](bruker_ikke_workspace.md) — maskinrommet
  eier tilgangsmatrise-oppdatering
- [Datalaget](datalaget.md) — maskinrommet skriver PG,
  sanntid via LISTEN/NOTIFY + WebSocket
