# Keep BYOC Activation Explicit and Prove the First Dataplane through OpenAPI

> Status: the explicit-only activation choice is superseded by ADR 0041. The
> BYOC-A product-proof and destroy boundaries below remain current.

BYOC customer-account mutation is an explicit lifecycle choice. `cp9 up`
creates the shared Dev full baseline without touching the customer account;
`cp9 byoc activate` is the only activation surface, and `cp9 byoc status` is
strictly read-only. The first slice has no eager `byoc.enabled` path and adds no
public proof command tree because CP9 product acceptance remains a reviewed
skill workflow rather than a CLI lifecycle.

A separate `byoc-openapi` skill owns train `BYOC-A`. Starting from a ready BYOC
Region, the train uses only public product APIs to create the first cluster and
its shared-pool/dataplane, prove the real customer VPC/EKS, exact-role
PrivateLink and private Kubernetes API path, customer-ECR image digests, Active
state, SQL endpoint and `select 1`, then delete the cluster and prove exact GET
and list absence. It never calls DPM directly and deliberately leaves one Empty
BYOC dataplane for session closeout.

Before mutation, BYOC-A requires every activation stage to be done, binds the
requested Region and control-plane role ARN to the activation journal, validates
the exact `auto-deployment-test` STS account, and includes the reviewed project
label in create. If any later gate fails, its exit handler deletes only the
returned TiDB id through the public API and records GET-404 plus list-absence
cleanup evidence; it never substitutes Region destruction.

`cp9 destroy` owns that closeout: it removes the Empty BYOC dataplane and
PrivateLink, BYOC regional runtime and catalog, endpoint security group,
control-plane DPM role, bound setup stacks, account lease, and selector-slot
reservation before ordinary host teardown. Completion requires exact
session-owned absence in both accounts. Only the named customer prerequisites,
retained ECR cache, and shared inert control-plane account-setup principal from
ADR 0040 are excluded, and status/evidence must state that O11Y runtime was not
deployed. Closeout shares the activation lifecycle lock and
persists a terminal `destroyed` checkpoint only after the repeated absence
proof, so an enclosing `cp9 destroy` retry cannot recreate or strand the BYOC
closure. The lock precedes the authoritative journal read; an empty lifecycle
is terminally fenced before ordinary host teardown without requiring a
selected host. Nonterminal journals still require the persisted remote host.

The shared Dev full baseline may also contain imported Dedicated product state.
That state is not part of the BYOC Region or its Premium-like runtime. Destroy
therefore closes and proves the BYOC closure before entering any unrelated
Dedicated baseline cleanup. A later baseline cleanup failure may preserve the
shared host for retry, but it must not strand BYOC CloudFormation stacks,
regional resources, or either lease. Conversely, a BYOC cleanup failure stops
before mutating the unrelated baseline.

The control-plane account's same-session tag set is not a BYOC absence
boundary because it also contains that shared Dev full baseline. BYOC destroy
proves its control-plane closure through the journal-bound role, network,
Kubernetes, catalog, and selector identities. The distinct customer account
has no shared baseline, so a broad customer-side session-tag scan remains the
unknown-resource backstop before either reservation is released.

Before a config-scoped cleanup creates the session root, it persists an
external reaper intent whose digest binds its owner/session, AWS account,
Region, profiles/role/tags, persona/workflow mode, and BYOC account boundary.
V1alpha2 `up` and `destroy` take a second, external session operation lock so
two no-root destroys cannot overwrite that intent with divergent contracts and
`up` cannot reopen a session while cleanup is pending. The lock and external
reaper intent are stored below the invalid session namespace
`.cp9/.internal/`; no valid session root can unlink either. If cleanup later
fails or stops during lock/journal publication, retry may reconstruct from the
supplied config only when that intent matches a classified pre-fence lock/temp
shell or empty fence plus optional destroy evidence. The intent is rejected as
ordinary persisted lifecycle reaper state. Successful local cleanup or GitHub
workflow handoff is checkpointed in it through session-root deletion, and the
intent is removed only after that deletion succeeds. Activated/unknown shells,
missing intent, concurrent divergent contracts, and config drift fail closed.
The legacy top-level names `reaper`, `destroy-summaries`, and
`lifecycle-operation-locks` remain reserved while old artifacts are read for
compatibility. Current processes acquire both the legacy and `.internal`
operation lock in a fixed order, preserving serialization with older CP9
processes during the transition.
