Skip to content

Modèle Conceptuel de Données (MCD)

⚓ Berth — Modèle Conceptuel de Données (MCD)

Périmètre : une instance Berth correspond à un seul client déployé en self-hosted. Le modèle ne contient donc aucune entité Tenant, Client ou Organisation.

  • Les identifiants sont des UUID générés côté API ou base PostgreSQL.
  • Les dates sont stockées en TIMESTAMP WITH TIME ZONE (abrégé TIMESTAMPTZ ci-dessous).
  • Les états métier sont représentés par des VARCHAR contrôlés par une contrainte CHECK ou par un enum PostgreSQL.
  • Les secrets et clés privées ne sont jamais stockés en clair. Les colonnes de certificat contiennent uniquement des informations publiques ou des empreintes.
  • JSONB est utilisé pour les métadonnées variables provenant de Docker, des Agents ou des commandes.
  • Redis (cache et Streams) est un composant technique hors MCD persistant : ses clés et événements sont dérivés des écritures PostgreSQL.
  • Les suppressions retirent directement les entités de la base de données. Les règles ON DELETE indiquées ci-dessous sont les comportements relationnels attendus.

Utilisateur humain autorisé à se connecter à l’instance Berth. Chaque utilisateur possède exactement un rôle applicatif (user_role) et peut être limité à un sous-ensemble d’agents.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
email VARCHAR(320) UNIQUE, non nul, normalisé en minuscules
password_hash VARCHAR(255) Non nul ; hash adaptatif (jamais le mot de passe)
first_name VARCHAR(100) Non nul
last_name VARCHAR(100) Non nul
role user_role (ENUM PostgreSQL) Non nul, défaut ROLE_READONLY; valeurs autorisées : ROLE_ADMIN, ROLE_MAINTAINER, ROLE_READONLY
is_active BOOLEAN Non nul, défaut TRUE
last_login_at TIMESTAMPTZ Nullable
created_at TIMESTAMPTZ Non nul, défaut now()
updated_at TIMESTAMPTZ Non nul, mis à jour automatiquement

Agent Berth installé sur une machine hôte exécutant Docker. L’Agent est le point de connexion gRPC/mTLS ; il n’est pas un tenant distinct.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
name VARCHAR(120) UNIQUE, non nul
hostname VARCHAR(255) Nullable
description VARCHAR(500) Nullable
agent_version VARCHAR(50) Nullable
agent_status VARCHAR(50) Non nul, not_register, offline, online, degraded
last_seen_at TIMESTAMPTZ Nullable ; dernier heartbeat reçu
enrollment_token VARCHAR(255) UNIQUE, Nullable ; token à usage unique d’enrôlement
mtls_cert_fingerprint VARCHAR(128) UNIQUE, Nullable ; empreinte SHA-256 du certificat client
created_at TIMESTAMPTZ Non nul, défaut now()
updated_at TIMESTAMPTZ Non nul

Table de liaison (relation N,N) permettant de restreindre les utilisateurs ROLE_MAINTAINER et ROLE_READONLY à des applications précises. Elle ne s’applique pas à ROLE_ADMIN : un administrateur accède à toutes les applications et tous les agents sans entrée UserApplicationAccess (accès total implicite au niveau applicatif). Pour les deux autres rôles, l’absence de ligne signifie aucun accès : règle de sécurité deny by default.

Attribut Type PostgreSQL Contraintes / description
user_id UUID PK, FK → User.id, ON DELETE CASCADE
application_id UUID PK, FK → Application.id, ON DELETE CASCADE
granted_by_user_id UUID FK → User.id, nullable, ON DELETE SET NULL
created_at TIMESTAMPTZ Non nul, défaut now()
expires_at TIMESTAMPTZ Nullable ; expiration facultative

Regroupement logique de conteneurs, comparable à un projet ou une stack Docker. Une application peut être reliée à aucun ou plusieurs agents (via la table pivot agent_application).

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
name VARCHAR(150) UNIQUE, non nul
slug VARCHAR(180) UNIQUE, non nul
description VARCHAR(500) Nullable
source_type VARCHAR(30) Non nul, ex. docker_compose, manual, imported
status VARCHAR(30) Non nul, ex. running, partial, stopped, unknown
metadata JSONB Non nul, défaut '{}'
created_at TIMESTAMPTZ Non nul, défaut now()
updated_at TIMESTAMPTZ Non nul

Table pivot (relation N,N) reliant directement une application à zéro, un ou plusieurs agents. Elle permet d’associer explicitement une application aux agents sur lesquels elle est déployée ou orchestrée.

