# Test1 standalone deployment

Test1 and Test2 share the AppRelease publisher-key preparation adapter. Before
importing an unregistered signed release, it calls the existing Controlplane
`publisher-trust` CAS when the selected release changes active keys. Same-key
upgrades and admitted releases do not rotate trust. Catalog owns the transition
receipt and readback; retry the same release inputs after an interruption.

This is the end-to-end operator runbook for the standalone Test1 stack. It
joins three independent deployment boundaries without turning them into a new
full-stack controller:

```text
L0 process group          .github/workflows/deploy-test-l0.yml
AppRelease + L2 metadata  .github/workflows/manage-test1-apprelease.yml
L1 AppService + UI        .github/workflows/deploy-test1-l1-application.yml
```

Dispatch all three Test1 controllers from reviewed L2 `main`. Product release
publishers may select `main` or an exact commit according to their owner
workflow; deployment consumes only the resulting immutable release identities.
Deployment never builds product code. The L0 controller resolves the selected
source identity and pulls its already-published Engine Release; the other two
controllers consume exact artifact digests. Each workflow owns only its
component.

This path preserves the Test1 database. It does not refresh the database, run a
full D3 deployment, apply a patch directory, or retain a previous deployment.

## Choose the smallest operation

| Change | Required operation |
|---|---|
| L0 Runtime API/internal code; Worker digest unchanged | L0 `upgrade` only |
| L0 Worker image changes | L0 `stop` → L0 `upgrade` → L1 `stop` → AppRelease `upgrade` → L0 `activate` → L1 `deploy` |
| Standalone Worker process closure changes | L0 `stop` → L0 `upgrade` → L1 `stop` → AppRelease `upgrade` → L0 `activate` → L1 `deploy` |
| L1/L2 metadata or AppRelease changes | L0 `stop` → L1 `stop` → AppRelease `upgrade` → L0 `activate` → L1 `deploy` |
| L1 AppService or UI image changes | L1 Application deploy only |
| First standalone Test1 installation | Complete first-install sequence below |
| Database refresh, schema migration, or complete D3 replacement | Use the operation that owns that change; do not add it here |

An AppRelease operation is not required merely because an L0 or L1 process
image changed. An L1 Application deployment is not required merely because
metadata changed. Changing one boundary must not silently redeploy another.

Database refresh requires standalone L0 and L1 to be stopped through their own
controllers. The refresh operation checks this before any receipt, connection
or database mutation; it does not manage those independent processes.

## First install

### 1. Publish and deploy L0 in bootstrap mode

Publish the selected NexusCRM commit through the L0 owner workflow, then deploy
that same source through the L2 controller:

```bash
gh workflow run publish-l0-engine-dev-release.yml \
  --repo pingcap-inc/NexusCRM \
  --ref main \
  -f source_commit=<exact-40-character-commit>
```

NexusCRM `main` is published automatically, so the manual publication command
is needed only for an exact non-main commit.

```bash
gh workflow run deploy-test-l0.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=upgrade \
  -f l0_source=<main-or-exact-40-character-commit>
```

Bootstrap starts Runtime API and keeps the matching supported Worker image carriers
stopped. It does not install application metadata.

Serving uses the role configuration produced by the AppRelease installer.
Current installs have nine owned work families and nine Worker processes.
Mutable Layout administration and its separate publication Worker are retired;
existing Page Layouts remain available through Runtime's read path. Historical
eight-, nine-, and ten-process installs remain valid during stopped upgrades.
The L0 process controller owns
starting/stopping those processes; the installer owns their configuration, not
their lifecycle.

### 2. Install the AppRelease and L2 metadata

Package `nexuscrm-app-packs` `main` or an exact commit through its owner
workflow:

```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 use the exact packaged L1 AppRelease and exact signed L2 installation
input:

```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:<digest>' \
  -f l2_installation_reference='ghcr.io/pingcap-inc/nexuscrm-serviceification-bom@sha256:<digest>'
```

This runs Controlplane, publishes the Catalog, and prepares the canonical L0
serving state. It does not operate the L0 process or deploy L1 binaries.

### 3. Activate L0

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

Activation starts the already-selected L0 release against the prepared state.
It does not select or pull another L0 image.

### 4. Deploy the L1 AppService and UI

```bash
gh workflow run deploy-test1-l1-application.yml \
  --repo pingcap-inc/nexuscrm-pingcap-pack \
  --ref main \
  -f operation=deploy \
  -f sfcrm_app_service_image='ghcr.io/pingcap-inc/nexuscrm-sfcrm-appservice@sha256:<digest>' \
  -f frontend_image='ghcr.io/pingcap-inc/nexuscrm-frontend-assets@sha256:<digest>'
