Validering 23.1: fase 1–2 (infra + maskinrommet) verifisert

Systematisk gjennomgang av PG-skjema, auth-middleware, intensjoner,
skrivestien og WebSocket-laget. Alle kjernetabeller matcher docs.
Auth fungerer korrekt (401 for ugyldig/manglende token). Skrivestien
er konsistent: direkte PG-skriving → NOTIFY → WebSocket.

Fikser:
- Fjern død kode: pg_writes enqueue-funksjoner (aldri kalt etter STDB-migrering)
- Fjern ubrukt truncate() i tts.rs
- Legg til #[allow(dead_code)] for sqlx-structs med ubrukte felt
- Rett feilaktig doc-påstand i api_grensesnitt.md om jobbkø
- Fjern utdatert STDB-referanse i agent_api.md
- Kompilerer uten warnings

Se logs/validering-23.1.md for fullstendig rapport.
This commit is contained in:
vegard 2026-03-18 13:58:50 +00:00
parent e0d06ddff4
commit 6bb1665b30
8 changed files with 120 additions and 134 deletions

View file

@ -112,7 +112,7 @@ med `job_type: 'spec_update'`. Oppretter `revision`-node med
forrige versjon for historikk.
#### `POST /agent/respond`
Send et svar i en samtale (erstatter dagens direkte STDB-skriving).
Send et svar i en samtale.
```json
{

View file

@ -143,7 +143,7 @@ Skriver til PG `mixer_channels`-tabell; NOTIFY-trigger propagerer til WS.
- Intensjoner fra frontend → `POST /intentions/*` endepunkter i maskinrommet.
- Tunge spørringer fra frontend → `GET /query/*` endepunkter i maskinrommet.
- Frontend (SvelteKit) har ingen direkte databasetilgang.
- Bakgrunnsjobber trigges av maskinrommet, ikke via en separat jobbkø-tabell.
- Bakgrunnsjobber trigges av maskinrommet via `job_queue`-tabellen (se `docs/infra/jobbkø.md`).
> **Historisk merknad:** Tidligere beskrev dette dokumentet en arkitektur
> der SvelteKit var web-API og Rust kun var workers. Denne ble erstattet

105
logs/validering-23.1.md Normal file
View file

@ -0,0 +1,105 @@
# Validering 23.1 — Fase 12 (infra + maskinrommet)
Dato: 2026-03-18
## PG-skjema vs. docs
### nodes-tabell
- **Match:** Alle kolonner fra `docs/primitiver/nodes.md` finnes i PG med korrekte typer og defaults.
- **Ekstra kolonner fra senere faser:** `last_accessed_at` (migrasjon 010, pruning), `search_vector` (migrasjon 011, fulltekstsøk). Disse er dokumentert i sine respektive migrasjoner.
- **Indekser:** Alle tre dokumenterte indekser (`idx_nodes_kind`, `idx_nodes_created_by`, `idx_nodes_visibility`) er på plass. I tillegg 4 ytelsesindekser fra migrasjon 017.
- **Triggere:** `nodes_notify` (PG NOTIFY), `trg_nodes_search_vector` (fulltekstsøk). Begge korrekte.
- **RLS:** `node_select`-policy korrekt — sjekker created_by, aliaser, node_access, og visibility.
### edges-tabell
- **Match:** Eksakt match med `docs/primitiver/edges.md` — alle kolonner, constraints, defaults.
- **UNIQUE constraint:** `(source_id, target_id, edge_type)` korrekt.
- **Indekser:** Alle tre dokumenterte + 3 ekstra ytelsesindekser. OK.
- **Trigger:** `edges_notify` for PG NOTIFY. Korrekt.
- **RLS:** `edge_select`-policy sjekker system-flagg, created_by, aliaser, og node_access.
### node_access-tabell
- **Match:** Eksakt match med migrasjon 001. `(subject_id, object_id)` PK, `via_edge` med CASCADE.
- **CASCADE-oppbygning:** Når en tilgangsgivende edge slettes, fjernes tilhørende `node_access`-rader automatisk via `ON DELETE CASCADE``via_edge`. Elegant og korrekt.
- **Trigger:** `node_access_notify` for sanntidsoppdatering av klientenes tilgangsmatrise.
### auth_identities-tabell
- **Match:** Eksakt match. `node_id` PK, `authentik_sub` UNIQUE, `email` UNIQUE.
- **Seed-data:** Vegard er opprettet med korrekt node_id og authentik_sub.
### Enums
- `visibility`: hidden, discoverable, readable, open — matcher docs.
- `access_level`: reader, member, admin, owner — matcher docs.
- `job_status`: pending, running, completed, error, retry — matcher jobbkø-spec.
### recompute_access-funksjon
- Korrekt implementert i migrasjon 001. Håndterer direkte tilgang, transitiv belongs_to, og team-propagering.
## Auth-middleware
- **JWKS:** Hentes fra Authentik ved oppstart. Støtter kid-matching.
- **JWT-validering:** RS256, issuer og audience valideres. Korrekt.
- **Identitetsoppslag:** `auth_identities.authentik_sub``node_id`. Fungerer.
- **401-respons:** Verifisert med curl — returnerer 401 for manglende token og ugyldig token.
- **WebSocket:** Bruker samme JWT-validering via query-parameter `?token=<JWT>`.
## Skrivestien (intensjoner)
### create_node
- Direkte PG-skriving med NOTIFY-trigger. Kontekst-arv (automatisk belongs_to-edge) fungerer.
- Alias-oppløsning for kontekstbasert identitet (fase 8.2).
- Agent-trigger og AI edge-forslag spawnes asynkront. Korrekt.
- Validering: visibility-enum, traits for samlingsnoder, AI-preset-metadata.
### create_edge
- Direkte PG-skriving. For tilgangsgivende edges (owner/admin/member_of/reader): transaksjon med recompute_access.
- Publiseringsvalidering: submitted_to og belongs_to til require_approval-samlinger.
- source_material-validering: context + excerpt påkrevd.
- A/B-test-trigger for presentasjonselement-edges.
### update_node / delete_node
- Tilgangskontroll: `user_can_modify_node` sjekker created_by, owner/admin-edge, og alias.
- Partial update: kun oppgitte felter endres, resten beholdes fra eksisterende node.
- Re-rendering trigges ved custom_domain- eller temaendring.
### update_edge / delete_edge
- Tilgangskontroll: `user_can_modify_edge` sjekker created_by, owner/admin til source-noden, og alias.
- submitted_to status-endring krever owner/admin av samlingen.
- Cache-invalidering ved belongs_to-sletting.
## WebSocket / sanntid
- PG LISTEN/NOTIFY-triggere: `node_changed`, `edge_changed`, `access_changed`, `mixer_channel_changed`.
- Berikelse: full raddata hentes fra PG etter NOTIFY (ikke bare ID).
- Tilgangsfiltrering: `should_send_to_user` sjekker `visible_nodes` (HashSet).
- Initial sync: alle noder, edges, access og mixer_channels brukeren kan se.
- Resync ved lag: sender full initial_sync.
- `visible_nodes` oppdateres dynamisk ved access_changed-events.
## Jobbkø
- PostgreSQL-basert: `SELECT ... FOR UPDATE SKIP LOCKED`.
- Retry med eksponentiell backoff (30s × 2^n).
- Ressursstyring: semaphore (3 samtidige), CPU-vekt, LiveKit-bevisst governor.
- Vedlikeholdsmodus: pauser dequeue.
- CLI-dispatch: spawner `synops-*`-verktøy for tunge jobber.
## Funn og fikser
### Fjernet død kode
1. **pg_writes.rs: enqueue_*-funksjoner** — Aldri kalt etter STDB-migreringen. Intensjonene skriver nå direkte til PG. Funksjoner fjernet, kommentar lagt til.
2. **tts.rs: truncate()** — Ubrukt hjelpefunksjon. Fjernet.
3. **ai_process.rs: SourceNodeRow**`#[allow(dead_code)]` lagt til (struct-felt brukes av sqlx men ikke lest direkte i kode).
4. **intentions.rs: FullEdgeRow**`#[allow(dead_code)]` lagt til (samme mønster).
### Oppdaterte docs
1. **docs/infra/api_grensesnitt.md** — Rettet feilaktig påstand om at bakgrunnsjobber ikke bruker jobbkø-tabell.
2. **docs/infra/agent_api.md** — Fjernet utdatert STDB-referanse.
### Kompilering
- Maskinrommet kompilerer uten warnings etter fiks.
- Maskinrommet kjører og svarer på `/health` med `{ "status": "ok" }`.
## Konklusjon
Fase 12 er solid implementert. PG-skjema matcher docs eksakt for kjernetabellene. Auth-middleware fungerer korrekt. Skrivestien er konsistent: direkte PG-skriving → NOTIFY → WebSocket. Ingen funksjonelle feil funnet. Kun død kode og småjusteringer i docs.

View file

@ -29,6 +29,7 @@ use crate::jobs::JobRow;
use crate::resource_usage;
#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct SourceNodeRow {
content: Option<String>,
title: Option<String>,

View file

@ -1388,6 +1388,7 @@ pub struct DeleteEdgeResponse {
}
#[derive(sqlx::FromRow)]
#[allow(dead_code)]
struct FullEdgeRow {
source_id: Uuid,
target_id: Uuid,

View file

@ -1,10 +1,10 @@
// pg_writes — Jobbkø-handlere for PG-skriveoperasjoner.
//
// Erstatter fire-and-forget `tokio::spawn()` med retry via jobbkøen.
// Hver skriveoperasjon (insert/update/delete for nodes og edges) er en
// egen jobbtype som behandles av den eksisterende worker-loopen med
// eksponentiell backoff (30s × 2^n) og dead letter queue (status='error'
// etter max_attempts).
// Historisk kontekst: Disse handlene ble opprettet da skrivestien gikk
// via jobbkøen (STDB → async PG-skriving). Etter STDB-migreringen (fase 22)
// skriver intensjonene direkte til PG, og NOTIFY-triggere sender
// sanntidsoppdateringer. Handlene beholdes for å prosessere eventuelle
// gjenværende jobber i køen.
//
// Jobbtyper:
// pg_insert_node, pg_insert_edge, pg_update_node,
@ -19,122 +19,11 @@ use uuid::Uuid;
use crate::jobs::JobRow;
use crate::publishing::IndexCache;
/// Prioritet for PG-skriveoperasjoner. Høy — data-konsistens er kritisk.
const PG_WRITE_PRIORITY: i16 = 8;
// =============================================================================
// Enqueue-funksjoner (erstatter spawn_pg_*)
// =============================================================================
/// Legger en insert_node-operasjon i jobbkøen.
pub fn enqueue_insert_node(
db: PgPool,
node_id: Uuid,
node_kind: String,
title: String,
content: String,
visibility: String,
metadata: serde_json::Value,
created_by: Uuid,
) {
let payload = json!({
"node_id": node_id,
"node_kind": node_kind,
"title": title,
"content": content,
"visibility": visibility,
"metadata": metadata,
"created_by": created_by,
});
tokio::spawn(async move {
if let Err(e) = crate::jobs::enqueue(&db, "pg_insert_node", payload, None, PG_WRITE_PRIORITY).await {
tracing::error!(node_id = %node_id, error = %e, "Kunne ikke legge pg_insert_node i jobbkø");
}
});
}
/// Legger en insert_edge-operasjon i jobbkøen.
pub fn enqueue_insert_edge(
db: PgPool,
edge_id: Uuid,
source_id: Uuid,
target_id: Uuid,
edge_type: String,
metadata: serde_json::Value,
system: bool,
created_by: Uuid,
) {
let payload = json!({
"edge_id": edge_id,
"source_id": source_id,
"target_id": target_id,
"edge_type": edge_type,
"metadata": metadata,
"system": system,
"created_by": created_by,
});
tokio::spawn(async move {
if let Err(e) = crate::jobs::enqueue(&db, "pg_insert_edge", payload, None, PG_WRITE_PRIORITY).await {
tracing::error!(edge_id = %edge_id, error = %e, "Kunne ikke legge pg_insert_edge i jobbkø");
}
});
}
/// Legger en update_node-operasjon i jobbkøen.
pub fn enqueue_update_node(
db: PgPool,
node_id: Uuid,
node_kind: String,
title: String,
content: String,
visibility: String,
metadata: serde_json::Value,
) {
let payload = json!({
"node_id": node_id,
"node_kind": node_kind,
"title": title,
"content": content,
"visibility": visibility,
"metadata": metadata,
});
tokio::spawn(async move {
if let Err(e) = crate::jobs::enqueue(&db, "pg_update_node", payload, None, PG_WRITE_PRIORITY).await {
tracing::error!(node_id = %node_id, error = %e, "Kunne ikke legge pg_update_node i jobbkø");
}
});
}
/// Legger en delete_node-operasjon i jobbkøen.
pub fn enqueue_delete_node(db: PgPool, node_id: Uuid) {
let payload = json!({ "node_id": node_id });
tokio::spawn(async move {
if let Err(e) = crate::jobs::enqueue(&db, "pg_delete_node", payload, None, PG_WRITE_PRIORITY).await {
tracing::error!(node_id = %node_id, error = %e, "Kunne ikke legge pg_delete_node i jobbkø");
}
});
}
/// Legger en delete_edge-operasjon i jobbkøen.
pub fn enqueue_delete_edge(
db: PgPool,
edge_id: Uuid,
source_id: Uuid,
target_id: Uuid,
edge_type: String,
) {
let payload = json!({
"edge_id": edge_id,
"source_id": source_id,
"target_id": target_id,
"edge_type": edge_type,
});
tokio::spawn(async move {
if let Err(e) = crate::jobs::enqueue(&db, "pg_delete_edge", payload, None, PG_WRITE_PRIORITY).await {
tracing::error!(edge_id = %edge_id, error = %e, "Kunne ikke legge pg_delete_edge i jobbkø");
}
});
}
// Enqueue-funksjonene (enqueue_insert_node, enqueue_insert_edge, etc.) er fjernet.
// Etter STDB-migreringen (fase 22) skriver intensjonene direkte til PG.
// NOTIFY-triggere sender sanntidsoppdateringer via WebSocket.
// Handle-funksjonene under beholdes for å prosessere eventuelle gjenværende
// jobber i køen fra den gamle arkitekturen.
// =============================================================================
// Job-handlere (kalles fra dispatch i jobs.rs)

View file

@ -135,12 +135,3 @@ async fn resolve_voice_id(
.unwrap_or_else(|_| "21m00Tcm4TlvDq8ikWAM".to_string()))
}
/// Forkorter en streng til maks `max_len` tegn med "..." suffix.
fn truncate(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
let end = s.char_indices().nth(max_len - 3).map(|(i, _)| i).unwrap_or(s.len());
format!("{}...", &s[..end])
}
}

View file

@ -295,8 +295,7 @@ verifiserer mot spec, og sjekker at ting faktisk fungerer. Ved funn:
fiks direkte hvis det er småting, eller opprett nye work_items (tasks)
med spesifikasjon for det som trenger en dedikert sesjon.
- [~] 23.1 Valider fase 12 (infra + maskinrommet): PG-skjema, indekser, auth-middleware, intensjoner, STDB-klient (nå erstattet av WS). Verifiser at skjema matcher docs, at auth fungerer, at skrivestien er konsistent.
> Påbegynt: 2026-03-18T13:50
- [x] 23.1 Valider fase 12 (infra + maskinrommet): PG-skjema, indekser, auth-middleware, intensjoner, STDB-klient (nå erstattet av WS). Verifiser at skjema matcher docs, at auth fungerer, at skrivestien er konsistent.
- [ ] 23.2 Valider fase 34 (frontend + tilgang): SvelteKit-oppsett, OIDC-flow, sanntid, mottaksflate, TipTap-editor, node_access-matrise, team-transitivitet, visibility-filtrering.
- [ ] 23.3 Valider fase 58 (kommunikasjon + CAS + lyd + aliaser): chat-loop, kontekst-arv, CAS-hashing/deduplisering, Whisper-pipeline, segmenttabell, SRT-eksport, alias-identitet.
- [ ] 23.4 Valider fase 910 (visninger + AI): kanban drag-and-drop, kalender, dagbok, kunnskapsgraf, LiteLLM-ruting, AI-foreslåtte edges, oppsummering, TTS.