Référence du Schéma Firestore

Modèles de données Firestore et collections complètes pour la plateforme TeamDay.

Vue d’Ensemble des Collections

TeamDay utilise Firebase Firestore comme base de données principale. Le schéma suit une structure hiérarchique avec des règles de sécurité strictes et des abonnements en temps réel.

CollectionObjectifContrôle d’Accès
organizationsGestion des organisations/équipesMembres uniquement
usersDonnées de compte utilisateurPropriétaire uniquement
userProfilesPréférences et paramètres utilisateurPropriétaire uniquement
characterAgents/assistants IABasé sur la visibilité
spacesEspaces de travail pour les agentsBasé sur la visibilité
chatsConversationsPropriétaire/membres org
tasksTâches créées par les agentsMembres org
executionsHistorique d’exécution des agentsMembres org
missionsTâches autonomes de longue duréeMembres org
scheduledTasksAutomatisations d’agents planifiéesMembres org
skillsPackages de prompts réutilisablesBasé sur la visibilité
subagentsConfigurations d’agents spécialisésBasé sur la visibilité
pluginsPackages de configurationBasé sur la visibilité
pluginInstallationsInstallations de plugins par espaceMembres org
mcpInstancesInstances de serveur MCPMembres org
personal_access_tokensTokens APIPropriétaire uniquement
invitesInvitations d’organisationLecture publique
usageAggregatesMétriques d’utilisationMembres org
creditTransactionsHistorique de solde de créditsMembres org

Collections Principales

organizations

Gestion des organisations/équipes. Chaque utilisateur appartient à une ou plusieurs organisations.

interface Organization {
  id: string
  name: string
  logoUrl?: string
  ownerId: string                    // User who created the org
  members: string[]                  // Array of user IDs
  subscription?: OrganizationSubscription
  createdAt: Timestamp
  updatedAt: Timestamp

  // Encrypted fields (server-side only)
  encryptedAnthropicApiKey?: string  // Encrypted API key
}

Exemple de Document:

{
  "id": "org_abc123",
  "name": "Acme Corporation",
  "logoUrl": "https://storage.googleapis.com/...",
  "ownerId": "user_xyz",
  "members": ["user_xyz", "user_abc", "user_def"],
  "subscription": {
    "tier": "pro",
    "status": "active"
  },
  "createdAt": "2024-01-15T10:00:00Z",
  "updatedAt": "2024-01-15T10:00:00Z"
}

Index Requis:

  • members (array-contains) pour les requêtes de membres
  • ownerId pour les requêtes de propriétaire

Sous-collections:

  • gitCredentials/{credentialId} - Tokens OAuth pour les intégrations Git

users

Authentification utilisateur et données de compte. Géré par Firebase Auth.

interface User {
  id: string                         // Firebase Auth UID
  email: string
  displayName?: string
  photoURL?: string
  createdAt: Timestamp
  lastSignIn?: Timestamp
}

Sécurité: Accès propriétaire uniquement. Suppression côté client non autorisée.

Sous-collections:

  • gitCredentials/{credentialId} - Tokens OAuth Git personnels

userProfiles

Préférences utilisateur, paramètres et dernière organisation utilisée.

interface UserProfile {
  userId: string
  lastUsedOrganizationId?: string
  preferences?: {
    theme?: 'light' | 'dark' | 'system'
    locale?: string
  }
  createdAt: Timestamp
  updatedAt?: Timestamp

  // Encrypted fields (server-side only)
  encryptedAnthropicApiKey?: string
  encryptedClaudeCodeOAuthToken?: string
}

Sécurité: Le propriétaire peut lire/mettre à jour. Ne peut pas modifier les champs d’identification chiffrés depuis le client.


character (Agents IA)

Agents/assistants IA créés et configurés par les utilisateurs.

type CharacterVisibility = 'private' | 'organization' | 'public' | 'unlisted'
type CharacterCategory = 'marketing' | 'finance' | 'hr' | 'engineering' | 'operations' | 'general'

interface Character {
  id: string
  name: string
  role: string                       // e.g., "Marketing Assistant"
  initialGreeting: string
  characterDescription: string
  system_message: string             // System prompt
  systemPrompt?: string              // Alias for system_message
  image: string                      // Avatar URL
  color: string                      // Brand color

