---
apiVersion: destructive-runbook/v1
id: apply-curated-layout-cutover-20260427c
title: "Curated page layout cutover — stage 3/3: manual destructive cutover"
description: "Manual destructive SQL/runbook for the curated page-layout cutover after residual blocking rows have been resolved and the destructive candidate manifest has been audited. Ordinary cmd/setup apply is not the destructive mechanism."
issue: "#2070"
created: 2026-04-27
scope: both
status: pending-staging
executable_by: manual-operator-only
requires_codeowner_approval: true
approval_format: "PR body must contain 'Approvers: @a, @b' with two distinct CODEOWNERS"
tables_modified:
  - _System_PageLayout
  - _System_LayoutAssignment
tables: [_System_PageLayout, _System_LayoutAssignment]
idempotent: true
risk: medium
reviewers: ["@codeowners"]
depends_on:
  - resolve-curated-layout-cutover-ownership-20260427a
  - audit-curated-layout-cutover-ownership-20260427b
---

# Curated page layout cutover — run the manual destructive cutover

## Summary

This is the destructive step for the curated page-layout cutover. It is
intentionally **not** `data-spec/v1` because the data-spec applier is for
non-destructive SQL only. Ordinary `cmd/setup apply` must not be used as this
destructive mechanism.

- this runbook preserves deterministic admin-routed and admin-owned layout rows
- then soft-deletes only audited platform-owned legacy
  `Source='auto_generated' AND IsCustomized=0` rows
- then manually replaces audited non-admin wildcard/default routes with
  `RouteSource='managed_seed'`

For tenant-enabled deployments, tenant overlay re-projection is a separate
post-cutover operational step. It must not be used to bypass this manual
destructive approval gate.

## Precondition — hard gate

Both prerequisite specs must be in `status: applied` on the target cluster:

1. `resolve-curated-layout-cutover-ownership-20260427a`
2. `audit-curated-layout-cutover-ownership-20260427b`

The stage-2 audit must have recorded:

- zero unresolved route rows
- zero missing wildcard defaults
- zero orphan/duplicate wildcard defaults
- the exact platform-owned layout deletion manifest
- the exact wildcard replacement manifest

If any blocker is non-zero, or if the stage-3 target set differs from the
recorded stage-2 manifests, stop.

## Apply

Run the destructive change manually, inside a transaction, from the audited
stage-2 manifests. Before the transaction, capture row-level backups for every
`_System_PageLayout` and `_System_LayoutAssignment` row in the manifests.

Use explicit IDs from the approved stage-2 output. Do not replace `${...}` with
a query that discovers additional rows during stage 3.

```sql
BEGIN;

-- 1. Soft-delete only page-layout rows listed in stage-2 manifest 3.
UPDATE `_System_PageLayout`
SET `IsDeleted` = 1,
    `LastModifiedDate` = CURRENT_TIMESTAMP
WHERE `Id` IN (${Stage2Manifest3LayoutIds});

-- 2. Delete only non-admin wildcard rows listed in stage-2 manifest 4.
DELETE FROM `_System_LayoutAssignment`
WHERE `Id` IN (${Stage2Manifest4AssignmentIds});

-- 3. Insert the reviewed managed wildcard route for each object that needs
--    replacement. Curated layout IDs must be selected and reviewed explicitly.
--    The NOT EXISTS guard makes the insert idempotent after a successful run.
INSERT INTO `_System_LayoutAssignment`
  (`Id`, `ObjectApiName`, `LayoutId`, `Specificity`, `RouteSource`,
   `RoleId`, `ProfileId`, `RecordTypeId`, `CreatedDate`, `LastModifiedDate`)
SELECT
  ${NewOpportunityAssignmentId}, pl.`ObjectApiName`, pl.`Id`, 0,
  'managed_seed', NULL, NULL, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
FROM `_System_PageLayout` pl
WHERE pl.`Id` = ${ReviewedOpportunityCuratedLayoutId}
  AND LOWER(pl.`ObjectApiName`) = LOWER('Opportunity')
  AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
  AND NOT EXISTS (
    SELECT 1
    FROM `_System_LayoutAssignment` la
    WHERE LOWER(la.`ObjectApiName`) = LOWER(pl.`ObjectApiName`)
      AND la.`RoleId` IS NULL
      AND la.`ProfileId` IS NULL
      AND la.`RecordTypeId` IS NULL
  );

-- Repeat the same reviewed insert for User only if the stage-2 manifests show
-- that the User wildcard route is being replaced.
INSERT INTO `_System_LayoutAssignment`
  (`Id`, `ObjectApiName`, `LayoutId`, `Specificity`, `RouteSource`,
   `RoleId`, `ProfileId`, `RecordTypeId`, `CreatedDate`, `LastModifiedDate`)
SELECT
  ${NewUserAssignmentId}, pl.`ObjectApiName`, pl.`Id`, 0,
  'managed_seed', NULL, NULL, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
FROM `_System_PageLayout` pl
WHERE pl.`Id` = ${ReviewedUserCuratedLayoutId}
  AND LOWER(pl.`ObjectApiName`) = LOWER('User')
  AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
  AND NOT EXISTS (
    SELECT 1
    FROM `_System_LayoutAssignment` la
    WHERE LOWER(la.`ObjectApiName`) = LOWER(pl.`ObjectApiName`)
      AND la.`RoleId` IS NULL
      AND la.`ProfileId` IS NULL
      AND la.`RecordTypeId` IS NULL
  );

COMMIT;
```

If any statement affects rows outside the stage-2 manifests, or affects a
different row count than expected, roll back the transaction and re-audit.

If a tenant overlay is intentionally applied after this cutover, run the
tenant-specific operational step only after the shared-base transaction has
committed and verification below has passed.

## Post-apply verification

1. Re-run stage-2 manifests 1 and 2. **Expected: zero rows.**
2. Re-run stage-2 manifests 3 and 4 and confirm no unapproved legacy
   candidates remain.
3. Confirm each inserted wildcard route points at the reviewed curated layout
   with `RouteSource='managed_seed'`.
4. If tenant overlay is applied afterward, validate that it succeeded in the
   same maintenance window.

## Rollback

Rollback is the reverse of the cutover transaction:

1. Restore `_System_PageLayout` / `_System_LayoutAssignment` from the
   pre-cutover row backup if the destructive transaction produced unexpected
   state.
2. Restore any stage-1 ownership-resolution changes from backup if needed.
3. Re-run the pre-cutover binary only after the database rows have been
   restored to pre-cutover state.

Do not attempt rollback by reintroducing ad-hoc runtime exceptions or by
guessing which legacy row was "probably" admin intent.
