Back to flin
flin

Historique des versions et requêtes de voyage dans le temps

Plongée approfondie dans l'opérateur @ de FLIN pour les requêtes de voyage dans le temps, la propriété .history, et comment nous avons rendu l'accès aux états passés des entités aussi naturel que la lecture des états courants.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 7 min flin
EN/ FR/ ES
flintime-travelhistoryat-timequeries

Le modèle temporel décrit dans l'article précédent stocke chaque version de chaque entité. Mais du stockage sans accès n'est qu'un usage coûteux du disque. La véritable puissance du système temporel de FLIN réside dans la façon dont les développeurs l'interrogent : l'opérateur @ pour l'accès ponctuel, la propriété .history pour les chronologies complètes, et un ensemble de mots-clés temporels qui rendent les requêtes courantes aussi lisibles que de l'anglais.

Cet article couvre trois fonctionnalités interconnectées construites au cours des sessions 012, 075 et 081 -- l'opérateur @, la propriété .history, et l'infrastructure qui rend les deux naturels.

L'opérateur @ : le voyage dans le temps en deux caractères

L'opérateur @ est la syntaxe de FLIN pour accéder aux états passés d'une entité. Il prend une entité à gauche et une référence temporelle à droite, et retourne l'entité telle qu'elle existait à ce moment.

Trois formes de référence temporelle sont supportées.

Accès par version relative

La forme la plus courante. Un entier négatif compte en arrière depuis la version courante :

flinuser @ -1      // Previous version
user @ -2      // Two versions ago
user @ -10     // Ten versions ago
user @ 0       // First version ever
user @ 1       // Second version

Accès par temps absolu

Une chaîne de date interroge l'état de l'entité à un moment précis :

flinuser @ "2024-01-15"                    // At specific date
user @ "2024-01-15T14:30:00Z"          // At specific datetime

Accès par mot-clé

Sept mots-clés intégrés couvrent les requêtes temporelles les plus courantes :

flinuser @ yesterday
user @ last_week
user @ last_month
Mot-cléDescription
nowMoment actuel
todayDébut d'aujourd'hui (00:00:00 UTC)
yesterdayDébut d'hier
tomorrowDébut de demain
last_weekIl y a sept jours
last_monthIl y a trente jours
last_yearIl y a trois cent soixante-cinq jours

Les mots-clés ne sont pas du sucre syntaxique pour des chaînes de date. Ce sont des jetons de première classe dans le lexeur, analysés comme des noeuds AST dédiés, et compilés en un code temporel d'un seul octet dans le bytecode. user @ yesterday se compile en deux instructions : charger l'utilisateur, puis AtTime(0x03). Pas d'analyse de chaîne à l'exécution. Pas d'arithmétique de dates dans le code applicatif.

Construction de l'opérateur @ : session 012

L'opérateur @ a été implémenté en session 012, une session de cinquante minutes qui a établi l'infrastructure de requêtes temporelles. La structure de données centrale était un struct EntityVersion stocké dans une carte d'historique de versions à l'intérieur de la VM :

rust/// Historical version of an entity for time-travel queries
#[derive(Debug, Clone)]
pub struct EntityVersion {
    pub version: u64,
    pub timestamp: i64,
    pub fields: HashMap<String, Value>,
}

// VM struct additions
/// Key: (entity_type, entity_id), Value: historical versions (oldest first)
version_history: HashMap<(String, u64), Vec<EntityVersion>>,

L'opcode save a été modifié pour prendre un instantané de l'état courant avant d'écrire la nouvelle version. Ce pattern « copier avant écrire » signifie que le tableau d'historique contient toujours les versions passées uniquement. La version courante vit dans le stockage d'entités, et la propriété .history l'ajoute lors de la construction de la chronologie complète.

La session a aussi ajouté des méthodes d'élagage de l'historique de versions, anticipant le coût de stockage d'un historique illimité :

rust/// Prune version history older than timestamp
pub fn prune_versions_before(&mut self, timestamp: i64) -> usize

/// Keep only N most recent versions per entity
pub fn prune_versions_keep_last(&mut self, count: usize) -> usize

/// Get total count of stored versions
pub fn version_history_count(&self) -> usize

Dix nouveaux tests. Trois cent un tests passant au total.

Correction d'AtTime : quand les mots-clés ne fonctionnaient pas

Il y avait un écart embarrassant entre « le modèle temporel existe » et « le modèle temporel fonctionne ». La session 012 a implémenté l'opcode AtTime comme un stub qui retournait simplement l'entité inchangée. Si vous écriviez user @ yesterday, vous obteniez l'utilisateur actuel.

La correction nécessitait de convertir les mots-clés temporels en horodatages réels :

rustOpCode::AtTime => {
    let time_code = self.read_u8(code);
    let entity_val = self.pop()?;

    let target_timestamp = if let Some(tc) = TimeCode::from_byte(time_code) {
        let now = current_timestamp_ms();
        match tc {
            TimeCode::Now => now,
            TimeCode::Today => {
                let secs_today = (now / 1000) - ((now / 1000) % 86400);
                secs_today * 1000
            }
            TimeCode::Yesterday => {
                let secs_today = (now / 1000) - ((now / 1000) % 86400);
                (secs_today - 86400) * 1000
            }
            TimeCode::LastWeek => now - (7 * 24 * 60 * 60 * 1000),
            TimeCode::LastMonth => now - (30 * 24 * 60 * 60 * 1000),
            TimeCode::LastYear => now - (365 * 24 * 60 * 60 * 1000),
        }
    };
    // Find version at target timestamp using history lookup
}