  // Configuration
  model?: string                     // e.g., 'claude-sonnet-4-6'
  visibility: CharacterVisibility
  ownerId: string
  organizationId?: string
  ownerName?: string

  // Resources
  files: any[]                       // Attached files
  linksToGetContextFrom: any[]       // URLs for context
  tools?: string[] | null            // Legacy AI tools
  advanced_tools?: string[]          // MCP tool IDs
  skillIds?: string[]                // Skill references
  subagentIds?: string[]             // Subagent references

  // Features
  suggested_topics?: any[]
  suggested_topics_conversation_starters?: string[]
  voice?: string                     // Voice model ID
  tags?: string[]
  articles?: string[]

  // Template system
  isTemplate?: boolean               // Global template flag
  category?: CharacterCategory
  featured?: boolean
  version?: number
  changelog?: string
  templateId?: string                // Source template ID
  instanceCount?: number             // For templates

  // State
  isForkable?: boolean
  archived?: boolean

  // Statistics
  statistics?: {
    likes: number
    dislikes: number
    uniqueChats: number
  }

  // Timestamps
  createdAt: Date | Timestamp
  updatedAt?: Date | Timestamp

  // Soft delete
  deletedAt?: Date | null
  isDeleted?: boolean
}

Exemple de Document:

{
  "id": "char_123",
  "name": "Marketing Maven",
  "role": "Marketing Assistant",
  "initialGreeting": "Hi! I'm here to help with marketing tasks.",
  "characterDescription": "Expert in content marketing and social media",
  "system_message": "You are a marketing specialist...",
  "image": "https://storage.googleapis.com/avatars/marketing.png",
  "color": "#FF6B6B",
  "model": "claude-sonnet-4-6",
  "visibility": "organization",
  "ownerId": "user_xyz",
  "organizationId": "org_abc123",
  "skillIds": ["research-assistant", "technical-writer"],
  "advanced_tools": ["mcp_google_analytics"],
  "tags": ["marketing", "content"],
  "archived": false,
  "isTemplate": false,
  "createdAt": "2024-01-15T10:00:00Z"
}

Index Requis:

  • Composite: organizationId + isTemplate + createdAt (desc)
  • ownerId + visibility
  • isTemplate pour les requêtes de template

Règles de Visibilité:

  • public: Visible par tous les utilisateurs authentifiés
  • organization: Visible par tous les membres de l’organisation
  • private: Seul le propriétaire peut voir
  • unlisted: Seul le propriétaire peut voir (pas dans les listes)
  • Templates (isTemplate: true): Visible par tous

spaces

Espaces de travail où les agents opèrent. Chaque espace a son propre système de fichiers et configuration git.

type SpaceVisibility = 'private' | 'organization' | 'public'
type SpaceInitializationType = 'empty' | 'readme' | 'git' | 'starterKit'
type SpaceInitializationStatus = 'pending' | 'initializing' | 'ready' | 'failed'

interface Space {
  id: string
  organizationId: string
  name: string
  description: string

  // Branding
  coverImage?: string
  gradientColors?: [string, string]  // [fromColor, toColor]
  coverImageAttribution?: {
    source: 'unsplash' | 'upload' | 'url' | 'ai'
    photographerName?: string
    photographerUsername?: string
    unsplashUrl?: string
  }

  // Access control
  visibility: SpaceVisibility
  ownerId: string
  allowedUserIds?: string[]          // For private spaces

  // Configuration
  assignedAgents?: string[]          // Character IDs
  instructions?: string              // Fallback instructions
  advanced_tools?: string[]          // MCP instance IDs
  skillIds?: string[]                // Skill IDs
  subagentIds?: string[]             // Subagent IDs
  pluginIds?: string[]               // Plugin IDs to install

  // Git configuration
  git?: {
    aiSettings?: {
      autoCommitOnSessionEnd?: boolean
      generateCommitMessages?: boolean
      autoResolveConflicts?: boolean
      requireApprovalForPush?: boolean
    }
    permissions?: Record<string, {
      readers: string[]
      writers: string[]
      pushers: string[]
      defaultPermission: 'none' | 'read' | 'write' | 'push'
    }>
    defaultUserId?: string
    remotes?: Record<string, string> // Cache only
  }

