Back to flin
flin

The Temporal Debugging Marathon

20 sessions spent debugging and perfecting the temporal model.

Thales & Claude | March 25, 2026 10 min flin
flintemporaldebuggingmarathonsessions

FLIN's temporal system -- the ability to query any entity at any point in its history using the @ operator -- is the feature that gives the language its name. "E flin nu" means "it remembers things" in Fongbe. The elephant never forgets. Neither does FlinDB.

But making the elephant's memory actually work required 21 sessions of focused debugging across three days: Sessions 068 through 088, January 6-9, 2026. It was the longest single-feature sprint of the entire project, and it revealed something important about building software with an AI CTO: the AI can build features quickly, but debugging requires a different kind of intelligence.

The Discovery: Session 068

Session 068 began as a routine audit. The temporal system had been on the tracking board at 5/160 tasks (3%) complete for weeks. Five tasks out of one hundred sixty. That number felt wrong -- code existed in the VM for temporal opcodes, the parser handled the @ operator, the database stored version history. The tracking said almost nothing worked. The code said otherwise.

The comprehensive audit in Session 068 revealed one of the project's most significant discoveries: the temporal system was actually at 60/160 tasks (37.5%). Progress was twelve times higher than documented. The tracking documents had not been updated as features were implemented across multiple sessions.

Before Session 068 Audit:
    Documented:  5/160 tasks (3%)
    Actual:     60/160 tasks (37.5%)
    Gap:        55 tasks undocumented

What Actually Worked: Lexer: @ token + all time keywords Parser: Expr::Temporal AST node Type Checker: check_temporal() validation Code Gen: emit_temporal() bytecode Bytecode: AtVersion, AtTime, AtDate, History opcodes VM: All temporal opcodes implemented Database: get_history(), soft delete, version tracking ```

But "implemented" and "working correctly" are different things. Session 068 also found that OpCode::AtTime was a stub -- it received an entity and a time code but simply returned the entity unchanged. The @ operator compiled, but user @ yesterday returned the current user, not yesterday's version.

Fixing AtTime: The First Victory

The AtTime stub was the first concrete fix. The implementation needed to convert time keywords (yesterday, last_week, last_month) into Unix timestamps, then search the entity's version history for the version that was current at that timestamp.

OpCode::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 // ... history lookup implementation } ```

The type checker also needed a fix: it rejected date strings in temporal expressions. user @ "2024-01-15" failed type checking because the type checker only allowed Int and Time types as temporal references, not Text. Adding FlinType::Text to the allowed types was a one-line fix that unlocked an entire class of temporal queries.

After Session 068, all seven time keywords were functional: now, today, yesterday, tomorrow, last_week, last_month, last_year. The @ operator worked with relative versions (@ -1), time keywords (@ yesterday), and absolute dates (@ "2024-01-15"). Progress jumped to 60/160.

But 60/160 is only 37.5%. The marathon was just beginning.

Sessions 069-076: Integration Testing

The audit revealed a critical gap: only 1 of 22 integration tests existed. The temporal system had been built layer by layer -- lexer, parser, type checker, code generator, VM, database -- but never tested end-to-end. Each layer worked in isolation. Whether they worked together was unverified.

Sessions 069 through 076 were a systematic campaign to write integration tests and fix the bugs they uncovered.

Session 069 wrote the first integration tests and immediately found bugs. The none handling was broken -- when a temporal query found no matching version, the VM did not return none correctly. Session 070 fixed none handling. Session 071 implemented hard delete and restore, which required new keywords (destroy, restore) and careful interaction with the version history.

// The temporal lifecycle, fully tested by Session 076

entity User { name: text, email: text }

// Create user = User { name: "Juste", email: "[email protected]" } save user

// Modify (creates version 2) user.name = "Juste Gnimavo" save user

// Time travel v1 = user @ -1 // Version 1: name = "Juste" v2 = user @ -0 // Version 2: name = "Juste Gnimavo" (current) old = user @ yesterday // Version as of yesterday

// History all_versions = user.history // [v1, v2, ...]

// Soft delete (preserves history) delete user

// Hard delete (permanent, admin-only) destroy user

// Restore from history restored = restore(user @ -1) ```

Session 072 fixed version tracking -- versions were being counted incorrectly when an entity was deleted and recreated. Session 073 fixed a subtle bug where the version number in the EntityInstance did not match the actual number of versions in storage. Session 074 was a detour to fix HTML whitespace rendering, but Session 075 returned to temporal work with the .history property implementation.

Session 076 marked the first major milestone: temporal tests at 100%. Every basic temporal operation -- create, modify, query by version, query by time, soft delete, version tracking -- was tested and passing.

Sessions 077-083: Advanced Features

With the basics working, Sessions 077-083 pushed into advanced temporal capabilities.

Session 077 implemented the destroy and restore keywords at the VM level. destroy performs a permanent deletion that removes an entity from the database entirely -- unlike delete, which sets a soft-delete flag and preserves all history. restore takes a historical version and creates a new live entity from it.

Session 078 tackled time arithmetic -- the ability to add and subtract time durations from temporal references:

// Time arithmetic (Session 078)
user_3_days_ago = user @ (now - 3.days)
user_next_week = user @ (now + 1.week)

