DEV Community

Casey Sun
Casey Sun

Posted on

When a Free Model Must Not Time the Dual-Write Cutover

A payments team scheduled a dual-write cutover for Sunday night. The checklist arrived as a long chat transcript. A free-tier coding model had drafted every step.

This scene is a composite. It is not a named outage. The failure mode is still common.

The model proposed a traffic split of five percent. It also invented a freeze window and a rollback owner. Nobody pinned those values in version control.

Dual-write cutovers are not brainstorming tasks. They are timed contracts with production data. A free lane can sketch a draft. It must not own the production clock.

Why cutover timing is not a chat task

A dual-write window binds two stores at once. Reads may still hit the old path. Writes must land in both paths without drift.

A wrong retry budget duplicates money movements. A wrong freeze window hides a split brain. A wrong rollback owner pages the wrong humans.

Free models optimize for a complete-looking checklist. Completeness is not correctness under production load. Cutover timing needs pinned owners and pinned numbers.

Idempotency keys are part of that contract. A model can invent a plausible key shape. Plausible shape is not a ledger invariant.

The engineering line

Prompt-built patches can look complete in review. A live dual-write cutover remains a hard counterexample. The work is sequencing, ownership, and halt rules.

Looks-good output from a model is not a merge stamp. A traffic percentage is not a style choice. It is a budget against measured error.

This article stays on when not to use the free lane. It is a field guide, not a product tour.

What the free lane may still do

A free coding model can still help on the edges. Keep that help far from the live switch.

The allowed uses include the following bounded items.

  • Draft a glossary of cutover terms for the team wiki
  • Turn a human outline into a Markdown skeleton
  • Generate unit tests for a pure planner function
  • List reviewer checks after humans pin the numbers

Those outputs stay advisory. They never become the live cutover controller. They never become the pull-request merge stamp.

Red flags for a free model

Stop the free lane when any flag below appears.

  1. The model chooses the traffic percentage without a measured error budget.
  2. The model invents the dual-write duration from typical folklore.
  3. The model names a rollback owner who is not on the roster.
  4. The model writes the feature-flag default to on.
  5. The model proposes deleting the old writer during the same window.
  6. The model treats checksum mismatch as a retry, not a halt.
  7. The model stores the cutover ledger only in chat history.
  8. The model mints idempotency key formats for money movements.

Any one flag is enough. Two flags mean the draft is unsafe.

Red flags for a free server

A free shared server is the wrong control plane. Cutover control needs a pinned runtime with a named owner.

Stop that host when any flag below appears.

  • The process must hold the traffic-shift lease
  • The process must write the dual-write ledger
  • The process must page humans on checksum failure
  • The process must keep the only copy of the freeze clock
  • The job needs a stable egress IP for store allowlists
  • The host can sleep, preempt, or recycle without notice

Drafting a checklist on a free server can be fine. Running the live controller there is not acceptable.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. Those lanes fit glossary drafts and planner unit tests. They do not fit the live cutover controller.

Artifact: a pinned plan file

The team should store a cutover plan as versioned JSON. Pinned humans must fill every timed field. Automation rejects a free-lane author on those fields.

The schema below is a labeled proposal. It has not governed a live payment cutover.