  // Initialization
  initializationStatus?: SpaceInitializationStatus
  initializationType?: SpaceInitializationType
  initializationError?: string

  // Statistics
  chatCount?: number
  fileCount?: number

  // Timestamps
  createdAt: Date
  updatedAt: Date

  // Soft delete
  deletedAt?: Date | null
  isDeleted?: boolean

  // Optimistic UI
  isOptimistic?: boolean
  clientSideId?: string
}

Exemple de Document:

{
  "id": "space_456",
  "organizationId": "org_abc123",
  "name": "Marketing Campaigns",
  "description": "Space for marketing campaign work",
  "visibility": "organization",
  "ownerId": "user_xyz",
  "assignedAgents": ["char_123"],
  "skillIds": ["research-assistant"],
  "gradientColors": ["#3b82f6", "#8b5cf6"],
  "initializationStatus": "ready",
  "initializationType": "empty",
  "chatCount": 5,
  "fileCount": 23,
  "createdAt": "2024-01-15T10:00:00Z",
  "updatedAt": "2024-01-15T12:00:00Z"
}

Index Requis:

  • organizationId + updatedAt (desc)
  • ownerId pour les requêtes de propriétaire
  • visibility pour le filtrage

chats

Conversations entre les utilisateurs et les agents.

type ChatStatus = 'standby' | 'working' | 'done' | 'needs_attention' | 'error'

interface Chat {
  id: string
  organizationId: string
  spaceId: string
  title: string
  subtitle?: string
  status: ChatStatus
  ownerId: string
  lastMessage?: string
  sessionId?: string                 // Claude Code session ID

  // Session lifecycle
  closed: boolean
  closedAt?: Date | null

  // Visibility
  visibility?: 'private' | 'organization' | 'public'

  // Timestamps
  createdAt: Date
  updatedAt: Date
}

Sous-collections:

  • messages/{messageId} - Messages de chat (immuables)

Structure des Messages:

interface ChatMessage {
  id: string
  chatId: string
  role: 'user' | 'assistant'
  content: string
  createdAt: Date
  sessionId?: string
  toolCalls?: Array<{
    name: string
    input: any
    result?: string
  }>
}

Index Requis:

  • Composite: organizationId + spaceId + updatedAt (desc)
  • ownerId pour les chats de l’utilisateur
  • sessionId pour les requêtes de session

Sécurité: Les messages sont immuables (pas de mise à jour/suppression).


tasks

Tâches créées par les agents pour la coordination et l’attribution.

type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'

interface Task {
  id: string
  organizationId: string
  title: string
  description: string
  assignedTo?: string | null         // Agent ID
  priority: TaskPriority
  status: TaskStatus
  spaceId?: string | null
  createdBy: string | null           // Agent ID or user ID

  // Timestamps
  createdAt: string                  // ISO 8601
  completedAt?: string | null
}

Index Requis:

  • Composite: organizationId + status + createdAt (desc)
  • assignedTo pour les requêtes d’attribution
  • spaceId pour les tâches d’espace

executions

Suivi de l’exécution des agents et historique de délégation.

type ExecutionStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'

interface Execution {
  id: string
  organizationId: string
  agentId: string
  chatId: string
  sessionId: string
  status: ExecutionStatus

  // Content
  message: string                    // User's message
  result?: string                    // Agent's response
  error?: string                     // Error if failed

  // Delegation tracking
  parentExecutionId?: string
  delegationDepth: number

  // Timing
  startedAt: Date
  completedAt?: Date
  duration?: number                  // milliseconds

  // Cost tracking
  tokensInput?: number
  tokensOutput?: number
  cost?: number
}

Index Requis:

  • Composite: organizationId + chatId + startedAt (desc)
  • sessionId pour les requêtes de session
  • parentExecutionId pour les chaînes de délégation

missions

Tâches d’agents autonomes de longue durée.

type MissionStatus = 'pending' | 'running' | 'paused' | 'completed' | 'failed'
type AgentType = 'claude' | 'gemini' | 'codex'

interface Mission {
  id: string
  userId: string
  organizationId: string
  spaceId?: string
  title: string
  goal: string
  agentType: AgentType

  // Schedule
  schedule: {
    type: 'once' | 'cron' | 'continuous'
    value?: string                   // Cron expression
    lastRun?: number
    nextRun?: number
  }

  // State
  state: {
    currentStep: string
    contextSummary: string
    artifacts: string[]
    memory: Record<string, any>
  }

  status: MissionStatus
  logs: MissionLogEntry[]

  // Timestamps
  createdAt: number
  updatedAt: number
}

interface MissionLogEntry {
  timestamp: number
  level: 'info' | 'warn' | 'error'
  message: string
  metadata?: any
}

Index Requis:

  • Composite: organizationId + status + createdAt (desc)
  • userId pour les missions utilisateur
  • spaceId pour les missions d’espace

scheduledTasks

Automatisations d’agents planifiées (tâches cron).

interface ScheduledTask {
  id: string
  organizationId: string
  characterId: string                // Agent to run
  spaceId?: string                   // Optional space context

  // Task details
  name: string
  prompt: string                     // What to ask the agent
  schedule: string                   // Cron expression

  // State
  isActive: boolean
  lastRun?: Date | null
  lastRunStatus?: 'success' | 'error' | 'running' | null
  lastRunSessionId?: string | null
  nextRun?: Date | null

  // Metadata
  createdAt: Date
  createdBy: string
  updatedAt?: Date | null
}

Collection Associée: scheduledTaskExecutions/{executionId} stocke l’historique d’exécution.

Index Requis:

  • Composite: organizationId + isActive + nextRun (asc)
  • characterId pour les plannings d’agents
  • createdBy pour les requêtes de créateur

skills

Packages de prompts réutilisables qui étendent les capacités des agents.

type SkillVisibility = 'private' | 'organization' | 'public'
type SkillCategory = 'research' | 'writing' | 'data' | 'code' | 'marketing' | 'productivity' | 'file-handling' | 'other'

interface Skill {
  id: string

  // Identity
  name: string                       // kebab-case (e.g., "research-assistant")
  displayName: string                // Human readable
  description: string                // When to invoke

  // Content
  prompt: string                     // SKILL.md markdown instructions

  // Dependencies
  builtInTools?: string[]            // ["Read", "Write", "Bash", "WebSearch"]
  requiredMCPs?: string[]            // ["google-analytics", "bigquery"]

  // Categorization
  category: SkillCategory
  tags?: string[]
  icon?: string                      // Emoji or icon name

  // Ownership
  organizationId: string
  ownerId: string
  visibility: SkillVisibility

  // Marketplace
  featured?: boolean
  verified?: boolean
  usageCount?: number
  rating?: number                    // 1-5
  ratingCount?: number

  // Versioning
  version: number
  changelog?: string

  // Timestamps
  createdAt: Date | Timestamp
  updatedAt: Date | Timestamp

  // Soft delete
  isDeleted?: boolean
  deletedAt?: Date | null
}

Exemple de Document:

{
  "id": "skill_research",
  "name": "research-assistant",
  "displayName": "Research Assistant",
  "description": "Deep research on a topic using web search",
  "prompt": "# Research Assistant\n\nYou are a research specialist...",
  "category": "research",
  "icon": "🔍",
  "builtInTools": ["WebSearch", "WebFetch", "Read", "Write"],
  "organizationId": "org_abc123",
  "ownerId": "user_xyz",
  "visibility": "organization",
  "version": 1,
  "createdAt": "2024-01-15T10:00:00Z"
}

Index Requis:

  • Composite: organizationId + visibility + createdAt (desc)
  • category + visibility pour la marketplace
  • featured pour les skills en vedette

plugins

Packages de configuration (dépôts GitHub) qui regroupent des commandes, des agents, des skills et des serveurs MCP.

type PluginVisibility = 'private' | 'organization' | 'public'
type PluginSourceType = 'github' | 'url' | 'path'

interface Plugin {
  id: string

  // Basic info
  name: string                       // kebab-case identifier
  displayName: string
  description: string
  icon?: string

  // Source
  source: {
    source: PluginSourceType
    repo?: string                    // owner/repo (GitHub)
    url?: string                     // Git URL
    path?: string                    // Local path
    ref?: string                     // branch/tag/commit
  }

  // Manifest (cached from repo)
  manifest?: {
    name: string
    description: string
    version: string
    author?: {
      name: string
      email?: string
      url?: string
    }
    homepage?: string
    repository?: string
    license?: string
    keywords?: string[]
    category?: string
    // Component flags
    commands?: boolean | string[]
    agents?: boolean | string[]
    skills?: boolean | string[]
    hooks?: boolean | string[]
    mcpServers?: boolean | string[]
  }

