# Action Table Consolidation Decision

Date: 2026-05-16

## Decision

Keep `_System_Action` and `_System_StandardAction` as separate tables,
formalize their ownership boundary, and add a fail-closed verify gate for the
actual bridge surface.

Do not merge `_System_StandardAction` into `_System_Action` in this phase.

## Rationale

The live system does not have a safe one-to-one table bridge today.

`_System_Action` is object-scoped action metadata. It is read by the quick action handler, the metadata service, and the generic automation action executor. Its stable business key is `(ObjectApiName, Name)`. Its `Type` column is an execution mechanism such as `CreateRecord`, `UpdateRecord`, `SendEmail`, or `Composite`.

`_System_StandardAction` is the global invocable-action registry. It is read by `StandardActionDispatcher` for `/actions/standard/:name` and by the standard-action discovery endpoint. Its route key is `Name`. Its internal join key is `Id`, which is referenced by `_System_ActionSchema`, `_System_ActionFieldDefault`, `_System_ActionInputGuard`, and `_System_StandardActionInternalAccessScope`.

Merging the tables now would require a schema migration, generated constants, bootstrap seeding, source-pack reconciliation, dispatcher reads, metadata reads, and pack hash changes in one broad cut. That is not the smallest correct fix for the current architectural risk.

## Scope

This phase ships the formal separation and the gate:

- `_System_Action` remains the SSoT for object-scoped quick/custom/automation actions.
- `_System_StandardAction` remains the SSoT for invocable standard actions.
- Standard-action satellite tables must point at a present (non-deleted) `_System_StandardAction` parent by `StandardActionId` — an admin-disabled (`IsActive=0`) parent still counts as present and keeps its satellites. The reconcile cascade enforces this on the write side (dropping a parent retires its satellites) and `cmd/setup verify` fails closed on the read side. Both halves scope the three customizable satellites to `IsCustomized=0` so admin-customized orphans are preserved; the L1-only access-scope table is swept and checked in full.

The two top tables are disjoint namespaces (`_System_Action` keyed by `(ObjectApiName, Name)`, `_System_StandardAction` by `Name`). A `Name` shared across them is not a bridge and is not treated as corruption — failing on it would block valid admin-authored QuickAction / automation metadata. See `docs/design/action-table-bridge.md`.

If a future phase needs a single action row to exist on both surfaces, it must add an explicit bridge column or migration first, then update the verify gate to enforce that new contract.

## Follow-up

Option A remains the longer-term cleanup if the team wants one physical action table. That work should be a dedicated migration phase, not a hidden side effect of this bridge hardening.
