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,ClientouOrganisation.
1. Principes et conventions
Section titled “1. Principes et conventions”- Les identifiants sont des
UUIDgénérés côté API ou base PostgreSQL. - Les dates sont stockées en
TIMESTAMP WITH TIME ZONE(abrégéTIMESTAMPTZci-dessous). - Les états métier sont représentés par des
VARCHARcontrôlés par une contrainteCHECKou 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.
JSONBest 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 DELETEindiquées ci-dessous sont les comportements relationnels attendus.
2. Entités
Section titled “2. Entités”2.1 User
Section titled “2.1 User”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 |
2.2 Agent
Section titled “2.2 Agent”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 |
2.3 UserApplicationAccess
Section titled “2.3 UserApplicationAccess”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 |
2.4 Application
Section titled “2.4 Application”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 |
2.5 AgentApplication
Section titled “2.5 AgentApplication”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() |
2.6 Container
Section titled “2.6 Container”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 |
2.7 CommandExecution
Section titled “2.7 CommandExecution”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 |
2.8 AuditLog
Section titled “2.8 AuditLog”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 |
2.9 Notification
Section titled “2.9 Notification”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() |
3. Sémantique métier des rôles
Section titled “3. Sémantique métier des rôles”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.
4. Associations et cardinalités
Section titled “4. Associations et cardinalités”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. |
5. Diagramme Mermaid
Section titled “5. Diagramme Mermaid”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"
6. Règles de gestion transverses
Section titled “6. Règles de gestion transverses”- Autorisation côté API : la vérification du rôle (
User.role) et du périmètreUserApplicationAccessdoit être réalisée côté API Go, dans un middleware d’autorisation, et ne doit jamais reposer uniquement sur le front.ROLE_ADMINbénéficie de l’accès total implicite ;ROLE_MAINTAINERetROLE_READONLYsont soumis àUserApplicationAccessvalide (non expiré), avec deny by default. - Cohérence des cibles : lorsqu’une commande cible un conteneur ou une application, sa cible doit appartenir au
agent_idde la commande. Cette règle est contrôlée par l’API et, si nécessaire, par des contraintes ou triggers PostgreSQL. - Unicité Docker :
UNIQUE(agent_id, docker_id)garantit qu’un même identifiant Docker n’est pas confondu entre deux agents. - Unicité des stacks :
UNIQUE(name)etUNIQUE(slug)empêchent deux applications homonymes. - Synchronisation Agent : les heartbeats et changements de statut mettent à jour
Agent.agent_statusetlast_seen_at, puis publient un événement Redis Stream ; Redis ne constitue pas la source de vérité durable. - Commandes idempotentes :
idempotency_keyempêche le traitement accidentel d’une même demande ;agent_request_idassure la corrélation avec la réponse gRPC. - Audit immuable :
AuditLogest append-only. Les actions réussies, échouées et refusées doivent être journalisées ; aucune donnée sensible ne doit apparaître dansdetails,parametersouresult. - 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 DELETEdocumentées. - 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.
- 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)etNotification(user_id, is_read, created_at).
