An audit without a fix plan is just a list of complaints. The value of reading 186,252 lines of code is not in the reading -- it is in the systematic elimination of every defect you found. On January 29, 2026, the same day the audit completed, we created the FLIN Audit Fix Plan: a five-phase roadmap to resolve all 30 TODOs and 6 pre-audit bugs before the beta release.
The plan estimated eight weeks of work across four priority phases, followed by a four-week exhaustive re-audit. We completed the fixes in two days. Not because the plan was wrong, but because having every defect precisely located, categorized, and documented made the fixing almost mechanical. The hard part was the finding. The easy part was the surgery.
The Five Phases
The fix plan organized work into a strict dependency order. Critical fixes first, because they blocked basic functionality. High-priority fixes next, because they affected important features. Medium, then low. And finally, a complete re-audit to verify that the fixes did not introduce regressions.
PHASE 1: Critical Fixes (Planned: Week 1-2, Actual: Day 1)
|
PHASE 2: High Priority (Planned: Week 3-4, Actual: Day 1)
|
PHASE 3: Medium Priority (Planned: Week 5-6, Actual: Day 2)
|
PHASE 4: Low Priority (Planned: Week 7-8, Actual: Day 2)
|
PHASE 5: Exhaustive Audit (Planned: Week 9-12, Actual: Week 2+)
|
BETA RELEASEEach fix item had a standardized format: the problem statement with exact file and line number, the proposed solution with code sketches, the verification criteria, and references to the session logs where the feature was originally implemented. This format meant that each fix could be implemented without any exploration or investigation -- the audit had already done that work.
Phase 1: Critical Fixes
Five critical items that blocked core functionality.
FIX-001: Duplicate CreateMap Opcode. The most dramatic finding of the audit -- two separate OpCode::CreateMap handlers with different logic. The fix was adding one else if let Value::Text(s) branch to the run() version. Applied in Session 260, verified with 3,108 passing tests and 9 of 10 todo apps working.
FIX-001b: Native Button Click Handler. The renderer was accidentally executing click handler functions during rendering while checking for "passthrough" patterns. When a native had click={toggleTheme()}, the renderer called toggleTheme() during the render pass, corrupting VM state before the user ever clicked anything.
// The bug: rendering phase executed click handlers
if attr.name == "click" || attr.name == "change" {
// This line CALLS the function during rendering!
let value_str = eval_expr_to_string_with_scope(expr, vm, scope);
}// The fix: only evaluate identifiers and member expressions, not calls let is_passthrough_candidate = matches!( expr, Expr::Identifier { .. } | Expr::Member { .. } ); if is_passthrough_candidate { let value_str = eval_expr_to_string_with_scope(expr, vm, scope); } ```
FIX-002: storage.destroy_entity(). The destroy command removed entities from memory but not from disk. The fix added a Destroy variant to the WAL entry enum, implemented the physical file removal, and updated WAL recovery to handle destroy entries:
pub fn destroy_entity(
&mut self,
entity_type: &str,
entity_id: u64,
) -> StorageResult<()> {
// 1. Write WAL entry for auditability
let entry = WalEntry::destroy(entity_type, entity_id);
self.wal.append(&entry)?;// 2. Remove the data file immediately let filename = format!("{}_{}.json", entity_type, entity_id); let path = self.data_dir.join("data").join(filename); if path.exists() { fs::remove_file(path)?; }
Ok(()) } ```
FIX-003: CloseUpvalue. Closures that captured mutable variables did not properly close over upvalues when the enclosing scope exited. The fix emitted OpCode::CloseUpvalue in the end_scope() function for any captured variables.
FIX-006: Component Click Handler Serialization. The companion to FIX-001b -- while FIX-001b fixed native elements, FIX-006 fixed FlinUI components. The root cause was that component click handlers evaluated their arguments after the loop scope was gone, losing variable values like todo in a {for todo in todos} loop.
The solution was a three-part change that evaluated function arguments at prop extraction time (while still inside the loop), stored them in a __flin_click__fnName::args_json format, and parsed that format back during HTML generation.
Phase 2: High Priority
Three items affecting important features.
FIX-004: Transaction WAL Entry. Transactions worked in memory but did not write grouped WAL entries, making atomic crash recovery impossible. The fix added a WalEntry::TransactionCommit variant that bundled all operations in a single WAL record:
pub fn commit_transaction(&mut self, tx: &Transaction) -> StorageResult<()> {
// Collect all operations into a single atomic WAL entry
let ops: Vec<WalEntry> = tx.pending_saves
.iter()
.map(|s| WalEntry::from_save(s))
.chain(
tx.pending_deletes
.iter()
.map(|d| WalEntry::from_delete(d))
)
.collect();// Write as single transaction -- atomic on recovery self.storage.wal_append(&WalEntry::TransactionCommit { id: tx.id, operations: ops, })?;
Ok(()) } ```
FIX-005: Destructuring Codegen. The audit found that destructuring was already fully implemented in the emitter -- list, map, and tuple patterns with rest and default support. The TODO comment was stale, left over from an earlier session before the feature was completed. The fix was updating the comment.
FIX-007: TryUnwrap Opcode. The ? operator for error propagation did not have a dedicated opcode. The fix added ExtendedOpCode::TryUnwrap (0x02) with a VM handler that returns early with None or error values and unwraps success values.
Phase 3: Medium Priority
Five items affecting secondary features, all completed in a single session.
The typechecker destructuring fix (FIX-008) implemented proper type extraction for list, map, and rest patterns -- extracting element types from FlinType::List(inner), field types from entity schemas, and propagating list types through rest patterns.
The WebSocket features fix (FIX-009) added direct messaging infrastructure with per-connection channels, binary frame support, RFC 6455 fragmented message handling, and connection close capability.
The version pruning API (FIX-010) replaced stubbed-out methods with working implementations: prune_versions_before(timestamp), prune_versions_keep_last(count), prune_entity_versions_before(entity_type, timestamp), and clear_all_version_history().
The entity predicate filtering fix (FIX-011) made Entity.where(field == value) actually filter results instead of returning all entities. The emitter now parses binary comparison expressions at compile time and emits structured queries that the VM dispatches to ZEROCORE's fluent query API.
The S3 backend fix (FIX-012) implemented a complete StorageBackend trait implementation for AWS S3 and S3-compatible services, with content-addressable storage, zstd compression, and presigned URL support.
Phase 4: Low Priority
Eight items completed in Session 264, all minor. Console entity registry wiring. Confirmed-working format destructuring. Updated compiler warning message. Documented intentional design decision for list serialization. Removed stale TODO for already-implemented range opcode. Implemented list.where() with lambda predicates. Added union type computation for try/catch expressions. Added middleware next() call verification.
The Velocity of Precise Knowledge
The most striking aspect of the fix plan execution was its speed. Eight weeks of estimated work completed in five sessions across two days. This was not because the estimates were bad -- they were conservative and reasonable for exploratory fix work. The acceleration came from the audit itself.
When you know the exact file, the exact line number, the exact root cause, and the exact verification criteria for every defect, fixing becomes mechanical. There is no investigation phase. There is no "hunting for the bug." Every fix session started with opening the file, navigating to the line, applying the documented solution, and running the tests.
Session 260: FIX-001, FIX-001b (2 critical fixes)
Session 261: FIX-006 (1 high fix)
Session 262: FIX-002, FIX-003, FIX-004, FIX-005,
FIX-007, FIX-008, FIX-009, FIX-010,
FIX-011, FIX-012 (10 fixes in one session)
Session 263: Typechecker and WebSocket verification
Session 264: FIX-013 through FIX-020 (8 low-priority fixes)Session 262 was the most productive -- ten fixes in a single session. This was possible because the medium-priority items were well-isolated changes that did not interact with each other. Each fix touched a different subsystem, and the test suite confirmed after each one that nothing else had broken.
The Progress Tracker
The fix plan included a progress tracker that was updated after each session:
Fix ID Priority Status Date
FIX-001 CRITICAL FIXED 2026-01-29
FIX-001b CRITICAL FIXED 2026-01-29
FIX-002 CRITICAL FIXED 2026-01-30
FIX-003 CRITICAL FIXED 2026-01-30
FIX-004 HIGH FIXED 2026-01-30
FIX-005 HIGH FIXED 2026-01-30
FIX-006 HIGH FIXED 2026-01-29
FIX-007 HIGH FIXED 2026-01-30
FIX-008 MEDIUM FIXED 2026-01-30
FIX-009 MEDIUM FIXED 2026-01-30
FIX-010 MEDIUM FIXED 2026-01-30
FIX-011 MEDIUM FIXED 2026-01-30
FIX-012 MEDIUM FIXED 2026-01-30
FIX-013 LOW FIXED 2026-01-30
FIX-014 LOW FIXED 2026-01-30
FIX-015 LOW FIXED 2026-01-30
FIX-016 LOW FIXED 2026-01-30
FIX-017 LOW FIXED 2026-01-30
FIX-018 LOW FIXED 2026-01-30
FIX-019 LOW FIXED 2026-01-30
FIX-020 LOW FIXED 2026-01-30Twenty-one fix items. All complete. Five critical, three high, five medium, eight low. All 3,117 tests passing after the final session. The codebase was clean.
What Came Next
With all fixes applied, Phase 5 began: the exhaustive re-audit. This was not just a re-read of the fixed code -- it was a feature-by-feature verification of every FLIN language feature documented in the specification. Every variable declaration, every event handler, every entity operation, every route guard, every template directive. The re-audit would confirm that the fixes were correct and that no regressions had been introduced.
But Phase 5 also revealed new gaps that the first audit had not caught -- gaps in opcode coverage that would lead to the Session 273 discovery of execute_until_return's 35% coverage rate. The audit was not a one-time event. It was the beginning of a continuous verification practice that would accompany FLIN through its beta launch and beyond.
---
This is Part 149 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO designed and built a programming language from scratch.
Series Navigation: - [148] 30 TODOs, 5 Production Panics, 0 Security Issues - [149] The Audit Fix Plan (you are here) - [150] Function Audit Day 7 Complete