---
apiVersion: data-spec/v1
id: rename-legacy-contract-names-20260422
description: "Rename 4 Contract rows whose Name fails the v2 autonumber derived regex"
issue: "#1676, #1708"
created: 2026-04-22
scope: production-only
status: applied
tables: [Contract]
depends_on: []
---

# Rename Legacy Contract.Name Rows to v2 Autonumber Format

## Summary

4 Contract rows on staging + prod have `Name` values that fail the v2 autonumber
derived regex for `Contract.Name` (format
`{Account.BillingCountryCode}-{RecordType.RecordTypeCode}-{YY}{MM}{DD}{00}`):

| Id | Old Name | Reason it fails |
|---|---|---|
| `800RC000009L0r3YAC` | `IDMA-24020501` | Missing `-` between country and RT code |
| `800RC000009L1GsYAK` | `USMA-24020502` | Missing `-` between country and RT code |
| `8ccd2d88-2a72-4c47-8bbb-aef8c7dba831` | `JP-CSA-260419-01` | Extra `-` between date and counter |
| `3da82bff-baf5-4ded-9425-23fd434f2e98` | `JP-MA-260419-01` | Extra `-` between date and counter |

Today these rows are tolerated via `AUTONUMBER_SEED_TOLERATE_LEGACY=true` (WARN
instead of FAIL). Renaming them makes the invariant pass cleanly — WARN count
drops from 4 to 0 — and removes the need for the tolerate flag as a safety net
(the flag itself stays on per user decision, see PR description).

The 2 IDMA/USMA rows are documented in `docs/grandfathered-records.md`. This
spec supersedes that entry (the doc entry is removed in the same PR).

### Target Names

| Id | Old Name | New Name | Reconstruction |
|---|---|---|---|
| `800RC000009L0r3YAC` | `IDMA-24020501` | `ID-MA-24020501` | Account.BillingCountryCode=ID, _System_RecordType.record_type_code=MA, date+seq preserved |
| `800RC000009L1GsYAK` | `USMA-24020502` | `US-MA-24020502` | Account.BillingCountryCode=US, _System_RecordType.record_type_code=MA, date+seq preserved |
| `8ccd2d88-...` | `JP-CSA-260419-01` | `JP-CSA-26041901` | Hyphen between date and counter removed; seq=01 unchanged |
| `3da82bff-...` | `JP-MA-260419-01` | `JP-MA-26041902` | Hyphen removed; seq bumped to 02 because `JP-MA-26041901` already exists on row `cf06ef3c-...` (created 2026-04-19 06:11 by the compliant code path) |

### Risk accepted

The user explicitly accepted the risk that downstream report / spreadsheet /
external integration references to the old Names may break. This is the same
risk that caused PR #1691 to grandfather instead of rename; the decision has
been re-evaluated.

## Changes

### 1. Rename 4 Contract rows to v2-compliant Names

```sql
UPDATE Contract SET Name = 'ID-MA-24020501'
  WHERE Id = '800RC000009L0r3YAC' AND Name = 'IDMA-24020501';

UPDATE Contract SET Name = 'US-MA-24020502'
  WHERE Id = '800RC000009L1GsYAK' AND Name = 'USMA-24020502';

UPDATE Contract SET Name = 'JP-CSA-26041901'
  WHERE Id = '8ccd2d88-2a72-4c47-8bbb-aef8c7dba831' AND Name = 'JP-CSA-260419-01';

UPDATE Contract SET Name = 'JP-MA-26041902'
  WHERE Id = '3da82bff-baf5-4ded-9425-23fd434f2e98' AND Name = 'JP-MA-260419-01';
```

**Idempotency**: Each UPDATE guards on BOTH the row's primary key (`Id`) AND
the exact old value of `Name`. On the first run, the old Name matches and the
row is updated. On every subsequent run, the old Name no longer matches (the
row now has the new Name), the WHERE clause returns zero rows, and the UPDATE
is a no-op. Re-running the spec any number of times produces identical results.

**Collision safety**: All 4 target Names were verified non-existent on both
staging and prod before this spec was authored. `JP-MA-26041902` is the next
free sequence in scope `JP-MA-260419` (NextValue=2 at spec authoring time
because only `JP-MA-26041901` exists); if the counter advances between spec
authoring and apply, the apply-time post-verify (see below) catches the
collision.