La propriété .history : session 075

L'opérateur @ donne une seule version. La propriété .history les donne toutes. La session 075 a révélé que chaque couche de la pile avait déjà le support -- les seuls problèmes étaient deux bugs qui faisaient produire des résultats incorrects.

Le bug de duplication : ZeroCore ajoutait la version initiale à l'historique, puis la VM l'ajoutait de nouveau. La correction a établi la règle : l'historique ne contient que les versions passées. La VM est responsable de l'ajout de la version courante.

Le bug des entités non sauvegardées : les entités avec id == 0 retournaient [current] au lieu de []. La correction a ajouté une vérification is_saved.

L'opérateur @ retourne un optionnel

L'accès temporel retourne une valeur optionnelle. La version demandée pourrait ne pas exister :

flinold_user = user @ "2020-01-01"        // User? (might not exist)

{if old_user}
    <p>User existed: {old_user.name}</p>
{else}
    <p>User did not exist yet</p>
{/if}

Cela force les développeurs à gérer le cas d'absence, empêchant les exceptions de pointeur nul.

Comparer à travers le temps

L'opérateur @ se compose naturellement avec les expressions de comparaison :

flin// Did the name change since the last version?
name_changed = user.name != (user @ -1).name

// Did the price increase since yesterday?
price_increased = product.price > (product @ yesterday).price

Ces patterns semblent évidents rétrospectivement, mais ils ne sont possibles que parce que @ retourne une entité complète avec tous les champs remplis.

Le tableau complet

À la fin de la session 081, le système d'accès temporel de FLIN était complet :

  • Trois formes d'accès @ (relatif, absolu, mot-clé).
  • Sept mots-clés temporels.
  • La propriété .history pour les chronologies complètes.
  • Des types de retour optionnels empêchant les erreurs de pointeur nul.
  • Des métadonnées de version sur chaque instantané historique.
  • Une couverture de tests bout en bout du lexeur au HTML rendu.

Pas de table d'audit. Pas de middleware de changelog. Pas de framework d'event sourcing. Deux caractères et un mot-clé.


Ceci est la partie 2 de la série sur le modèle temporel de « How We Built FLIN », documentant la conception et l'implémentation du système de requêtes de voyage dans le temps de FLIN.

Navigation de la série : - [046] Every Entity Remembers Everything: The Temporal Model - [047] Version History and Time Travel Queries (vous êtes ici) - [048] Temporal Integration: From Bugs to 100% Test Coverage - [049] Destroy and Restore: Soft Deletes Done Right - [050] Temporal Filtering and Ordering

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude thales

Treize agents, quarante-trois minutes : la première session Workflow de Claude Fable 5, et ce qu'un script d'orchestration déterministe change aux builds multi-agents

Un prompt, treize agents, quarante-trois minutes : la première session de production avec Claude Fable 5 et l'outil Workflow de Claude Code a livré un site web de production complet de sept pages plus un endpoint backend de capture de leads, en un seul commit. Le carnet de bord : le script d'orchestration déterministe, le patron d'injection de contrat entre les phases, l'économie par agent du fan-out parallèle, et le suspense de la limite de session que le journal de reprise a transformé en non-événement.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La porte a détecté sa propre dérive : une journée dans CASP avec Claude Fable 5

Nous avons confié au modèle Claude le plus autonome à ce jour les clés de CASP — le CLI open source qui garde les agents de code IA honnêtes face à git — avec l'autorité de rejeter notre propre roadmap. Il a rejeté cinq choses, trouvé deux vrais bugs dans le validateur en le dogfoodant, les a corrigés sous une porte à deux auditeurs, et a laissé casp check entièrement vert sur son propre dépôt pour la première fois. CASP 0.3.0 en est le résultat.

16 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9
Thales & Claude zerosuite

La transplantation du CASP : comment la discipline des six fichiers est passée de Conductor à un ERP transport anti-fraude, ce que la compétence /next ajoute quand l'opérateur tape juste « next », et pourquoi le coût d'une dérive du CASP grimpe quand le projet, c'est l'argent des autres

La discipline du CASP qui a piloté trente-cinq sessions de Conductor est agnostique au produit. Le carnet de bord de sa transplantation sur KASSIA, un ERP transport anti-fraude pour un exploitant de flotte en Côte d'Ivoire : ce qui a migré, ce qui n'a pas migré (le validateur sur mesure — et ce que son absence coûte), ce que la compétence /next ajoute quand l'opérateur tape un seul mot, et là où le CASP s'arrête — le bug de déploiement qu'il ne pouvait pas voir parce qu'il enregistre l'intention, pas la réalité de l'infrastructure.

23 min Jun 8, 2026
kassiaerp-kassia-transport-logistiquezerosuiteCASP +15