Attribut Type PostgreSQL Contraintes / description
agent_id UUID PK, FK → Agent.id, ON DELETE CASCADE
application_id UUID PK, FK → Application.id, ON DELETE CASCADE
created_at TIMESTAMPTZ Non nul, défaut now()

Conteneur Docker découvert et synchronisé par l’Agent. application_id est nullable car un conteneur peut exister hors d’une application/stack.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
agent_id UUID FK → Agent.id, non nul, ON DELETE RESTRICT
application_id UUID FK → Application.id, nullable, ON DELETE SET NULL
docker_id VARCHAR(128) Non nul ; UNIQUE par agent_id
name VARCHAR(255) Non nul
image VARCHAR(500) Non nul
image_tag VARCHAR(255) Nullable
state VARCHAR(30) Non nul, ex. running, exited, paused, created, dead
status_text VARCHAR(500) Nullable ; statut Docker lisible
ports JSONB Non nul, défaut '[]'
labels JSONB Non nul, défaut '{}'
resources JSONB Non nul, défaut '{}'; limites CPU/mémoire, etc.
last_seen_at TIMESTAMPTZ Nullable
created_at TIMESTAMPTZ Non nul, défaut now()
updated_at TIMESTAMPTZ Non nul

Commande potentiellement longue envoyée à un Agent (par exemple restart, logs ou action sur une stack). Cette entité permet de suivre l’état, le résultat et l’identifiant d’idempotence.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
requested_by_user_id UUID FK → User.id, nullable, ON DELETE SET NULL
agent_id UUID FK → Agent.id, non nul, ON DELETE RESTRICT
container_id UUID FK → Container.id, nullable, ON DELETE SET NULL
application_id UUID FK → Application.id, nullable, ON DELETE SET NULL
command_type VARCHAR(50) Non nul, ex. start, stop, restart, logs, inspect
parameters JSONB Non nul, défaut '{}'
status VARCHAR(20) Non nul, queued, running, succeeded, failed, cancelled
idempotency_key VARCHAR(128) UNIQUE, non nul
agent_request_id VARCHAR(128) Nullable ; corrélation côté Agent
result JSONB Nullable
error_message TEXT Nullable
requested_at TIMESTAMPTZ Non nul, défaut now()
started_at TIMESTAMPTZ Nullable
finished_at TIMESTAMPTZ Nullable

Journal immuable des actions utilisateur et des événements de sécurité/administration. La cible est polymorphe (target_type + target_id) afin de couvrir User, Agent, Application, Container et les autres objets métier.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
actor_user_id UUID FK → User.id, nullable, ON DELETE SET NULL (système ou utilisateur supprimé)
action VARCHAR(100) Non nul, ex. container.restart, user.update
target_type VARCHAR(50) Non nul, ex. AGENT, CONTAINER, USER, APPLICATION
target_id UUID Nullable si l’action est globale
agent_id UUID FK → Agent.id, nullable, ON DELETE SET NULL
result VARCHAR(20) Non nul, success, failure, denied
request_id VARCHAR(128) Nullable ; corrélation HTTP/gRPC
ip_address INET Nullable
details JSONB Non nul, défaut '{}'; ne pas y stocker de secret
created_at TIMESTAMPTZ Non nul, défaut now(); indexé pour recherche chronologique

Alerte ou information destinée à un utilisateur, par exemple Agent hors ligne, commande échouée ou conteneur arrêté.

Attribut Type PostgreSQL Contraintes / description
id UUID PK, non nul
user_id UUID FK → User.id, non nul, ON DELETE CASCADE
agent_id UUID FK → Agent.id, nullable, ON DELETE SET NULL
type VARCHAR(50) Non nul, ex. agent_offline, command_failed, container_alert
severity VARCHAR(20) Non nul, info, warning, critical
title VARCHAR(200) Non nul
message TEXT Non nul
data JSONB Non nul, défaut '{}'
is_read BOOLEAN Non nul, défaut FALSE
read_at TIMESTAMPTZ Nullable
created_at TIMESTAMPTZ Non nul, défaut now()

Le rôle est porté directement par User.role via l’ENUM PostgreSQL user_role. Il n’existe plus de tables dédiées à la gestion des rôles et des droits : les capacités sont définies par ces trois rôles applicatifs et contrôlées par l’API.