// Duration literals duration = 2.hours + 30.minutes offset = 90.days ```

Sessions 079-082 completed the temporal filtering and ordering system. Entities could now be queried with temporal conditions:

// Temporal filtering (Session 082)
recent_changes = User.where(updated_at > last_week)
old_records = User.where(created_at < "2025-01-01")
ordered = User.all.order(updated_at, "desc")

Session 083 added temporal comparison helpers -- utility functions that make it easy to compare entity versions:

// Comparison helpers (Session 083)
has_changed = user.changed_since(yesterday)
diff = user.diff(user @ -1)  // Returns changed fields
is_newer = user.newer_than(other_user)

Session 084-087: The Push to 95%

By Session 084, the temporal system was at roughly 80%. The remaining work was a mix of edge cases, performance optimizations, and comprehensive test coverage.

Sessions 084-086 completed bitemporal storage (tracking both the "valid time" when a fact was true in the real world and the "transaction time" when it was recorded in the database), time-based filters, and integration test coverage.

Session 087 completed temporal comparisons -- the final major category. With it, 10 of 11 temporal categories were at 100%. The overall progress hit 152/160 tasks: 95%.

Category Completion After Session 087:

TEMP-1: Core Soft Delete 5/5 100% TEMP-2: Temporal Access (@) 18/18 100% TEMP-3: Temporal Keywords 14/14 100% TEMP-4: History Queries 22/22 100% TEMP-5: Time Arithmetic 12/12 100% TEMP-6: Temporal Comparisons 10/10 100% TEMP-7: Time-Based Filters 15/15 100% TEMP-8: Hard Delete/Restore 12/12 100% TEMP-9: Retention Policies 0/10 0% TEMP-10: Bitemporal Storage 20/20 100% TEMP-11: Integration Tests 27/27 100%

Overall: 152/160 (95.0%) ```

The only incomplete category was TEMP-9: Retention Policies -- automatic cleanup of old version data. This was deliberately deferred as a low-priority optimization feature. The temporal system was production-ready without it.

Session 088: The Decision to Move On

Session 088 was not a coding session. It was a decision session. The temporal system sat at 95%. The remaining 5% (retention policies) was useful but not essential. Meanwhile, other FLIN features -- security, file storage, the admin console -- were waiting.

The decision was to declare victory and move on. This is the kind of judgment call that defines the CEO's role in the CEO + AI CTO model. An AI, left to its own optimization, might push for 100% completion. A human with product sense knows that 95% of the temporal system is worth less than 0% of the security system. Resources are finite, even when one of your team members is an AI.

The session log records the decision clearly: "95% temporal system completion is production-ready. Remaining 5% are nice-to-have optimization features. Other FLIN features may have higher ROI."

What the Marathon Taught Us

The temporal debugging marathon -- 21 sessions, 3 days, 37.5% to 95% -- taught three lessons that shaped the rest of FLIN's development.

Lesson 1: Audit before assuming. Session 068's discovery that progress was 12x higher than documented changed the entire approach to tracking. After the temporal marathon, every feature area received regular audits to ensure that tracking matched reality. The cost of inaccurate tracking is not just confusion -- it is misallocated effort.

Lesson 2: Integration tests find bugs that unit tests miss. The temporal system had solid unit tests for each layer. The lexer correctly tokenized @. The parser correctly built Expr::Temporal nodes. The VM correctly executed OpCode::AtTime. But the end-to-end path -- from source text through every compiler phase to database query and back -- had bugs that only integration tests could reveal. After the temporal marathon, integration tests became a standard deliverable for every feature.

Lesson 3: Debugging requires different skills than building. Building the temporal system's individual layers was fast -- the AI could implement a version tracking system or a time arithmetic engine in a single session. But debugging the interactions between layers required understanding the full system. Why does user @ -1 return none when version 1 definitely exists? The answer might be in the code generator (wrong opcode emitted), the VM (wrong stack ordering), or the database (wrong version comparison logic). Finding the answer requires tracing through all layers simultaneously.

// The full temporal execution path that had to work end-to-end

// 1. Lexer: "user @ yesterday" // -> [Identifier("user"), At, Keyword(Yesterday)]

// 2. Parser: // -> Expr::Temporal { expr: Ident("user"), time: Keyword(Yesterday) }

// 3. Type Checker: // -> Entity(User) @ Time -> Entity(User)

// 4. Code Generator: // -> LoadLocal(user), AtTime(0x03)

// 5. VM: // -> Pop entity, calculate yesterday's timestamp, // query history, find matching version, push result

// 6. Database: // -> get_history("User", id) -> find version where // valid_from <= yesterday < valid_to ```

The AI can implement each step. The human has to understand when and why the steps do not connect. That understanding is the debugging skill that makes the CEO + AI CTO model work for complex systems, not just simple features.

Twenty-one sessions. Three days. A temporal system that lets every FLIN entity remember its complete history, queryable by version number, time keyword, or absolute date. The elephant remembers.

---

This is Part 199 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO built a programming language from scratch.

Series Navigation: - [198] The FlinUI Sprint: 70 Components Overnight - [199] The Temporal Debugging Marathon (you are here) - [200] The Security Sprint: 18 Sessions

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles