---
apiVersion: data-spec/v1
id: audit-curated-layout-cutover-ownership-20260427b
title: "Curated page layout cutover — stage 2/3: audit destructive candidate manifest"
description: "Read-only audit between route-source classification (stage 1) and the manual destructive curated-layout cutover (stage 3). It emits the exact row sets stage 3 may touch: unresolved route rows, orphan/duplicate wildcard defaults, platform-owned layout candidates, and wildcard replacement candidates."
issue: "#2070"
created: 2026-04-27
scope: both
status: pending-staging
tables_modified: []
tables: [_System_PageLayout, _System_LayoutAssignment]
idempotent: true
risk: low
reviewers: ["@codeowners"]
depends_on: [resolve-curated-layout-cutover-ownership-20260427a]
blocks: [apply-curated-layout-cutover-20260427c]
---

# Curated page layout cutover — audit destructive candidate manifest

## Summary

**Read-only spec. No SQL mutations.**

This is the hard gate between stage 1 and the manual destructive curated-layout
cutover. It is intentionally a manifest, not just a zero-row check: every row
that stage 3 may delete, soft-delete, or replace must appear here first.

Stage 3 must not run until the blocking sections return zero rows and the
candidate sections have been captured as the approved destructive manifest.

## Operator protocol

1. Apply stage 1 (`resolve-curated-layout-cutover-ownership-20260427a`) until
   its preflight manifest is empty.
2. Run every query below and capture the output.
3. Blocking manifests must return **zero rows**.
4. Candidate manifests may return rows; those rows are the only rows stage 3 is
   allowed to touch.
5. If the stage-3 runbook would touch a row absent from this audit, stop and
   re-audit before proceeding.

## Changes

### Manifest 1 — unresolved route rows or missing wildcard defaults, expect zero rows

```sql
WITH target_objects AS (
  SELECT 'Opportunity' AS ObjectApiName
  UNION ALL
  SELECT 'User' AS ObjectApiName
),
live_layout_candidates AS (
  SELECT
    t.ObjectApiName,
    pl.`Id` AS LayoutId,
    pl.`LayoutName`,
    ROW_NUMBER() OVER (
      PARTITION BY LOWER(t.ObjectApiName)
      ORDER BY
        CASE WHEN COALESCE(pl.`IsDefault`, 0) = 1 THEN 0 ELSE 1 END,
        pl.`CreatedDate` ASC,
        pl.`Id` ASC
    ) AS CandidateRank
  FROM target_objects t
  JOIN `_System_PageLayout` pl
    ON LOWER(pl.`ObjectApiName`) = LOWER(t.ObjectApiName)
   AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
),
missing_wildcard AS (
  SELECT c.*
  FROM live_layout_candidates c
  WHERE c.CandidateRank = 1
    AND NOT EXISTS (
      SELECT 1
      FROM `_System_LayoutAssignment` la
      WHERE LOWER(la.`ObjectApiName`) = LOWER(c.ObjectApiName)
        AND la.`RoleId` IS NULL
        AND la.`ProfileId` IS NULL
        AND la.`RecordTypeId` IS NULL
    )
)
SELECT
  'unresolved_assignment' AS BlockingClass,
  la.`Id`           AS AssignmentId,
  la.`ObjectApiName`,
  la.`LayoutId`,
  la.`RoleId`,
  la.`ProfileId`,
  la.`RecordTypeId`,
  la.`Specificity`,
  CASE WHEN pl.`Id` IS NULL THEN 0 ELSE 1 END AS TargetPresent,
  pl.`LayoutName`
FROM `_System_LayoutAssignment` la
LEFT JOIN `_System_PageLayout` pl
  ON pl.`Id` = la.`LayoutId`
 AND LOWER(pl.`ObjectApiName`) = LOWER(la.`ObjectApiName`)
 AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
WHERE LOWER(la.`ObjectApiName`) IN ('opportunity', 'user')
  AND (
    (
      pl.`Id` IS NOT NULL
      AND pl.`Source` = 'auto_generated'
      AND COALESCE(pl.`IsCustomized`, 0) = 0
      AND (la.`RouteSource` IS NULL OR TRIM(la.`RouteSource`) = '')
    ) OR (
      la.`RoleId` IS NULL
      AND la.`ProfileId` IS NULL
      AND la.`RecordTypeId` IS NULL
      AND (la.`RouteSource` IS NULL OR TRIM(la.`RouteSource`) = '')
    )
  )
UNION ALL
SELECT
  'missing_wildcard' AS BlockingClass,
  NULL AS AssignmentId,
  mw.ObjectApiName,
  mw.LayoutId,
  NULL AS RoleId,
  NULL AS ProfileId,
  NULL AS RecordTypeId,
  0 AS Specificity,
  1 AS TargetPresent,
  mw.LayoutName
FROM missing_wildcard mw
ORDER BY ObjectApiName, BlockingClass, Specificity DESC, AssignmentId;
```