```

The workflow proves the exact AppService against the live L0 before replacing
the existing AppService. It publishes the matching frontend only after the new
AppService is ready.

## Upgrade and retry rules

- Use the decision table above; do not replay unrelated steps.
- Use AppRelease `upgrade`, not `install`, after the first installation.
- Reuse the existing signed L2 installation input when neither its L2 content
  nor its AppRelease binding changed. Publish a new one only when one of those
  facts changed.
- Stop L0 and the L1 AppService before an AppRelease upgrade. The AppRelease
  workflow never stops or restarts either process itself. After activation,
  deploy the desired exact L1 images; reuse the pre-stop references when they
  did not change.
- A Worker image or process-closure change requires the stopped L0/AppRelease/activate sequence so
  Worker ownership and configuration move with the Worker release.
- A failed operation retains no rollback or previous-release state. Fix the
  owning failure and rerun the same exact desired release.
- Database refresh is an explicit independent operation and is never implied by
  install, upgrade, activation, or L1 Application deployment.

## Success criteria

The three workflow summaries are the authority for their selected immutable
artifacts. After the required operations succeed:

1. L0 Runtime API and all nine Worker processes are healthy and Runtime is ready.
2. The L1 AppService is ready against the live L0.
3. `https://nexuscrm-new-test1.pingcap.com/health` returns HTTP 200.
4. `https://nexuscrm-new-test1.pingcap.com/version.json` returns HTTP 200 and
   the selected frontend version.

Business-path inspection is separate evidence. It does not change the result
of these deployment workflows.

## Verified complete deployment

The complete standalone sequence succeeded on Test1 on 2026-08-29 using L2
controller commit `47627e59d0342c1bba89a6a182db66e322991755`:

- L0 source: `12f2509c7fa61ca1f34ae8920baae1938e37710e`
- L0 Engine Release:
  `ghcr.io/pingcap-inc/nexuscrm-l0-engine-release@sha256:8ed998fa10e8d9a9a51317d0a359633e566341f9813baa34e1e6db93ea1b6056`
- Runtime API:
  `ghcr.io/pingcap-inc/nexuscrm-runtime-api@sha256:cbeb38068bef491731ec012d8c136199144a670237b5f9f373fd6d9d2f34702f`
- Runtime Worker:
  `ghcr.io/pingcap-inc/nexuscrm-runtime-worker@sha256:15287690979e46006a59b301429be130d211451d97938078b697070a27c10e7e`
- AppRelease:
  `ghcr.io/pingcap-inc/nexuscrm-sfcrm-apprelease@sha256:8fd74e3e1393585184748d40a52e27489d5e7f5d0cc9ebe1074bd3d5c414cc35`
- signed L2 installation input:
  `ghcr.io/pingcap-inc/nexuscrm-serviceification-bom@sha256:df7db4e9ea3daa470d862bd656458885df521e0e440dc48449ac231251f6c64d`
- AppService:
  `ghcr.io/pingcap-inc/nexuscrm-sfcrm-appservice@sha256:b7dfd60b1e09904eb0f290ef8ea944b94d2364ebafd28e54b3f8e689bf92e998`
- frontend:
  `ghcr.io/pingcap-inc/nexuscrm-frontend-assets@sha256:6da1beeb465846d75f081bacb8f53172abd912f4397f10eb83a91c27342d019a`

Successful runs:

- [initial L0 bootstrap](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33213588965)
- [AppRelease install and Worker ownership](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33213671249)
- [initial L0 activation](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33214085071)
- [latest L0 replacement](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33227193368)
- [latest L1 AppService and UI replacement](https://github.com/pingcap-inc/nexuscrm-pingcap-pack/actions/runs/33227271143)

The final public health and version probes both returned HTTP 200. No database
refresh, AppRelease rebuild, signed-L2 rebuild, or full D3 deployment was used
for the final L0 and L1 replacements. The public frontend reported version
`sha256:735d8a8fcf8e64fa0eaf7beade4230e0da9e0fa5c884318c74d4dc772db2c61a`
and build date `2026-08-28T21:35:35+02:00`.

## Component references

- [L0 deployment and management](test-l0-only-deployment.md)
- [AppRelease installation and upgrade](test1-apprelease-management.md)
- [L1 AppService and UI deployment](test1-l1-application-deployment.md)
