---
apiVersion: destructive-runbook/v1
id: drop-system-object-label-unique-20260426
title: "Drop UNIQUE index on Label for _System_Object / _System_App / Group / PermissionSet"
description: "Removes the platform-binary UNIQUE index that the schema descriptor previously declared on the Label column of four metadata tables. This index has no Salesforce analog (SF EntityDefinition.Label is not unique — only QualifiedApiName is) and has no callers in the platform code (no WHERE Label = ?, no FindByLabel paths). Its only effect was to abort apply-packs with Error 1062 whenever a tenant custom object's Label collided with a standard object's canonical Label (e.g. order_product__c vs OrderItem, both 'Order Product'). The schema descriptor change ships in the same PR; this runbook is the matching one-shot index drop on already-deployed clusters that were created before the descriptor change. Greenfield clusters created from the new descriptor never carry the index and skip this runbook entirely."
issue: "#2019"
created: 2026-04-26
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_Object
  - _System_App
  - "`Group`"
  - PermissionSet
tables: [_System_Object, _System_App, "`Group`", PermissionSet]
idempotent: true
risk: low
reviewers: ["@codeowners"]
depends_on: []
---

# Drop UNIQUE index on Label

## Summary

The four tables `_System_Object`, `_System_App`, `Group`, `PermissionSet`
previously carried a `UNIQUE` index on their `Label` column, declared in
`backend/internal/bootstrap/system_tables.json` as `Label.unique: true`.

The same PR that contains this runbook flips that JSON to drop the
`unique` declaration. New clusters get the new (correct) DDL on first
apply; already-deployed clusters need this runbook to align.

This is intentionally **not** `data-spec/v1`. The data-spec applier rejects
`ALTER TABLE ... DROP` (`.agent/skills/data-spec-apply/SKILL.md:100`) by
design. `DROP INDEX` falls inside that forbidden pattern zone, so this
file declares `apiVersion: destructive-runbook/v1` and is executed by a
human operator only.

The drop is **low risk** and **fully reversible**:
- No platform code reads `_System_Object` / `_System_App` / `Group` /
  `PermissionSet` by Label — verified by `grep -rnE 'WHERE Label\s*=|FindByLabel|GetByLabel|ByLabel\(' backend/internal shared` returning zero hits inside these four tables (the two unrelated hits are `CampaignMemberStatus` and a UI ListInfo helper).
- The actual identity column on every one of these tables is `ApiName` /
  `Name` / `DeveloperName` (still `UNIQUE`) — Salesforce-correct.
- Rollback re-adds the same UNIQUE index. Will only succeed if the data
  meanwhile remained label-unique; if a collision exists at rollback time
  the operator must first re-rename a row.

## Pre-flight (per cluster, per table)

For each affected table, discover whether the cluster carries the legacy
unique index. The index name on legacy clusters is just `Label`
(implicit, generated by the original `UNIQUE` column declaration); on
clusters that explicitly named it, the name may differ. Discover both.

```sql
SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('_System_Object', '_System_App', 'Group', 'PermissionSet')
  AND COLUMN_NAME = 'Label'
  AND NON_UNIQUE = 0
ORDER BY TABLE_NAME, INDEX_NAME;
```

**Expected pre-apply state on legacy clusters**: zero or more rows, one
per table that still carries the index. Empty result means this runbook
is a no-op for that cluster (already drift-free, e.g. greenfield from
the new descriptor).

Record the actual `INDEX_NAME` values returned — they feed the next step.

## Apply (per index found)

For each `(table, index_name)` row returned by the pre-flight, run the
matching `DROP INDEX` from the list below. Do not run statements for
tables/indexes the pre-flight did not return — the operator's job is to
match pre-flight output to the runbook's allowed actions, not to
template-execute every statement.

Allowed actions:

```sql
-- _System_Object
ALTER TABLE `_System_Object` DROP INDEX `Label`;

-- _System_App
ALTER TABLE `_System_App` DROP INDEX `Label`;

-- Group  (table name is a reserved word, must be backtick-quoted)
ALTER TABLE `Group` DROP INDEX `Label`;

-- PermissionSet
ALTER TABLE `PermissionSet` DROP INDEX `Label`;
```

If an `INDEX_NAME` discovered by pre-flight does not match `Label` for
its table, escalate before running — the runbook covers only the
canonical legacy index name.

**Idempotency**: re-running is safe. If the pre-flight returns zero
rows, no actions are taken. The `ALTER TABLE` statements themselves are
not idempotent (`DROP INDEX` errors on a missing index in TiDB, which
does not support `DROP INDEX IF EXISTS`), so the operator must gate
execution on the pre-flight result. This is the contract for runbook
execution per `apiVersion: destructive-runbook/v1`.

## Audit (per cluster, after apply)

```sql
SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('_System_Object', '_System_App', 'Group', 'PermissionSet')
  AND COLUMN_NAME = 'Label'
  AND NON_UNIQUE = 0;
```

**Expected post-apply result**: zero rows (no UNIQUE index on Label
remains on any of the four tables).

## End-to-end gate (per cluster, after apply)

Run `cmd/setup apply` end-to-end against the cluster. The previously
fatal step `apply-packs: pack platform-core: save object metadata:
Error 1062 (23000): Duplicate entry '?' for key '_System_Object.Label'`
must succeed. This validates that:

1. The legacy `order_product__c` tenant row (`Label='Order Product'`)
   is preserved unchanged.
2. The standard `OrderItem` row (`Label='Order Product'`) is inserted
   without colliding.
3. Both rows coexist in `_System_Object`, distinguished by `ApiName`
   (`order_product__c` vs `OrderItem`) — the actual SObject identity
   per Salesforce semantics.

## Rollback (per index dropped)

Only run if a regression is observed AND the data is verified to be
label-unique on the table being rolled back.

```sql
-- _System_Object
ALTER TABLE `_System_Object` ADD UNIQUE INDEX `Label` (`Label`);

-- _System_App
ALTER TABLE `_System_App` ADD UNIQUE INDEX `Label` (`Label`);

-- Group
ALTER TABLE `Group` ADD UNIQUE INDEX `Label` (`Label`);

-- PermissionSet
ALTER TABLE `PermissionSet` ADD UNIQUE INDEX `Label` (`Label`);
```

A label collision at rollback time will fail the `ADD UNIQUE INDEX`
with Error 1062 — operator must resolve (rename one row) before
re-trying.
