# Standalone L0 deployment

This directory owns the app-blind NexusCRM L0 runtime composition boundary:

- one-shot `l0-controlplane` command in the Runtime API image for clean
  substrate initialization;
- one signed `nexuscrm-controlplane-catalog-reconcile` job image for immutable
  Catalog publication;
- one explicit Runtime API bootstrap mode that requires only L0 TLS material,
  remains unready, and rejects business RPCs;
- one `runtime-api` private TLS gRPC endpoint;
- one `runtime-worker` image instantiated with seven role configurations.

TiDB is external. The Compose project does not contain TiDB, sfcrm,
Integration, frontend, ingress, a generic Gateway, or L1/L2 metadata.

## Build the two long-running Runtime images

Use the same private-module BuildKit secrets as Runtime image CI:

```text
docker build --target runtime-api -f infra/runtime.Dockerfile -t nexuscrm-runtime-api:dev .
docker build --target runtime-worker -f infra/runtime.Dockerfile -t nexuscrm-runtime-worker:dev .
```

`l0-controlplane` is a job entrypoint carried by the Runtime API image, not a
third long-running service. Catalog reconcile remains the existing signed
one-shot image owned by `nexuscrm-controlplane`; it is selected by the shared
L0 release manifest and never opens another network endpoint.

## Database profiles

Copy one example outside source control and fill in its values:

- `database.local.env.example`: local TiUP/TiDB, explicitly non-TLS;
- `database.remote-tls.env.example`: TiDB Cloud or another remote TiDB,
  secure by default.
- `controlplane.env.example`: L0-only identity/crypto secrets used by
  `init-l0`; it contains no application metadata.

Local mode requires `TIDB_TLS_DISABLE=1`. Remote mode forbids that setting.
For a non-loopback host, the existing `nexuscrm-db` owner negotiates TLS 1.2+
using the image's system CA roots and verifies `TIDB_HOST`. The deployment
script does not implement a second SQL or TLS client.

## Clean deployment and activation order

### 1. Initialize the L0 substrate

```text
python3 infra/runtime/deploy.py init \
  --database-mode local \
  --database-env-file /secure/path/database.local.env \
  --controlplane-env-file /secure/path/controlplane.env \
  --runtime-api-image nexuscrm-runtime-api:dev
```

Use `--database-mode remote` and the remote TLS env file for TiDB Cloud. Remote
init requires `--controlplane-env-file` with non-empty `JWT_SECRET` and
`NEXUS_ADMIN_PASSWORD`; this prevents unstable generated identity secrets and
secret-bearing init logs. Local init may omit the file for disposable testing.

### 2. Deploy L0 independently

Prepare only the L0-owned transport files:

```text
l0-state/
  secrets/tls/server.crt
  secrets/tls/server.key
```

Then deploy the final endpoint without any application metadata:

```text
python3 infra/runtime/deploy.py deploy \
  --state-root /secure/path/l0-state \
  --runtime-api-image nexuscrm-runtime-api:dev
```

This uses the same Runtime API image with the explicit `bootstrap` argument.
The container-internal `/livez` succeeds, `/readyz` does not, and every
business RPC fails closed. Runtime's admin port is deliberately not published;
use `deploy.py status` or the container health state rather than sending HTTP
to the published gRPC port. Bootstrap does not connect to TiDB or require
`serving.json`, AppInstallation, capabilities, Worker configuration, or a
Catalog snapshot.

## Connect to the private endpoint

The `deploy` and `activate` commands publish only the TLS gRPC endpoint. By
default it is:

```text
127.0.0.1:9090
```

Use `--runtime-grpc-port <host-port>` to choose another loopback port. Trust
the certificate at `<state-root>/secrets/tls/server.crt`; do not disable TLS
verification. For example, this checks only the transport handshake:

```text
openssl s_client -connect 127.0.0.1:9090 \
  -CAfile /secure/path/l0-state/secrets/tls/server.crt \
  -tls1_3
```

Bootstrap intentionally has no business token. After activation, sfcrm and
Integration use the generated neutral gRPC clients with:

- their own `authorization: Bearer <workload-token>` credential;
- the protocol major/minor and required-capability metadata owned by the
  neutral contract client;
- `x-nexuscrm-runtime-delegation` for the signed end-user delegation when the
  RPC requires user authority.

There is no universal operator token to print or copy from the deployment.
Workload credentials and delegations are scoped to the published
AppInstallation/security authority and must be provisioned to the owning L1
service, not to a browser or a human test command.

### 3. Publish L1 metadata through L0 Catalog

The L1 installation operation supplies a neutral reconcile input to the signed
L0 Catalog job selected by the release manifest. That job writes immutable
Catalog output into an L0-owned state root. The deployer does not inspect the
application values. Before `activate`, the state root has this neutral layout:

```text
config/
  runtime.env
  serving.json
  workers/
    audit-history-fanout.json
    file-upload-lifecycle.json
    platform-command.json
    record-command-lifecycle.json
    record-event-dispatch.json
    rollup-delta.json
    scheduled-flow.json
secrets/
  workload-identities.json
  signing-keys.json
  tls/server.crt
  tls/server.key
  workers/<role>.token
data/
  catalog/
  files/
  uploads/
```

`serving.json` is an opaque Catalog-published activation artifact at the
deployment boundary. Runtime remains its only semantic validator.

### 4. Activate Runtime API and Worker

```text
python3 infra/runtime/deploy.py activate \
  --database-mode local \
  --database-env-file /secure/path/database.local.env \
  --state-root /secure/path/l0-state \
  --runtime-api-image nexuscrm-runtime-api:dev \
  --runtime-worker-image nexuscrm-runtime-worker:dev
```

The command stops bootstrap, validates the full L0 state and database profile,
starts the serving project, and waits for container health. Only
`127.0.0.1:9090` is published by default. Runtime and Worker admin ports remain
container-internal.

Status and teardown do not require application metadata or database arguments:

```text
python3 infra/runtime/deploy.py status --project nexuscrm-l0
python3 infra/runtime/deploy.py down --project nexuscrm-l0
```

Use explicit release tags or image digests for a release deployment. `:dev` is
shown only for a local source build.

## Boundary

The L0 deployer owns process composition and generic authority mounts. Deploy
and business activation are separate states. It does not install a particular
L1, choose tenant metadata, reset a database, mutate
production routing, or construct a second metadata source. See
[`docs/design/l0-independent-deployment-working-notes.md`](../../docs/design/l0-independent-deployment-working-notes.md)
for the decisions and known constraints.
