Skip to content

Cookbook: Legacy Codebase Migration ​

Migrating an enterprise application to a new framework is usually a multi-year nightmare. You have to rewrite the entire application from the ground up, run it in parallel, and pray you didn't miss any edge cases.

Carotene flips this model upside down using Reverse Blueprint Extraction. You can ingest your legacy codebase, generate the architectural guardrails, and selectively hand over ownership to the AI function by function, file by file.

Here is how you onboard a legacy API into the Carotene ecosystem.

1. Introspect the Codebase ​

Let's say you have an aging Express.js/Sequelize codebase governing your company's billing system. You start by pointing the Carotene CLI at your source folder.

bash
$ carrot introspect ./legacy-billing-api/src

Carotene's AI engine analyzes your routes, your database models, and your middleware. In a few minutes, it generates a billing.carrot file that maps your exact physical architecture into Carotene's strict DSL.

2. Review the Generated Blueprint ​

When you open the generated billing.carrot file, you will notice two things:

  1. Carotene successfully mapped your data shapes and Zero-Trust boundaries.
  2. Every execution block is protected by the @manual decorator.
dart
domain Billing {
  backend BillingAPI {
    
    store {
      model Invoice {
        id: UUID
        amount: Float
        status: String
      }
    }

    // Carotene flagged this as manual. It recognizes the inputs, outputs,
    // and database interactions, but will NOT attempt to overwrite your legacy code.
    @manual
    function ProcessInvoice(invoiceId: store.Invoice.id) -> Boolean {
      reads store.Invoice
      updates store.Invoice
      
      implements {
        // Points to your existing legacy file
        file: "./legacy-billing-api/src/controllers/processInvoice.js"
      }
    }
  }
}

At this stage, you are actively using Carotene. Running carrot dev will strictly lint your legacy architecture to ensure your existing Express code isn't violating its own boundaries, but the AI will not write a single line of code.

3. The Surgical Takeover ​

You decide that the ProcessInvoice logic is buggy, full of technical debt, and needs a rewrite. You want the Carotene compiler to take over.

You do this in three steps:

  1. Change @manual to @ai.
  2. Add strict mathematical bounds (ensures).
  3. Replace the legacy file reference with a Generative Operator (@).
dart
domain Billing {
  backend BillingAPI {
    
    // 1. Transfer ownership to the AI Compiler
    @ai
    function ProcessInvoice(invoiceId: store.Invoice.id) -> Boolean {
      reads store.Invoice
      updates store.Invoice
      
      // 2. Enforce absolute mathematical safety before the rewrite
      ensures rule.ValidTransaction(result)

      implements {
        // 3. Drop the code hole. The AI will now synthesize the new logic.
        return @("Fetch the invoice, calculate late fees (if any), and mark as paid.")
      }
    }
  }
}

4. Compile and Verify ​

When you run carrot generate:

  1. Carotene ignores all the remaining @manual functions, leaving your legacy code untouched.
  2. It zeroes in on ProcessInvoice.
  3. It provisions the Vapor Sandbox, writes the new logic in clean, highly-optimized TypeScript, and runs it against the fuzzed ensures bounds to guarantee safety.

You have successfully replaced a piece of legacy tech debt with mathematically verified, AI-generated code without taking your application offline or executing a massive rewrite.