## Post-Verify

After applying, confirm:

```sql
-- Expect 0 rows: all 4 legacy Names gone
SELECT Id, Name FROM Contract WHERE Name IN (
  'IDMA-24020501', 'USMA-24020502',
  'JP-CSA-260419-01', 'JP-MA-260419-01'
);

-- Expect 4 rows: all 4 new Names present, one each
SELECT Id, Name FROM Contract WHERE Name IN (
  'ID-MA-24020501', 'US-MA-24020502',
  'JP-CSA-26041901', 'JP-MA-26041902'
) ORDER BY Name;

-- Expect 0 rows: no more Contract rows fail the backend's derived regex
SELECT Id, Name FROM Contract
  WHERE Name COLLATE utf8mb4_bin NOT REGEXP '^.*-.*-[0-9]{8,}$';
```

The third query is the functional assertion: after apply, `cmd/setup verify`
V2/V7 for `Contract.Name` should report 0 legacy rows.

## Counter state (post-apply, verified)

Pre-apply on 2026-04-22, only `JP-MA-260419` had a live counter row
(NextValue=2 from the compliant `cf06ef3c-...` row at 06:11). Legacy-format
scopes `IDMA-240205` / `USMA-240205` / `JP-CSA-260419` / `JP-MA-260419-01` had
no counter rows because tolerate-mode samples legacy rows instead of counting
them. On the next backend restart after apply, the seed scanner will
populate:

- `ID-MA-240205` (NextValue=2)
- `US-MA-240205` (NextValue=3 — sparse; max observed seq=2)
- `JP-CSA-260419` (NextValue=2)
- `JP-MA-260419` (NextValue bumps from 2 → 3; max observed seq=2)

No orphan counter rows result from this rename (nothing to clean up for the
old scopes because nothing existed there).

## Rollback

Reverses by `Id` (new Name is unique enough to identify, but Id is the safest
anchor for rollback since it is immutable).

**Rollback hazard — read before executing**: if any new Contract rows have
been created by the engine AFTER apply AND the seed scanner has run
(backend restart), the newly-populated `_System_AutoNumberCounter` rows may
have allocated numbers that collide with the rollback. Example: user creates
a new `JP-MA-26041902` after this spec applies → seed has counter
NextValue=3 → engine hands out `JP-MA-26041903` → all fine until rollback
renames the legacy row BACK to `JP-MA-260419-01`, leaving the live
engine-created `JP-MA-26041902` and the earlier compliant `JP-MA-26041901`
in place without issue, BUT if someone later re-forward-applies this spec
the collision check at apply time would fail (the old name that we want to
remap to `JP-MA-26041902` is gone, guard fails silently → apply becomes a
no-op; no data corruption, but spec status becomes confusing). Before
running rollback: `SELECT Id, Name FROM Contract WHERE Name LIKE 'JP-MA-26041902'`
to confirm only one row holds that value.

```sql
UPDATE Contract SET Name = 'IDMA-24020501'
  WHERE Id = '800RC000009L0r3YAC' AND Name = 'ID-MA-24020501';

UPDATE Contract SET Name = 'USMA-24020502'
  WHERE Id = '800RC000009L1GsYAK' AND Name = 'US-MA-24020502';

UPDATE Contract SET Name = 'JP-CSA-260419-01'
  WHERE Id = '8ccd2d88-2a72-4c47-8bbb-aef8c7dba831' AND Name = 'JP-CSA-26041901';

UPDATE Contract SET Name = 'JP-MA-260419-01'
  WHERE Id = '3da82bff-baf5-4ded-9425-23fd434f2e98' AND Name = 'JP-MA-26041902';
```

Rollback does NOT reset `_System_AutoNumberCounter` rows — after renaming back
to the legacy format, the counter state for scopes `ID-MA-240205`,
`US-MA-240205`, `JP-CSA-260419`, `JP-MA-260419` may have been updated by the
seed scanner if the backend restarted between apply and rollback. Resetting
the counter on rollback is risky (could hand out an already-used number if new
rows were created in the meantime). If the rollback is needed, inspect
`_System_AutoNumberCounter` state manually; typically the post-rollback state
is acceptable as-is since counters only go forward.
