A-06 Certified 

The Living Ledger

A legacy Flutter application for tracking real estate wealth. Firebase-coupled, untyped, with a silent data model bug that had been corrupting cash flow calculations since the first version shipped. The founding team knew the numbers felt wrong. They could not prove it. Three Phoenix Runtime agents ran a full archaeological survey, rebuilt the stack from the ground up in React 18 and Dexie.js, produced 51 passing tests, and issued A-06 certification. The app is live. The numbers are right.

3
Agents Run
51
Tests Passing
0
Firebase Deps
A-06
Certification
React 18
Stack
Live
w2t.semanticintent.ai

Pipeline via Phoenix Runtime — 7-Agent Modernization™

What the original app was carrying

The application was a Flutter-based real estate wealth tracker built by a three-person founding team. It tracked investment properties, mortgages, rent income, and a portfolio overview across CAD and USD. It worked well enough to use. It did not work well enough to trust.

The core problem was structural. The original stack had no TypeScript, no unit tests, and no separation between data access and display logic. Firebase provided authentication and storage — a coupling that meant no offline access, no data portability, and no ability to run the application without an active cloud connection. The founding team had chosen Firebase for speed. The debt compounded quietly.

Data

The frequencyFactor bug

Additional property expenses carried a frequencyFactor field intended to normalise recurring payments to monthly equivalents. One-time expenses stored frequencyFactor: 0. The division operation used this value as a divisor. Division by zero returned NaN, which propagated silently through every cash flow calculation upstream. The cash flow total shown in the portfolio overview was wrong by a compounding factor. The team knew the numbers felt off. The bug had no error, no warning, and no test to catch it.

Impact: portfolio cash flow total corrupted — silent, no stack trace.
Types

No type surface to reason from

The original codebase carried no TypeScript types. Property data, borrowing records, and expense entries were passed as untyped maps. The archaeological survey found that the typed store name for cascade deletes in the new system — db.additionalExpenses — had been written as the string 'additionalProperties' in the original. A category error invisible without types. The absence of a type surface meant every refactoring decision had to be made against runtime behaviour rather than schema guarantees.

Impact: cascade delete silently targeting wrong store — data integrity risk.
Arch

Firebase coupling with no migration path

The original application required Firebase authentication to function. All user data lived in Firestore. There was no export mechanism, no local fallback, and no way to use the application without an active cloud session. For a personal finance tool handling investment property data, this was a structural misfit: the data is sensitive, the use is personal, and the dependency on an external service introduced both a privacy surface and a single point of failure the user could not control.

Impact: no offline access, no data portability, cloud dependency on sensitive personal data.

“The numbers feel wrong but I can’t prove it.”

— Founding team, pre-modernisation

The pipeline

Phoenix Runtime deploys a sequential seven-agent pipeline. Each agent has a defined scope and produces structured outputs that downstream agents consume. For this engagement, three agents ran: A-04 (Archaeological Survey), A-05 (Engineering, two passes), and A-06 (Validator/Certifier). The earlier agents — A-00 through A-03 — were skipped via an episode declaration that recorded the stack pivot decision as first-class pipeline state.

Pipeline A-00 Intent A-01–03 Skipped A-04 Survey A-05 Build ×2 A-06 Certified
A-04

Archaeological Survey — what was there

A-04 catalogued the full original codebase: data model, Firebase schema, screen inventory, business logic distribution, and known bugs. The survey produced the definitive finding that the frequencyFactor division-by-zero was the root cause of cash flow corruption. It identified the typed store name mismatch. It documented the LTV calculation producing NaN when market value was zero. It confirmed that no unit test surface existed anywhere in the project. The survey output became the A-05 work brief.

Output: full bug inventory, data model map, zero test surface confirmed.
A-05

Engineering — two passes, two scopes

A-05 ran twice. Pass 4 extracted normaliseToMonthly(amount, frequency) as the single source of truth for payment frequency normalisation — replacing the division-by-zero with an explicit 0 return for one-time expenses. It extracted calculateLtv(totalBorrowed, marketValue) as a pure function with a zero-division guard. Both functions were placed in dedicated modules, testable in isolation. Pass 5 built the test suite: 51 tests across 4 files using Vitest and fake-indexeddb, covering normalisation, LTV, export/import round-trips, and overview calculations. All 51 passed before A-06 ran.

Output: normaliseToMonthly(), calculateLtv(), 51 tests passing, 0 failures.
A-06

Validator — independent certification

A-06 ran as an independent agent with no prior context from A-04 or A-05 — a deliberate constraint that prevents the certifier from inheriting the builder’s assumptions. It reviewed the full codebase, ran the test suite, and issued five findings: the exchange rate direction convention needed documentation; stub data used frequencyFactor: 1 for annual expenses (should be 12); the useProperties() hook returned oldest-first; the vite.config.ts import was not from vitest/config; email validation was absent on the profile screen. All five were non-blocking. All five were fixed before deployment. Certification issued.

Output: A-06 CERTIFIED — 5 non-blocking findings, all resolved.

What the new stack is

The rebuilt application runs on React 18, TypeScript, Vite 5, Tailwind CSS, Zustand, and Dexie.js. All data lives in IndexedDB on the user’s device. There is no authentication layer, no cloud dependency, and no data leaving the browser. The founding team’s original instinct — that a personal finance tool should be personal — is now architectural fact.

The local-first decision shaped every subsequent choice. Dexie.js provides a typed IndexedDB wrapper with reactive hooks. Data is exported as a single JSON file and imported with schema validation. The exchange rate between CAD and USD is user-controlled and stored locally. Address lookup uses Nominatim/OpenStreetMap with a 1000ms debounce and no API key. The application works offline, on a plane, without a Cloudflare edge, without a Firebase project, and without an account.

