AI Migration Assistants: Strangler Fig Cuts With Contract Tests

AI Migration Assistants: Strangler Fig Cuts With Contract Tests

Big-bang rewrites fail. Strangler fig migrations succeed when every cut is proven — not when an agent “migrates the service” in one PR. Use coding agents to accelerate endpoint-sized cuts: generate adapters, dual-run harnesses, and contract tests that fail the cutover when response drift exceeds semantic tolerances you define.

⚡ TL;DR: Agents propose strangler adapters and characterization tests; humans own cutover. Dual-run old vs new on shadow traffic; compare with schema-aware matchers (not brittle string equality). Cut traffic only when drift < threshold for N windows. Illustrative pace: 1–3 endpoints/week with agent assist vs months of stalled rewrite.

Strangler shape the agent must respect

Client → Facade / Routing layer
            ├─ Legacy monolith handler (default)
            └─ New service handler (canary %)
                 └─ Dual-run comparator (shadow)

Encode this in Cursor rules / Claude instructions so the agent never deletes the legacy path in the same PR that adds the new one. See Cursor Rules for TypeScript Monorepos.

# migration rule excerpt
- One endpoint per PR unless a shared DTO must move first.
- Never remove legacy routes in the introducing PR.
- Always add dual-run tests under packages/migration-contracts.
- ❌ No “while we’re here” refactors outside the endpoint boundary.

Agent task: characterization tests first

Before writing the new handler, the agent should freeze legacy behavior:

// characterization from recorded fixtures — illustrative
import { legacyHandle } from "../legacy/invoices";
import fixtures from "./fixtures/invoices.get.json";

test.each(fixtures)("legacy characterization %s", async (fx) => {
  const res = await legacyHandle(fx.req);
  expect(res.status).toBe(fx.res.status);
  expect(normalize(res.body)).toEqual(normalize(fx.res.body));
});

function normalize(body: any) {
  const { requestId, serverTime, ...rest } = body;
  return rest; // strip volatile fields
}

✅ Agents generate fixtures from sanitized traffic.
❌ Agents invent expected JSON from memory of another codebase.

Dual-run contract tests with semantic tolerances

type Tolerance = {
  ignorePaths: string[]; // json-pointer
  numericEpsilon?: Record<string, number>;
  arrayOrderInsensitive?: string[];
};

export function semanticEqual(a: unknown, b: unknown, tol: Tolerance): string[] {
  const drifts: string[] = [];
  // walk both trees; skip ignorePaths; compare numbers with epsilon
  // return human-readable drift list for the PR bot
  return drifts;
}

test("dual-run GET /invoices/:id", async () => {
  const req = sampleRequest();
  const [legacy, next] = await Promise.all([
    legacyHandle(req),
    newHandle(req),
  ]);
  const drifts = semanticEqual(legacy.body, next.body, {
    ignorePaths: ["/requestId", "/serverTime"],
    numericEpsilon: { "/taxCents": 1 },
  });
  expect(drifts).toEqual([]);
});

Wire shadow traffic in staging: copy a % of GETs to the new path, log drifts to S3, fail the promotion pipeline when drift rate > 0.1%. Semantic review bots (companion) should treat migration PRs as high-signal.

Cutover playbook the agent cannot skip

# cutover checklist enforced in CI labels
# 1. dual-run green for 48h on staging shadow
# 2. canary 5% → 25% → 100% with error-budget gates
# 3. only then open PR to remove legacy handler
gh pr edit "$PR" --add-label "migration/cutover-ready"
// routing facade — illustrative
export async function getInvoice(ctx, id: string) {
  const mode = await flags.get("invoices.get.impl", ctx.tenantId);
  if (mode === "new") return newHandle(ctx, id);
  if (mode === "shadow") {
    const legacy = await legacyHandle(ctx, id);
    void shadowCompare(legacy, () => newHandle(ctx, id));
    return legacy; // always return legacy until cut
  }
  return legacyHandle(ctx, id);
}

Keep agent tools sandboxed (Lambda sandboxes) — migration assistants should not apply production traffic flags themselves.

What to automate vs what stays human

Step Agent Human
Draft adapter + DTOs review
Characterization fixtures ✅ assist approve redaction
Dual-run matcher tuning propose set tolerances
Canary % changes ✅ on-call
Delete legacy draft PR merge after soak

Atomic commits help (Agentic Git Workflows): tests, adapter, flags, deletion as separate SHAs.

Closing checklist

✅ Dos
– ✅ One endpoint (or tightly bound group) per migration PR
– ✅ Characterization tests before new implementation
– ✅ Semantic dual-run with explicit tolerances
– ✅ Feature-flag cutover with soak windows
– ✅ Delete legacy only after canary success

❌ Don’ts
– ❌ Don’t let the agent rewrite the entire monolith in one branch
– ❌ Don’t compare raw JSON including volatile fields
– ❌ Don’t flip 100% traffic from an unattended agent tool
– ❌ Don’t skip CODEOWNERS on migration facades
– ❌ Don’t mix unrelated refactors into strangler PRs

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a Reply