Valider fase 3–4: fiks belongs_to-tilgangspropagering og mottakssortering

Validering av fase 3 (frontend) og fase 4 (tilgangskontroll) avdekket to bugs:

1. belongs_to-access-gap: Når en belongs_to-edge opprettes ETTER at
   noen allerede har tilgang til foreldrenoden, fikk ikke barnenoden
   tilgangsoppføringer i node_access-matrisen. F.eks. kunne Vegard (eier
   av en kommunikasjonsnode) ikke se innhold opprettet av Claude med
   belongs_to-edge til den noden.

   Løsning: Ny PG-funksjon propagate_belongs_to_access() som kopierer
   forelderens tilgangsrader til barnet. Kalles fra maskinrommet ved
   opprettelse av belongs_to-edges (create_node m/context, create_edge,
   create_communication m/context). Retroaktiv fiks for eksisterende data.

2. Mottaksflate-sortering: Brukte .microsSinceUnixEpoch (SpacetimeDB-
   BigInt-arv) på vanlig number-felt, ga alltid 0n → ingen sortering.
   Fikset til direkte number-sammenligning.

Verifisert: SvelteKit + maskinrommet bygger og kjører. PG-skjema, OIDC,
WebSocket/NOTIFY, RLS-policies, team-transitivitet og visibility fungerer.
This commit is contained in:
vegard 2026-03-18 14:41:20 +00:00
parent 773569759c
commit 5dfaeff53c
4 changed files with 85 additions and 7 deletions

View file

@ -67,11 +67,7 @@
} }
// Sort by created_at descending // Sort by created_at descending
nodes.sort((a, b) => { nodes.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
const ta = a.createdAt?.microsSinceUnixEpoch ?? 0n;
const tb = b.createdAt?.microsSinceUnixEpoch ?? 0n;
return tb > ta ? 1 : tb < ta ? -1 : 0;
});
return nodes; return nodes;
}); });

View file

@ -595,6 +595,16 @@ pub async fn create_node(
"belongs_to-edge opprettet (kontekst-arv)" "belongs_to-edge opprettet (kontekst-arv)"
); );
// Propager tilgang: alle som har tilgang til ctx_id får tilgang til node_id
if let Err(e) = sqlx::query("SELECT propagate_belongs_to_access($1, $2)")
.bind(node_id)
.bind(ctx_id)
.execute(&state.db)
.await
{
tracing::error!("propagate_belongs_to_access feilet: {e}");
}
Some(edge_id) Some(edge_id)
} else { } else {
None None
@ -881,6 +891,18 @@ pub async fn create_edge(
internal_error("Databasefeil ved opprettelse av edge") internal_error("Databasefeil ved opprettelse av edge")
})?; })?;
// Propager tilgang ved belongs_to: alle som har tilgang til target (forelder) får tilgang til source (barn)
if req.edge_type == "belongs_to" {
if let Err(e) = sqlx::query("SELECT propagate_belongs_to_access($1, $2)")
.bind(req.source_id)
.bind(req.target_id)
.execute(&state.db)
.await
{
tracing::error!("propagate_belongs_to_access feilet: {e}");
}
}
// Trigger rendering ved belongs_to // Trigger rendering ved belongs_to
if req.edge_type == "belongs_to" { if req.edge_type == "belongs_to" {
if let Ok(Some(config)) = crate::publishing::find_publishing_collection_by_id(&state.db, req.target_id).await { if let Ok(Some(config)) = crate::publishing::find_publishing_collection_by_id(&state.db, req.target_id).await {
@ -1946,6 +1968,16 @@ pub async fn create_communication(
context_id = %context_id, context_id = %context_id,
"belongs_to-edge opprettet til kontekstnode" "belongs_to-edge opprettet til kontekstnode"
); );
// Propager tilgang fra kontekstnoden
if let Err(e) = sqlx::query("SELECT propagate_belongs_to_access($1, $2)")
.bind(node_id)
.bind(context_id)
.execute(&state.db)
.await
{
tracing::error!("propagate_belongs_to_access feilet: {e}");
}
} }
tracing::info!( tracing::info!(

View file

@ -0,0 +1,51 @@
-- Migrasjon 020: Propager tilgang ved nye belongs_to-edges
--
-- Når en belongs_to-edge opprettes mellom barn og forelder, skal alle
-- som allerede har tilgang til forelderen også få tilgang til barnet.
-- recompute_access håndterer dette i steg 2 for access-granting edges,
-- men ikke for belongs_to-edges som opprettes etter at tilgang allerede er gitt.
CREATE OR REPLACE FUNCTION propagate_belongs_to_access(
p_child_id UUID,
p_parent_id UUID
) RETURNS void LANGUAGE plpgsql AS $$
BEGIN
-- For alle som har tilgang til forelder: gi samme tilgang til barnet
INSERT INTO node_access (subject_id, object_id, access, via_edge)
SELECT na.subject_id, p_child_id, na.access, na.via_edge
FROM node_access na
WHERE na.object_id = p_parent_id
ON CONFLICT (subject_id, object_id)
DO UPDATE SET access = GREATEST(node_access.access, EXCLUDED.access),
via_edge = CASE
WHEN EXCLUDED.access > node_access.access THEN EXCLUDED.via_edge
ELSE node_access.via_edge
END;
END;
$$;
-- Retroaktivt: fiks eksisterende belongs_to-edges der tilgang mangler.
-- Finn barn som har belongs_to-edge til forelder, men der forelderens
-- tilgangssubjekter ikke har tilgang til barnet.
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT e.source_id AS child_id, e.target_id AS parent_id
FROM edges e
WHERE e.edge_type = 'belongs_to'
AND EXISTS (
SELECT 1 FROM node_access na
WHERE na.object_id = e.target_id
AND NOT EXISTS (
SELECT 1 FROM node_access na2
WHERE na2.subject_id = na.subject_id
AND na2.object_id = e.source_id
)
)
LOOP
PERFORM propagate_belongs_to_access(r.child_id, r.parent_id);
END LOOP;
END;
$$;

View file

@ -296,8 +296,7 @@ fiks direkte hvis det er småting, eller opprett nye work_items (tasks)
med spesifikasjon for det som trenger en dedikert sesjon. med spesifikasjon for det som trenger en dedikert sesjon.
- [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. - [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. - [x] 23.2 Valider fase 34 (frontend + tilgang): SvelteKit-oppsett, OIDC-flow, sanntid, mottaksflate, TipTap-editor, node_access-matrise, team-transitivitet, visibility-filtrering.
> Påbegynt: 2026-03-18T14:30
- [ ] 23.3 Valider fase 58 (kommunikasjon + CAS + lyd + aliaser): chat-loop, kontekst-arv, CAS-hashing/deduplisering, Whisper-pipeline, segmenttabell, SRT-eksport, alias-identitet. - [ ] 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. - [ ] 23.4 Valider fase 910 (visninger + AI): kanban drag-and-drop, kalender, dagbok, kunnskapsgraf, LiteLLM-ruting, AI-foreslåtte edges, oppsummering, TTS.
- [ ] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS. - [ ] 23.5 Valider fase 11 (produksjon): LiveKit-oppsett, sanntidslyd, pruning-logikk, podcast-RSS.