Rôle Capacités Périmètre applications
ROLE_ADMIN Accès total. Peut gérer les utilisateurs Berth (CRUD sur User) et toutes les autres fonctions. Accès à toutes les applications et tous les agents, sans exception, même sans ligne UserApplicationAccess ; l’accès total est implicite au niveau applicatif.
ROLE_MAINTAINER Peut gérer applications et conteneurs, exécuter start / stop / restart et consulter les AuditLog. Ne peut pas gérer les utilisateurs Berth (aucun CRUD sur User). Restreint par UserApplicationAccess : avec des entrées valides, uniquement les applications concernées ; sans entrée, aucun accès (deny by default).
ROLE_READONLY Lecture uniquement. Aucune modification et aucune action de commande (start / stop / restart). Restreint par UserApplicationAccess selon la même règle : sans entrée valide, aucun accès (deny by default).

ROLE_ADMIN est le seul rôle autorisé à créer, modifier ou supprimer des utilisateurs Berth. Les contrôles de périmètre ne doivent jamais être déduits d’une absence de ligne UserApplicationAccess pour un rôle non administrateur.

Notation Merise/Chen : 1,1 = exactement un, 0,1 = zéro ou un, 1,N = un à plusieurs, 0,N = zéro à plusieurs.

Association Cardinalité Règle de gestion
User — dispose d’un accès — Application User (0,N) ↔ Application (0,N) Accès explicite pour ROLE_MAINTAINER et ROLE_READONLY, révocable et soumis à expiration ; ROLE_ADMIN est exempté. Via UserApplicationAccess.
User — accorde — UserApplicationAccess User (0,N) ↔ UserApplicationAccess (1,1) Un octroi peut être attribué par un administrateur ; granted_by_user_id est nullable pour les migrations ou actions système.
Agent — est relié à — Application Agent (0,N) ↔ Application (0,N) Une application peut être reliée à aucun ou plusieurs agents ; un agent peut exécuter zéro ou plusieurs applications. Via AgentApplication.
Application — regroupe — Container Application (0,1) ↔ Container (0,N) Un conteneur peut être hors application ; une application regroupe zéro ou plusieurs conteneurs.
Agent — héberge — Container Agent (1,1) ↔ Container (0,N) Chaque conteneur est rattaché à son agent Docker d’origine.
User — demande — CommandExecution User (0,N) ↔ CommandExecution (0,1) Une commande peut être lancée par un utilisateur ou par un processus système.
Agent — reçoit — CommandExecution Agent (1,1) ↔ CommandExecution (0,N) Toute commande est exécutée par un Agent cible.
Container — cible — CommandExecution Container (0,1) ↔ CommandExecution (0,N) Une commande peut cibler un conteneur, une application ou l’agent seul.
Application — cible — CommandExecution Application (0,1) ↔ CommandExecution (0,N) Une commande peut porter sur une stack entière.
User — est acteur de — AuditLog User (0,N) ↔ AuditLog (0,1) L’acteur peut être absent pour un événement système ; l’entrée d’audit reste conservée.
Agent — est contexte de — AuditLog Agent (0,N) ↔ AuditLog (0,1) Une action peut être globale ou contextualisée par un agent.
User — reçoit — Notification User (1,1) ↔ Notification (0,N) Une notification appartient à un seul destinataire.
Agent — génère — Notification Agent (0,1) ↔ Notification (0,N) Une notification peut être liée à un agent ou être globale.
erDiagram
    USER {
        UUID id PK
        VARCHAR email UK
        VARCHAR password_hash
        VARCHAR first_name
        VARCHAR last_name
        USER_ROLE role
        BOOLEAN is_active
        TIMESTAMP last_login_at
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }


    AGENT {
        UUID id PK
        VARCHAR name UK
        VARCHAR hostname
        VARCHAR description
        VARCHAR agent_version
        VARCHAR agent_status
        TIMESTAMP last_seen_at
        VARCHAR enrollment_token UK
        VARCHAR mtls_cert_fingerprint UK
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }

    USER_APPLICATION_ACCESS {
        UUID user_id PK, FK
        UUID application_id PK, FK
        UUID granted_by_user_id FK
        TIMESTAMP created_at
        TIMESTAMP expires_at
    }

    APPLICATION {
        UUID id PK
        VARCHAR name UK
        VARCHAR slug UK
        VARCHAR description
        VARCHAR source_type
        VARCHAR status
        JSONB metadata
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }

    AGENT_APPLICATION {
        UUID agent_id PK, FK
        UUID application_id PK, FK
        TIMESTAMP created_at
    }

    CONTAINER {
        UUID id PK
        UUID agent_id FK
        UUID application_id FK
        VARCHAR docker_id
        VARCHAR name
        VARCHAR image
        VARCHAR image_tag
        VARCHAR state
        VARCHAR status_text
        JSONB ports
        JSONB labels
        JSONB resources
        TIMESTAMP last_seen_at
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }

    COMMAND_EXECUTION {
        UUID id PK
        UUID requested_by_user_id FK
        UUID agent_id FK
        UUID container_id FK
        UUID application_id FK
        VARCHAR command_type
        JSONB parameters
        VARCHAR status
        VARCHAR idempotency_key UK
        VARCHAR agent_request_id
        JSONB result
        TEXT error_message
        TIMESTAMP requested_at
        TIMESTAMP started_at
        TIMESTAMP finished_at
    }

    AUDIT_LOG {
        UUID id PK
        UUID actor_user_id FK
        VARCHAR action
        VARCHAR target_type
        UUID target_id
        UUID agent_id FK
        VARCHAR result
        VARCHAR request_id
        INET ip_address
        JSONB details
        TIMESTAMP created_at
    }

    NOTIFICATION {
        UUID id PK
        UUID user_id FK
        UUID agent_id FK
        VARCHAR type
        VARCHAR severity
        VARCHAR title
        TEXT message
        JSONB data
        BOOLEAN is_read
        TIMESTAMP read_at
        TIMESTAMP created_at
    }

    USER ||--o{ USER_APPLICATION_ACCESS : "dispose de"
    APPLICATION ||--o{ USER_APPLICATION_ACCESS : "est accessible"
    USER o|--o{ USER_APPLICATION_ACCESS : "accorde"
    AGENT ||--o{ AGENT_APPLICATION : "est relié à"
    APPLICATION ||--o{ AGENT_APPLICATION : "est reliée à"
    AGENT ||--o{ CONTAINER : "héberge"
    APPLICATION o|--o{ CONTAINER : "regroupe"
    USER o|--o{ COMMAND_EXECUTION : "demande"
    AGENT ||--o{ COMMAND_EXECUTION : "reçoit"
    CONTAINER o|--o{ COMMAND_EXECUTION : "cible"
    APPLICATION o|--o{ COMMAND_EXECUTION : "cible"
    USER o|--o{ AUDIT_LOG : "est acteur de"
    AGENT o|--o{ AUDIT_LOG : "contextualise"
    USER ||--o{ NOTIFICATION : "reçoit"
    AGENT o|--o{ NOTIFICATION : "génère"
  1. Autorisation côté API : la vérification du rôle (User.role) et du périmètre UserApplicationAccess doit être réalisée côté API Go, dans un middleware d’autorisation, et ne doit jamais reposer uniquement sur le front. ROLE_ADMIN bénéficie de l’accès total implicite ; ROLE_MAINTAINER et ROLE_READONLY sont soumis à UserApplicationAccess valide (non expiré), avec deny by default.
  2. Cohérence des cibles : lorsqu’une commande cible un conteneur ou une application, sa cible doit appartenir au agent_id de la commande. Cette règle est contrôlée par l’API et, si nécessaire, par des contraintes ou triggers PostgreSQL.
  3. Unicité Docker : UNIQUE(agent_id, docker_id) garantit qu’un même identifiant Docker n’est pas confondu entre deux agents.
  4. Unicité des stacks : UNIQUE(name) et UNIQUE(slug) empêchent deux applications homonymes.
  5. Synchronisation Agent : les heartbeats et changements de statut mettent à jour Agent.agent_status et last_seen_at, puis publient un événement Redis Stream ; Redis ne constitue pas la source de vérité durable.
  6. Commandes idempotentes : idempotency_key empêche le traitement accidentel d’une même demande ; agent_request_id assure la corrélation avec la réponse gRPC.
  7. Audit immuable : AuditLog est append-only. Les actions réussies, échouées et refusées doivent être journalisées ; aucune donnée sensible ne doit apparaître dans details, parameters ou result.
  8. Suppression logique : la suppression d’un agent, d’une application ou d’un conteneur doit préserver l’historique et les journaux. Les accès et notifications associés peuvent être nettoyés selon les règles ON DELETE documentées.
  9. Cache : les lectures peuvent être servies par Redis ; toute écriture métier invalide les clés concernées après validation de la transaction PostgreSQL.
  10. Index recommandés : index sur User.email, Agent.agent_status, Agent.last_seen_at, les FK, Container(agent_id, state), CommandExecution(status, requested_at), AuditLog(created_at, action) et Notification(user_id, is_read, created_at).