A-06 Certified
Certification verdict, April 2026: The modernisation is structurally sound. The frequencyFactor division-by-zero is resolved. LTV calculation is guarded. The test suite provides meaningful regression coverage. The local-first architecture is the correct fit for the use case. Five non-blocking findings were identified and resolved before this certification was issued. The application is production-ready.
Phoenix TracePipeline state — machine-readable record of the run
-- ep-001.sil — Stack Pivot Episode Declaration
EPISODE "stack-pivot"
DATE "2026-04"
AFFECTS A-04, A-05, A-06
SKIPS   A-00, A-01, A-02, A-03

-- Original: Flutter + Firebase + Dart
-- Rebuilt:  React 18 + Dexie.js + TypeScript + Vite 5
-- Reason:   Local-first architecture. No auth. No cloud coupling.
--           The data is personal. The stack should reflect that.

NOTE "A-04 reads the original Flutter codebase as archaeological source."
NOTE "A-05 builds against the new stack from scratch."
NOTE "A-06 certifies the new build independently."

-- .phoenix/state.json (abridged)
{
  "project": "wealth2track",
  "agents": [
    { "id": "a-04", "name": "Archaeologist",
      "status": "complete", "confidence": "high",
      "outputCount": 3 },
    { "id": "a-05", "name": "Engineer",
      "status": "complete", "confidence": "high",
      "outputCount": 5 },
    { "id": "a-06", "name": "Validator",
      "status": "certified", "confidence": "high",
      "findings": 5, "blocking": 0 }
  ]
}
SURVEY A-04 identified the frequencyFactor division-by-zero as the root cause of the cash flow corruption the founding team had sensed but could not locate. One-time expenses stored frequencyFactor: 0. The existing code divided payment amounts by this value without a guard. The result was NaN, which propagated silently into every upstream total. The survey also found the typed store name bug ('additionalProperties' vs db.additionalExpenses) and confirmed there was no unit test surface anywhere in the project. Zero. No tests meant the bugs had no mechanism for detection short of the founding team noticing that the numbers felt wrong — which they did, and which was correct.
BUILD A-05 Pass 4 extracted normaliseToMonthly(amount, frequency) as the single source of truth for payment normalisation. The function handles six frequency types — Monthly, Bi-weekly, Weekly, Annual, Quarterly, Semi-monthly — and returns 0 explicitly for one-time expenses, eliminating the division-by-zero permanently. calculateLtv(totalBorrowed, marketValue) was extracted as a pure testable function with a guard returning 0 when market value is zero or less. Pass 5 built the test suite: 51 tests, 4 files, Vitest, fake-indexeddb. Tests covered normalisation across all six frequencies, LTV edge cases, export/import round-trip fidelity, and overview calculation correctness. All 51 passed before A-06 ran a single check.
CERTIFY A-06 ran without context from A-04 or A-05 — the independence constraint is the point. A certifier that inherits the builder’s mental model cannot find what the builder normalised away. Five findings: (1) exchange rate direction convention undocumented; (2) stub annual expenses used frequencyFactor: 1 instead of 12, producing a stub cash flow total of −$15,180 instead of the correct −$4,455; (3) useProperties() returned oldest-first; (4) vite.config.ts imported defineConfig from 'vite' instead of 'vitest/config'; (5) profile screen accepted any string as email with no validation. Zero blocking. All five resolved. Certification issued.
DEPLOY The application was deployed to Cloudflare Pages at w2t.semanticintent.ai following certification. The build produces a 432KB JS bundle (125KB gzip), a 19KB CSS bundle (4.67KB gzip), and a single _redirects rule for client-side routing. Total upload: 5 files. Deployment time: under 3 seconds. The app loads, runs, and stores data entirely in the browser. No server, no session, no account. The founding team can hand the URL to anyone and the app will work — including offline, after the first load.

What this engagement reveals

Silent bugs are the most expensive kind

The frequencyFactor division-by-zero produced no error, no warning, and no stack trace. It produced wrong numbers — plausible-looking, slightly-off numbers that a user would notice only by feel. The founding team noticed. They had no mechanism to prove it. A-04’s archaeological mandate — read everything, document everything, trust nothing — is specifically designed to surface this class of bug. You cannot fix what you cannot locate. You cannot locate what you cannot name.

The test suite is the most important A-05 output

A-05 produced normalised functions, a corrected data model, a full React rebuild, and 51 tests. The tests are the most durable output. The functions will be refactored. The data model will evolve. The tests will catch both. A codebase with zero tests is a codebase where every change is a bet. A-05’s second pass exists specifically to convert the bet into a guarantee. The 51 tests are not a metric. They are the mechanism by which future changes can be made without fear.

Local-first is the correct architecture for personal finance

Firebase was chosen for speed. The debt was privacy, portability, and offline access. A personal finance application — tracking investment properties, mortgage balances, and net worth — handles data that users should own. Local-first with Dexie.js and IndexedDB means the data lives on the user’s device, exports to a JSON file they control, and requires no account to access. The architecture is not a technical preference. It is the correct answer to the question: whose data is this?

Independent certification finds what builders normalise away

A-06’s five findings were not failures of A-05 — they were findings that A-05 could not structurally produce. The stub annual expense error required looking at the data model with fresh eyes. The email validation gap required asking “what is missing” rather than “what is broken.” The certifier’s independence constraint is not a formality. It is the mechanism that makes certification mean something. A builder who certifies their own work is auditing their own assumptions. That is not an audit.

The numbers were wrong. Now they’re right. The stack is local-first. The tests are there. The app is live.

Phoenix Runtime runs the same pipeline on any legacy codebase. Three agents. One engagement. A-06 certification before deployment.