From b55705d12c6588d145f102f8815c5152385d020e Mon Sep 17 00:00:00 2001 From: vegard Date: Tue, 17 Mar 2026 15:57:25 +0100 Subject: [PATCH] Kontekst-arv: automatisk belongs_to-edge ved input i kommunikasjonsnode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Legger til context_id-parameter på create_node-intensjonen. Når context_id er satt (og peker på en kommunikasjonsnode), opprettes automatisk en belongs_to-edge fra den nye noden til kontekstnoden. Dette er kjernen i kontekst-arv: si noe i et møte → noden tilhører møtet automatisk. Backend: Validerer at context_id eksisterer og er communication-node, oppretter belongs_to-edge i STDB+PG etter node-opprettelse. Frontend: Oppdaterer API-typer med context_id og belongs_to_edge_id. Ref: docs/retninger/universell_input.md (kontekst arves automatisk) Co-Authored-By: Claude Opus 4.6 --- frontend/src/lib/api.ts | 4 ++ maskinrommet/src/intentions.rs | 92 +++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9698a3d..bebceba 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -12,10 +12,14 @@ export interface CreateNodeRequest { content?: string; visibility?: string; metadata?: Record; + /** Kontekst-node (kommunikasjonsnode). Gir automatisk belongs_to-edge. */ + context_id?: string; } export interface CreateNodeResponse { node_id: string; + /** Edge-ID for automatisk belongs_to-edge (kun ved context_id). */ + belongs_to_edge_id?: string; } export interface CreateEdgeRequest { diff --git a/maskinrommet/src/intentions.rs b/maskinrommet/src/intentions.rs index 123792f..fdd3d90 100644 --- a/maskinrommet/src/intentions.rs +++ b/maskinrommet/src/intentions.rs @@ -136,17 +136,28 @@ pub struct CreateNodeRequest { pub visibility: Option, /// Typespesifikk metadata (JSON-objekt). pub metadata: Option, + /// Kontekst-node (f.eks. kommunikasjonsnode). Hvis satt, opprettes + /// automatisk en `belongs_to`-edge fra den nye noden til kontekstnoden. + /// Ref: docs/retninger/universell_input.md (kontekst-arv). + pub context_id: Option, } #[derive(Serialize)] pub struct CreateNodeResponse { pub node_id: Uuid, + /// Edge-ID for automatisk opprettet `belongs_to`-edge (kun ved context_id). + #[serde(skip_serializing_if = "Option::is_none")] + pub belongs_to_edge_id: Option, } /// POST /intentions/create_node /// /// Validerer input, skriver til STDB (instant), spawner async PG-skriving. /// Returnerer node_id umiddelbart. +/// +/// Hvis `context_id` er satt, opprettes automatisk en `belongs_to`-edge +/// fra den nye noden til kontekstnoden. Kontekstnoden må eksistere og +/// være en kommunikasjonsnode. Ref: docs/retninger/universell_input.md pub async fn create_node( State(state): State, user: AuthUser, @@ -165,6 +176,31 @@ pub async fn create_node( ))); } + // -- Valider context_id hvis satt -- + if let Some(ctx_id) = req.context_id { + let ctx_node = sqlx::query_as::<_, NodeKindRow>( + "SELECT node_kind FROM nodes WHERE id = $1", + ) + .bind(ctx_id) + .fetch_optional(&state.db) + .await + .map_err(|e| { + tracing::error!("PG-feil ved context_id-sjekk: {e}"); + internal_error("Databasefeil ved validering av context_id") + })?; + + match ctx_node { + None => return Err(bad_request(&format!("context_id {} finnes ikke", ctx_id))), + Some(row) if row.node_kind != "communication" => { + return Err(bad_request(&format!( + "context_id {} er en '{}'-node, ikke en kommunikasjonsnode", + ctx_id, row.node_kind + ))); + } + _ => {} // OK — kommunikasjonsnode + } + } + let title = req.title.unwrap_or_default(); let content = req.content.unwrap_or_default(); let metadata = req @@ -196,6 +232,7 @@ pub async fn create_node( node_id = %node_id, node_kind = %node_kind, created_by = %user.node_id, + context_id = ?req.context_id, "Node opprettet i STDB" ); @@ -211,7 +248,54 @@ pub async fn create_node( user.node_id, ); - Ok(Json(CreateNodeResponse { node_id })) + // -- Kontekst-arv: automatisk belongs_to-edge -- + let belongs_to_edge_id = if let Some(ctx_id) = req.context_id { + let edge_id = Uuid::now_v7(); + let edge_id_str = edge_id.to_string(); + let ctx_id_str = ctx_id.to_string(); + let bt_metadata = serde_json::json!({}); + let bt_metadata_str = bt_metadata.to_string(); + + state + .stdb + .create_edge( + &edge_id_str, + &node_id_str, // source = ny node + &ctx_id_str, // target = kommunikasjonsnoden + "belongs_to", + &bt_metadata_str, + false, + &created_by_str, + ) + .await + .map_err(|e| stdb_error("create_edge (belongs_to)", e))?; + + tracing::info!( + edge_id = %edge_id, + node_id = %node_id, + context_id = %ctx_id, + "belongs_to-edge opprettet i STDB (kontekst-arv)" + ); + + // belongs_to er ikke tilgangsgivende — enkel PG-insert + spawn_pg_insert_edge( + state.db.clone(), + state.stdb.clone(), + edge_id, + node_id, + ctx_id, + "belongs_to".to_string(), + bt_metadata, + false, + user.node_id, + ); + + Some(edge_id) + } else { + None + }; + + Ok(Json(CreateNodeResponse { node_id, belongs_to_edge_id })) } // ============================================================================= @@ -705,6 +789,12 @@ struct NodeRow { metadata: serde_json::Value, } +/// Enkel rad for å sjekke node_kind (brukes ved context_id-validering). +#[derive(sqlx::FromRow)] +struct NodeKindRow { + node_kind: String, +} + /// Spawner en tokio-task som skriver noden til PostgreSQL i bakgrunnen. fn spawn_pg_insert_node( db: PgPool,