  // Ownership
  organizationId: string
  ownerId: string
  visibility: PluginVisibility

  // Stats
  installCount?: number
  starCount?: number

  // Verification
  verified?: boolean
  featured?: boolean

  // Timestamps
  createdAt: Date
  updatedAt: Date

  // Soft delete
  isDeleted?: boolean
  deletedAt?: Date | null
}

Collection Associée: pluginInstallations/{installationId} suit les installations par espace.

Index Requis:

  • Composite: organizationId + visibility + createdAt (desc)
  • visibility pour la marketplace
  • featured pour les plugins en vedette

personal_access_tokens

Tokens API pour l’accès programmatique.

interface PersonalAccessToken {
  id: string                         // Token ID (not the token itself)
  uid: string                        // Owner user ID
  organizationId: string
  name: string                       // User-defined name

  // Token hash (actual token never stored)
  tokenHash: string

  // Permissions
  scopes: string[]                   // e.g., ["read:spaces", "write:chats"]

  // Usage tracking
  lastUsedAt?: Date | null
  createdAt: Date
  expiresAt?: Date | null
}

Sécurité: Géré côté serveur. Les utilisateurs peuvent uniquement lire/supprimer leurs propres tokens.

Index Requis:

  • Composite: uid + organizationId
  • tokenHash pour l’authentification (unique)

invites

Gestion des invitations d’organisation.

interface Invite {
  id: string
  organizationId: string
  email: string
  role: 'member' | 'admin'
  invitedBy: string                  // User ID
  status: 'pending' | 'accepted' | 'expired'
  expiresAt: Date
  createdAt: Date
}

Sécurité: Lecture publique (nécessaire pour les liens d’invitation). Seuls les propriétaires d’organisation peuvent créer/mettre à jour/supprimer.


Collections Exclusivement Côté Serveur

Ces collections n’ont pas d’accès client et sont gérées exclusivement via le SDK Admin:

authStates

Mise en cache des niveaux d’authentification pour les performances.

cli_pending_auth

Codes OAuth temporaires pour l’authentification CLI.

cli_refresh_tokens

Tokens de rafraîchissement pour l’outil CLI.

partner_connections

Clés RSA pour l’authentification des partenaires (clés privées chiffrées).

partner_organizations

Lie les partenaires aux organisations.

usageLogs

Journaux d’utilisation détaillés par requête.


Collections en Lecture Seule (Client)

usageAggregates

Métriques d’utilisation agrégées. Structure: usageAggregates/{period}/data/{orgId}_{dateKey}

interface UsageAggregate {
  organizationId: string
  period: 'daily' | 'monthly'
  dateKey: string                    // YYYY-MM-DD

  // Metrics
  totalRequests: number
  totalTokensInput: number
  totalTokensOutput: number
  totalCost: number

  // Breakdowns
  byAgent: Record<string, {
    requests: number
    tokensInput: number
    tokensOutput: number
    cost: number
  }>
}

Sécurité: Les membres de l’organisation peuvent lire. L’écriture est côté serveur uniquement.

creditTransactions

Historique du solde de crédits.

interface CreditTransaction {
  id: string
  organizationId: string
  type: 'purchase' | 'usage' | 'refund' | 'grant'
  amount: number                     // Positive or negative
  balance: number                    // Running balance
  description: string
  metadata?: any
  createdAt: Date
}

Sécurité: Les membres de l’organisation peuvent lire. L’écriture est côté serveur uniquement.


Résumé des Index

Index Composites Requis

// Organizations
organizations: members (array-contains)

// Characters
character: organizationId + isTemplate + createdAt (desc)
character: ownerId + visibility

// Spaces
spaces: organizationId + updatedAt (desc)

// Chats
chats: organizationId + spaceId + updatedAt (desc)

// Tasks
tasks: organizationId + status + createdAt (desc)

// Executions
executions: organizationId + chatId + startedAt (desc)

// Missions
missions: organizationId + status + createdAt (desc)

// Scheduled Tasks
scheduledTasks: organizationId + isActive + nextRun (asc)

