# Test environments

Each Test slot has a fixed public route, runtime root and TiDB branch. Shared
nginx routes are defined in [`nginx.conf`](nginx.conf); component deployment
changes only the selected slot.

| Slot | Public URL | Backend port | Deployment authority |
|---|---|---:|---|
| Test1 | https://nexuscrm-new-test1.pingcap.com | 3301 | [Standalone runbook](../../../docs/test1-standalone-deployment.md); signed-BOM D3 remains supported |
| Test2 | https://nexuscrm-new-test2.pingcap.com | 3302 | [Standalone runbook](../../../docs/test2-standalone-deployment.md) |
| Test3 | https://nexuscrm-new-test3.pingcap.com | 3303 | `deploy-test-serviceification.yml`, signed-BOM D3 |

For Test1/Test3 D3, the exact controller selected by authorized dispatch owns the
operation. Full deployment consumes an immutable signed BOM. SFCRM image
replacement consumes a published image digest and is a private-Test diagnostic;
it does not publish a release or change metadata. Read the current workflow and
`scripts/run_serviceification_d3_test.py` before choosing operation inputs.

The Test2 runbook links the separate L0, AppRelease and L1 component contracts.
Test2 is not accepted by the D3 deployment controller. Single-process Test
deployment and its bootstrap flags are retired.

Database maintenance has a separate owner:
`refresh-test-database-from-production.yml`. Never combine database refresh with
a normal application upgrade. The per-slot operation lock is shared by each
slot's component and maintenance workflows.

Deployment success proves the selected components are running and ready.
Functional Env Inspection and external-integration readiness require their own
verification.

## Host-only Test3 development

`scripts/run_serviceification_d3_test.py` also accepts explicit
`--local-unsigned` mode on Test3 only, outside GitHub Actions. This is a
database-preserving development deployment, not a signed release or promotion
input. Test1, Test2, Staging and Prod do not accept its manifest.

Build images with the existing owner-local builders first. Use
`prepare-local --help` for preparation inputs: the desired BOM, six **Docker
image IDs**, exact Core/AppRelease/L2 checkouts, the verified deployment-tools
bundle, and the Approval publication compiled by the existing AppPacks compiler
for the selected L2 tree. `prepare-local --local-unsigned` writes a new candidate
directory; it never overwrites a candidate or builds/pushes images.
Without `--desired-bom`, preparation derives the six local roles from the formal
request, reports the exclusion of a formal-only MCP Worker on stderr, and freezes
that local selection and its digest into the candidate. This preserves the
existing six-image development command when formal Staging enables MCP. An
explicit `--desired-bom` is never filtered: a request containing MCP is rejected
and must use the signed deployment path. This local candidate proves only its
six selected roles, not MCP or equivalence with the formal Staging topology.
The controller must be committed. Preparation binds that exact controller,
not a moving `main` ref, and verifies image source labels, role, platform and
frontend tuple. Signed AppRelease verification and L2 lineage checks remain.

Deploy the resulting manifest using the same controller:

```bash
python3 scripts/run_serviceification_d3_test.py deploy --local-unsigned \
  --env-file "$TEST3_ENV_FILE" --run-id "$DEPLOYMENT_RUN_ID" \
  --resolved-bom "$CANDIDATE_DIR/resolved-bom.json" \
  --core-root "$CORE_ROOT" --app-packs-root "$APP_RELEASE_ROOT" \
  --l2-root "$L2_ROOT" --preserve-current-database
```

Use the slot's existing operation lock around mutation commands. Local images
are inspected and run by immutable Docker image ID, without registry pulls.
The existing stopped upgrade, Catalog, ownership/fencing and readiness checks
remain in effect. Resume the same run and sealed manifest with
`--local-unsigned`; individual-image replacement commands are not supported
for local candidates. Evidence explicitly says
`test-only-unsigned-non-promotable` and contains no invented signature,
provenance or SBOM digests. `verify-serving` verifies deployment health only,
not full functionality or external synchronization.
