# Test1 AppRelease installation and upgrade

This is the standalone Test1 path for changing application metadata without a
full-stack deployment. It has one CI workflow and two operator operations:

```text
.github/workflows/manage-test1-apprelease.yml
install | upgrade
```

For the complete Test1 sequence and the smallest-operation decision table, see
[`test1-standalone-deployment.md`](test1-standalone-deployment.md).

## Boundary

This workflow owns the AppRelease transition:

```text
packaged L1 AppRelease + selected L2 installation input
  → Controlplane applies the complete L1/L2 pack set
  → Controlplane publishes one Catalog revision
  → installer prepares the canonical L0 serving state
```

It does not build, select, pull, or replace an L0 image. It does not deploy the
L1 AppService, frontend, Integration Service, or Runtime Workers. It does not
refresh the database, accept a patch, run `ApplyAll`, or maintain its own
active-state file.

It never operates the L0 process. It reads the protocol/capability contract
published by L0 upgrade and prepares the one canonical L0 state root. After it
succeeds, run the separate L0 management CI with `operation=activate`. There is
no handoff artifact, activation digest, release pointer, or second state root.

Controlplane pack convergence is offline. The first install may run while the
standalone L0 container is in bootstrap mode. Before an upgrade, stop both the
L0 serving process and the L1 AppService through their separate management CIs.
The AppRelease workflow only verifies that the serving target is quiesced; it
never stops either process itself.

## Operations

Use `install` for the first AppInstallation. It creates the first `PREPARE`
operation at Catalog revision and SecurityEpoch zero. If publication succeeded
but state preparation did not, rerunning the same install re-enters through the
normal same-release upgrade path.

Use `upgrade` when an AppInstallation already exists. It reads the current
AppInstallation ID, Catalog revision, SecurityEpoch, and generations from the
database and submits an `UPGRADE` against those exact values. Selecting the
same L1 AppRelease with changed L2 metadata uses the existing supported
active-release refresh path; it does not invent another installation.

Both operations use the same implementation. The operation name changes only
the Controlplane lifecycle precondition.

## Run

First package the selected L1 source with the `nexuscrm-app-packs` owner
workflow. The controller runs from `main`; its `source` input accepts `main` or
one exact commit and reports an immutable AppRelease reference:

```bash
gh workflow run package-sfcrm-apprelease.yml \
  --repo pingcap-inc/nexuscrm-app-packs \
  --ref main \
  -f source=<main-or-exact-40-character-commit>
```

Then select the matching signed L2 installation input and run:

```bash
gh workflow run manage-test1-apprelease.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=install \
  -f apprelease_reference='ghcr.io/pingcap-inc/nexuscrm-sfcrm-apprelease@sha256:<l1-digest>' \
  -f l2_installation_reference='ghcr.io/pingcap-inc/nexuscrm-serviceification-bom@sha256:<l2-digest>'
```

For a later application update, first stop L0 and the L1 AppService, then run
`upgrade` with the new exact L1 and L2 references:

```bash
gh workflow run deploy-test-l0.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=stop

gh workflow run deploy-test1-l1-application.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=stop
```

The complete operator sequence is `L0 stop → L1 stop → AppRelease upgrade → L0
activate → L1 deploy`. The final L1 deploy uses the desired exact AppService and
frontend images; reuse the pre-stop references when those images did not change.
These are direct operations with no handoff artifact or additional deployment
state.

The L1 OCI contains only the AppRelease package, its publisher attestation and
verification key, and its source identity. It contains no L2 data. The signed
L2 input supplies the selected overlay, Approval publication, deployment-tools
bundle, and one-shot Catalog Controlplane image. The installer requires both
references to name the same AppRelease before changing the database. It never
consumes the L2 input's Runtime image selection.

## Failure behavior

The shared standalone Catalog reader distinguishes a fresh installation from
an interrupted upgrade. Retry the same AppRelease and L2 inputs: the original
Catalog publication supplies the starting revision, and the pending operation's
idempotency key must match before the existing reconciler can continue. It does
not create a new recovery record or take over a different pending release.
Final read-back still requires an idle ACTIVE installation.

If Controlplane, Catalog publication, or state preparation fails, L0 remains
stopped. Fix the owning failure and rerun the same operation with the same
signed candidate. Retry reads the database authority and uses the normal
PREPARE or UPGRADE path; there is no recovery state machine or automatic
rollback.

After a successful install or upgrade, activate the unchanged L0 image:

```bash
gh workflow run deploy-test-l0.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=activate
```

Then deploy the desired exact L1 AppService and frontend as described in
[Test1 L1 Application deployment](test1-l1-application-deployment.md).

## Verified evidence

The successful AppRelease installation, immutable inputs, activation, and
database-preserving result are recorded once in the
[integrated Test1 runbook](test1-standalone-deployment.md#verified-complete-deployment).