// Skills
skills: organizationId + visibility + createdAt (desc)
skills: category + visibility

// Plugins
plugins: organizationId + visibility + createdAt (desc)

// Personal Access Tokens
personal_access_tokens: uid + organizationId

Index sur Champ Unique

La plupart des champs createdAt, updatedAt, ownerId, visibility sont automatiquement indexés par Firestore.


Résumé des Règles de Sécurité

Modèles d’Accès

Propriétaire uniquement:

  • users - Seul le propriétaire peut lire/mettre à jour
  • userProfiles - Seul le propriétaire peut lire/mettre à jour
  • personal_access_tokens - Le propriétaire peut lire/supprimer

Accès Membres de l’Organisation:

  • organizations - Les membres peuvent lire
  • spaces - Filtré par visibilité
  • chats - Propriétaire ou membres de l’organisation
  • tasks - Membres de l’organisation
  • executions - Membres de l’organisation
  • missions - Membres de l’organisation
  • scheduledTasks - Membres de l’organisation

Basé sur la Visibilité:

  • character - Public/organisation/privé/non répertorié
  • skills - Public/organisation/privé
  • plugins - Public/organisation/privé

Serveur Uniquement:

  • authStates
  • cli_pending_auth
  • cli_refresh_tokens
  • partner_connections
  • partner_organizations
  • usageLogs

Lecture Seule (Client):

  • usageAggregates - Les membres de l’organisation peuvent lire
  • creditTransactions - Les membres de l’organisation peuvent lire

Champs Chiffrés

Ces champs sont chiffrés côté serveur et ne peuvent pas être modifiés depuis le client:

  • organizations.encryptedAnthropicApiKey
  • userProfiles.encryptedAnthropicApiKey
  • userProfiles.encryptedClaudeCodeOAuthToken

Les mises à jour côté client tentant de modifier ces champs seront rejetées par les règles de sécurité.


Bonnes Pratiques

Contraintes Firestore

  1. Pas de Valeurs Undefined: Firestore n’autorise pas undefined. Utilisez null ou omettez le champ.
// ❌ Mauvais
await updateDoc(docRef, { optional: undefined })

// ✅ Bon
await updateDoc(docRef, { optional: value ?? null })

// ✅ Meilleur - omettre undefined
const cleanData = Object.fromEntries(
  Object.entries(data).filter(([_, v]) => v !== undefined)
)
  1. Utilisez les Server Timestamps: Utilisez toujours serverTimestamp() pour la cohérence.
import { serverTimestamp } from 'firebase/firestore'

await addDoc(collection, {
  ...data,
  createdAt: serverTimestamp()
})
  1. Nettoyez les Abonnements: Toujours se désabonner des écouteurs en temps réel.
const unsubscribe = onSnapshot(query, snapshot => { })

onUnmounted(() => {
  if (unsubscribe) unsubscribe()
})

Suppressions Douces

La plupart des collections utilisent des suppressions douces pour la récupération:

// Soft delete
await updateDoc(docRef, {
  isDeleted: true,
  deletedAt: new Date()
})

// Restore
await updateDoc(docRef, {
  isDeleted: false,
  deletedAt: null
})

Mises à Jour Optimistes

Pour une meilleure UX, l’application utilise des mises à jour optimistes pour les créations:

// Add optimistic item immediately
optimisticItems.value.push(newItem)

// Create in Firestore
const docRef = await addDoc(collection, data)

// Snapshot listener updates with real data

Notes de Migration

Champs Hérités

Certaines collections ont des champs obsolètes maintenus pour la rétrocompatibilité:

Characters:

  • systemPrompt → Utilisez system_message
  • skills (inline) → Utilisez skillIds (références)
  • tools → Utilisez advanced_tools

UserProfiles:

  • anthropicApiKey (texte brut) → Utilisez encryptedAnthropicApiKey
  • claudeCodeOAuthToken (texte brut) → Utilisez encryptedClaudeCodeOAuthToken

Migration de Données

Lors de la migration de données, toujours:

  1. Créer les nouveaux champs à côté des anciens
  2. Mettre à jour le code applicatif pour lire les deux
  3. Tâche de fond migre les données
  4. Supprimer les références aux anciens champs après migration complète
  5. Conserver les champs obsolètes documentés pendant 6 mois minimum