**Expected result**: zero rows.

Any returned row means the target cluster still contains a blocking legacy
route: either a live layout route whose intent cannot be distinguished from
runtime fallback, a wildcard default whose route source is still unresolved, or
live layouts with no wildcard route at all. The destructive cutover must not
proceed.
For missing wildcard defaults, the `IsDefault`-first candidate ordering relies
on ordinary pack apply preserving the legacy `_System_PageLayout.IsDefault`
evidence until this audit is complete.

### Manifest 2 — orphan or duplicate wildcard defaults, expect zero rows

```sql
WITH wildcard AS (
  SELECT
    la.*,
    ROW_NUMBER() OVER (
      PARTITION BY LOWER(la.`ObjectApiName`)
      ORDER BY
        CASE WHEN la.`RouteSource` = 'admin_authored' THEN 0 ELSE 1 END,
        la.`CreatedDate` ASC,
        la.`Id` ASC
    ) AS PreferredRank,
    COUNT(*) OVER (PARTITION BY LOWER(la.`ObjectApiName`)) AS WildcardCount,
    CASE WHEN pl.`Id` IS NULL THEN 1 ELSE 0 END AS IsOrphan
  FROM `_System_LayoutAssignment` la
  LEFT JOIN `_System_PageLayout` pl
    ON pl.`Id` = la.`LayoutId`
   AND LOWER(pl.`ObjectApiName`) = LOWER(la.`ObjectApiName`)
   AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
  WHERE LOWER(la.`ObjectApiName`) IN ('opportunity', 'user')
    AND la.`RoleId` IS NULL
    AND la.`ProfileId` IS NULL
    AND la.`RecordTypeId` IS NULL
)
SELECT
  `Id` AS AssignmentId,
  `ObjectApiName`,
  `LayoutId`,
  `RouteSource`,
  WildcardCount,
  PreferredRank,
  IsOrphan
FROM wildcard
WHERE IsOrphan = 1
   OR WildcardCount > 1;
```

**Expected result**: zero rows.

Any returned row means the current wildcard state is not a single live default
route. Return to stage 1 or perform an explicit non-destructive ownership
resolution before authorizing stage 3.

### Manifest 3 — platform-owned layout candidates for stage-3 soft delete

```sql
SELECT
  pl.`Id` AS LayoutId,
  pl.`ObjectApiName`,
  pl.`LayoutName`,
  pl.`Source`,
  pl.`IsCustomized`
FROM `_System_PageLayout` pl
WHERE LOWER(pl.`ObjectApiName`) IN ('opportunity', 'user')
  AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
  AND pl.`Source` = 'auto_generated'
  AND COALESCE(pl.`IsCustomized`, 0) = 0
  AND NOT EXISTS (
    SELECT 1
    FROM `_System_LayoutAssignment` la
    WHERE la.`LayoutId` = pl.`Id`
      AND LOWER(la.`ObjectApiName`) = LOWER(pl.`ObjectApiName`)
      AND la.`RouteSource` = 'admin_authored'
  )
ORDER BY pl.`ObjectApiName`, pl.`CreatedDate` ASC, pl.`Id` ASC;
```

These rows are the only page-layout rows stage 3 may soft-delete. The operator
must save this result as the deletion manifest and row-level backup input.

### Manifest 4 — wildcard replacement candidates for stage 3

```sql
SELECT
  la.`Id` AS AssignmentId,
  la.`ObjectApiName`,
  la.`LayoutId`,
  la.`RouteSource`,
  pl.`LayoutName`
FROM `_System_LayoutAssignment` la
LEFT JOIN `_System_PageLayout` pl
  ON pl.`Id` = la.`LayoutId`
 AND LOWER(pl.`ObjectApiName`) = LOWER(la.`ObjectApiName`)
 AND (pl.`IsDeleted` = 0 OR pl.`IsDeleted` IS NULL)
WHERE LOWER(la.`ObjectApiName`) IN ('opportunity', 'user')
  AND la.`RoleId` IS NULL
  AND la.`ProfileId` IS NULL
  AND la.`RecordTypeId` IS NULL
  AND COALESCE(la.`RouteSource`, '') <> 'admin_authored'
ORDER BY la.`ObjectApiName`, la.`CreatedDate` ASC, la.`Id` ASC;
```

These rows are the only wildcard assignment rows stage 3 may replace with a
reviewed `RouteSource='managed_seed'` wildcard. If an `admin_authored` wildcard
exists for the object, it is not a replacement candidate.

## Rollback

Not applicable — this spec mutates nothing.