{
  "plan_id": "cutover-ledger-2026-09-19",
  "service": "ledger-writer",
  "authors": {
    "draft": "free-lane-model",
    "timed_fields": "human:sre-oncall",
    "rollback_owner": "human:payments-tl"
  },
  "traffic": {
    "initial_percent": 1,
    "step_percent": 4,
    "max_percent_before_freeze": 20,
    "error_budget_ppm": 50
  },
  "window": {
    "freeze_start_utc": "2026-09-20T02:00:00Z",
    "freeze_end_utc": "2026-09-20T06:00:00Z",
    "dual_write_required": true,
    "old_writer_delete_same_window": false
  },
  "halt": {
    "on_checksum_mismatch": "halt",
    "on_retry_storm": "halt",
    "on_free_lane_author_for_timed_fields": "halt"
  },
  "idempotency": {
    "key_format": "human-pinned:ledger-v1",
    "author": "human:payments-tl"
  },
  "controller": {
    "runtime": "pinned-paid-path",
    "free_server_forbidden": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Keep the file next to the service. Teams should review that file like production code. Bot approval on this file should fail CI.

Artifact: a Node validator

The Node validator below is a lab example. Run it in CI against the committed plan. Do not treat it as a production orchestrator.

#!/usr/bin/env node
"use strict";

const fs = require("fs");

const FREE_LANE_MARKERS = [
  "free-lane",
  "free-model",
  "free-server",
  "chat-draft",
];

function isFreeLane(value) {
  const s = String(value || "").toLowerCase();
  return FREE_LANE_MARKERS.some((m) => s.includes(m));
}

function fail(msg, errors) {
  errors.push(msg);
}

function validate(plan) {
  const errors = [];
  const timedAuthor = plan?.authors?.timed_fields;
  const rollback = plan?.authors?.rollback_owner;
  const traffic = plan?.traffic || {};
  const window = plan?.window || {};
  const halt = plan?.halt || {};
  const idem = plan?.idempotency || {};
  const controller = plan?.controller || {};

  if (isFreeLane(timedAuthor)) {
    fail("timed_fields must not come from a free lane", errors);
  }
  if (!String(timedAuthor || "").startsWith("human:")) {
    fail("timed_fields need a pinned human owner", errors);
  }
  if (isFreeLane(rollback) || !String(rollback || "").startsWith("human:")) {
    fail("rollback_owner must be a named human", errors);
  }
  if (!(traffic.initial_percent >= 1 && traffic.initial_percent <= 5)) {
    fail("initial_percent must sit between 1 and 5", errors);
  }
  if (traffic.error_budget_ppm == null || traffic.error_budget_ppm > 100) {
    fail("error_budget_ppm must be pinned at or below 100", errors);
  }
  if (window.old_writer_delete_same_window !== false) {
    fail("old writer must survive the first window", errors);
  }
  if (window.dual_write_required !== true) {
    fail("dual_write_required must stay true during cutover", errors);
  }
  if (halt.on_checksum_mismatch !== "halt") {
    fail("checksum mismatch must halt, not retry", errors);
  }
  if (isFreeLane(idem.author) || !String(idem.key_format || "").startsWith("human-pinned:")) {
    fail("idempotency rules must be human-pinned", errors);
  }
  if (controller.free_server_forbidden !== true) {
    fail("controller must forbid a free server runtime", errors);
  }
  if (String(controller.runtime).includes("free")) {
    fail("controller runtime must not be a free server", errors);
  }
  return errors;
}

module.exports = { validate, isFreeLane };

if (require.main === module) {
  const path = process.argv[2];
  if (!path) {
    console.error("usage: node validate-cutover-plan.js <plan.json>");
    process.exit(2);
  }
  const plan = JSON.parse(fs.readFileSync(path, "utf8"));
  const errors = validate(plan);
  if (errors.length) {
    console.error("cutover plan rejected:");
    for (const e of errors) console.error("- " + e);
    process.exit(1);
  }
  console.log("cutover plan accepted: timed fields are pinned");
}
Enter fullscreen mode Exit fullscreen mode

Local check commands look like the block below.

node scripts/validate-cutover-plan.js cutover/plan.json
echo $?
Enter fullscreen mode Exit fullscreen mode

A failing plan should exit one. A pinned plan should exit zero. Keep the JSON in the same repo as the service.

Artifact: a tiny unit test

The test below is also a lab example. It locks the halt rule for checksum mismatch. It also locks the free-server ban.

"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const { validate } = require("./validate-cutover-plan");

function basePlan() {
  return {
    authors: {
      draft: "free-lane-model",
      timed_fields: "human:sre-oncall",
      rollback_owner: "human:payments-tl",
    },
    traffic: { initial_percent: 1, error_budget_ppm: 50 },
    window: {
      dual_write_required: true,
      old_writer_delete_same_window: false,
    },
    halt: { on_checksum_mismatch: "halt" },
    idempotency: {
      key_format: "human-pinned:ledger-v1",
      author: "human:payments-tl",
    },
    controller: {
      runtime: "pinned-paid-path",
      free_server_forbidden: true,
    },
  };
}

test("accepts a human-timed plan", () => {
  const errors = validate(basePlan());
  assert.equal(errors.length, 0);
});

test("rejects free-lane timed fields", () => {
  const plan = basePlan();
  plan.authors.timed_fields = "free-model-bot";
  const errors = validate(plan);
  assert.ok(errors.some((e) => e.includes("timed_fields")));
});

test("rejects checksum retry and free-server runtime", () => {
  const plan = basePlan();
  plan.halt.on_checksum_mismatch = "retry";
  plan.controller.runtime = "free-server";
  const errors = validate(plan);
  assert.ok(errors.length >= 2);
});
Enter fullscreen mode Exit fullscreen mode

Run that test with the command shown below.

node --test scripts/validate-cutover-plan.test.js
Enter fullscreen mode Exit fullscreen mode

A red test means the policy drifted. Fix the validator before the freeze window. Do not hot-patch policy during dual-write.

The CI sketch below is a labeled proposal. It only gates the plan file. It does not shift live traffic.

name: cutover-plan
on:
  pull_request:
    paths:
      - "cutover/*.json"
      - "scripts/validate-cutover-plan.js"
      - "scripts/validate-cutover-plan.test.js"
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: node scripts/validate-cutover-plan.js cutover/plan.json
      - run: node --test scripts/validate-cutover-plan.test.js
Enter fullscreen mode Exit fullscreen mode

Decision table

Signal Free model Free server Better path
Draft glossary Allowed Allowed Wiki pull request
Fill timed percentages Forbidden Forbidden Human plus error budget
Name rollback owner Forbidden Not applicable On-call roster
Hold traffic-shift lease Forbidden Forbidden Pinned controller
Write dual-write ledger Forbidden Forbidden Durable store
Pin idempotency key format Forbidden Forbidden Human-owned spec
Generate planner unit tests Allowed Allowed CI on a pinned runner
Delete old writer Forbidden Forbidden Later, after soak

The table is a policy aid. It is not a capacity or latency plan. Teams should replace numeric bounds with their own budgets.

Better alternatives

Use the free lane as a stenographer, not a dispatcher. A human should write the outline first. The model only expands already pinned headings.

Pin every timed field in git. Require two human reviewers on that file. Reject a bot LGTM on the plan JSON.

Run the controller on a pinned path. That path needs a named owner and a retention policy. Chat logs are not a retention policy.

Keep checksum failure as a halt. Retries belong only to idempotent downstream workers. They do not belong to the traffic switch.

Rehearse the halt path in a lab. Use synthetic dual-write drift, not customer rows. Record who halted and how long recovery took.

Exit criteria

Leave the free-lane draft the moment any criterion trips.

  • A timed field changed without a human commit
  • The rollback owner is a model identifier
  • The controller hostname maps to a free server
  • Checksum mismatch is mapped to retry
  • The old writer is scheduled for same-window delete
  • The only freeze clock lives in a chat thread
  • Feature flags default to on in the draft
  • Idempotency key rules arrived from a chat model

After exit, freeze traffic at the last good percent. Restore the human-authored plan from git. Rehearse the halt path before the next window.

Limitations

This guide does not time a real cutover. The validator does not talk to a service mesh. The numeric bounds are examples, not SLOs.

The approach assumes a dual-write style migration. It does not cover shadow reads without writes. It does not cover multi-region leader election.

Do not use this approach when no human rollback owner exists. Do not use it as a reason to skip load tests. Do not use it to bless prompt-built production switches.

Teams without a pinned controller should delay the cutover. A polished checklist is not a control plane.

Who should skip this

Skip this pattern in a few documented cases.

  • Solo prototypes with no customer data
  • Read-only shadow traffic with no dual write
  • Docs-only migrations that never touch stores
  • Lossy workloads where a missed write is acceptable

Those cases can stay on a free draft lane. Ledger money, identity, and medical records cannot.

A free model can still write the glossary. A free server can still host validator tests. The live switch stays on a pinned path.

Teams already drafting in MonkeyCode should keep that work narrow. Limit it to glossary text and planner unit tests. Move timed fields into a human commit before freeze.

Top comments (0)