# AI VM Deployment

The AI environment file is `.env`. For legacy installations, rerun the updated
AI installer: it copies `.env.ai` to `.env` only if `.env` is absent, preserving
credentials and the BYOK encryption key. The old file remains as a backup;
keep it restricted because it also contains secrets. If both exist, `.env` wins.
Updated manager commands use `ai/.env`, so run this migration before updating
or managing an older installation.

Run `bash install.sh` on Linux or `./install.ps1` in PowerShell. The installer
asks for database, storage, and ERP gateway settings and writes `.env`.
Database passwords are entered raw; the installer generates URL-encoded values
for both database connection URLs. Do not manually encode the actual password.
Existing installations should rerun the updated installer with their existing
settings to generate `DB_USER_URLENCODED` and `DB_PASSWORD_URLENCODED`.

## TensorFlow framework schema repair

AI startup applies migration 104 to align PostgreSQL's framework enum with
SQLAlchemy: the legacy `tensorflow` label becomes `TENSORFLOW`, preserving
existing model rows. Databases without a TensorFlow label receive the canonical
label. API request and response values remain lowercase (`tensorflow`).
Deploy the updated AI service and restart it to apply the migration. Database
permissions must allow altering the enum; migration failures stop startup
rather than being ignored.

## Platform and ERP Connections

On the same server with sibling `apis` and `ai` deployments, the installer
discovers `../apis/.env` and `../apis/keys/public.pem`. It reuses available ERP
gateway URL, the AI ingest token, and externally reachable
database settings without asking for them again or exposing credentials.
Explicit AI parameters/environment overrides and existing `ai/.env` values
take precedence. ERP environment files are parsed, never executed.
Database hosts such as `postgres` or `localhost` are not reused automatically:
the standalone AI container is on a separate Docker network. Missing or
unreachable settings still require input. The installer does not extract
credentials from Auth's private installation-state volume.

AI uses ERP Auth pairing exclusively for Platform access. For remote/hybrid
installs, both installers request a one-time ERP pairing code, entered without
echoing it. They no longer accept direct Platform URL or installation ID/key
options, import installation credentials from sibling ERP settings, or offer an
authentication-mode picker. Rerunning an installer removes obsolete
direct Platform URLs, install ID/key, JWT/internal tokens, installation-state
and mTLS settings, enrollment tokens, and unrelated service-relay tokens from
AI's generated `.env`, including exported assignments and old
`KORO_PLATFORM_*` installer overrides. AI uses only its own saved pairing;
the Payments/Notifications/Maps relay tokens do not belong in AI's environment.

At runtime AI loads its saved ERP pairing, or exchanges `AI_PAIRING_CODE`
with ERP Auth. Auth returns a dedicated relay credential and the JWT public
key plus Platform signing trust, never ERP's installation key. Platform calls,
including signing-trust refreshes, go through Auth with Auth's enrolled
installation credentials. Old direct credentials cannot override a saved pairing.

Before upgrading an AI instance that used direct credentials, enroll ERP Auth
with Platform and generate an AI pairing code. Existing paired instances need
no new pairing on normal restarts; preserve the `ai_connection` volume.
Unpaired remote/hybrid instances fail explicitly with a pairing-required error.
Local-only wallet mode still works without a Platform connection.

Platform-to-AI user requests carry a short-lived Platform-signed token bound to
the HTTP method, path, and body. AI verifies it using the Platform trust supplied
during pairing and refreshed through the Auth relay. Direct ERP user JWTs
remain supported through `X-User-Token`.

### ERP Pairing

In the ERP Business app, a tenant administrator opens **AI > Settings > Connect
AI Server**, selects **Generate Pairing Code**, and uses the copy button. The
screen displays the remaining lifetime; the code expires after five minutes
and works once. Generate it when the AI installer is ready for the code. The
connection controls remain available even if the AI service is not running.
Do not share the code publicly or store it in support tickets. The installer
prompts users with this screen location when a pairing code is needed.

An authenticated tenant administrator creates a code with
`POST /api/v1/auth/ai-platform/pairing-code` through the ERP gateway (send `{}`
as JSON and the normal `X-User-Token` header). Direct Auth exposes
`POST /api/v1/ai-platform/pairing-code`. The code expires after five minutes and
is consumed once; creating another replaces the pending code for that scope.

Supply it as `KORO_AI_PAIRING_CODE` (PowerShell: `-PairingCode`) when running the
AI installer. Configure `GATEWAY_SERVICE_URL` and, if necessary,
`KORO_AI_ERP_AUTH_BASE_URL` (PowerShell: `-ErpAuthBaseUrl`) with the exact Auth
base URL. If the explicit base is unset, AI uses `AUTH_SERVICE_URL/api/v1` when
configured, otherwise `GATEWAY_SERVICE_URL/api/v1/auth`. Connection and pairing
URLs must use HTTPS, except for HTTP on loopback during local development.

Pairing supplies the JWT public key automatically, so no manual PEM transfer
is necessary for this mode. The AI connection is saved with restricted
permissions on the persistent `ai_connection` volume. Auth stores hashes of
relay credentials alongside its installation state. Keep both volumes across
restarts. Once paired, AI uses saved state instead of consuming the code again.
Remove the spent code from `.env` after successful pairing; for an installer
rerun without a local PEM, provide a fresh code or export the existing ERP public
key. Pairing state still takes precedence over a supplied code on normal restarts.

`DELETE /api/v1/auth/ai-platform/pairings` lets an administrator revoke all AI
relay credentials for their tenant. To re-pair after revocation, remove only
`/app/data/ai-platform.json` from the AI connection volume and supply a fresh
code. Do not delete the BYOK encryption key or model-storage volumes.

For an AI host serving multiple SaaS tenants, the enrolled SaaS host's
`super_admin` must explicitly create a code with `{"shared_saas":true}`.
Auth must have `DEPLOYMENT_MODE=saas` and its existing enrolled installation
state. The relay uses that installation's credentials and Platform-issued trust.
That operator-only relay can serve
multiple tenants; a normal tenant pairing cannot. The host super administrator
can revoke shared pairings using the same DELETE endpoint. Customer-hosted
on-prem installations use tenant-scoped pairings and ERP's install credentials.

The relay accepts only AI wallet reserve/debit/release, policy, capabilities,
offline events, managed AI credentials, and the paired tenant's AI policy patch.
It rejects requests outside that allowlist and rejects cross-tenant requests
from tenant pairings.
Auth reads `PLATFORM_API_URL` from its own environment and uses its existing
installation credentials, so Platform connections need not be repeated on AI.
Deploy updated Auth and gateway images before using the pairing installer.

AI uses three separate destinations:

- `GATEWAY_SERVICE_URL`: ERP business data, through the ERP gateway.
- ERP Auth's paired relay: Platform wallet, policy, reserve/debit, signing trust,
  and usage sync. Configure `AI_ERP_AUTH_BASE_URL` if automatic Auth URL selection
  does not match the reachable endpoint.
- `AUTH_SERVICE_URL`: optional direct Auth connection for live user counts.
  Leave blank if Auth's internal endpoint is not reachable from AI.

The default wallet mode is `remote`. Both `remote` and `hybrid` require ERP
pairing. The installer accepts `KORO_AI_PAIRING_CODE`, `KORO_AI_ERP_AUTH_BASE_URL`,
`KORO_AI_INGEST_TOKEN`, and `KORO_AUTH_SERVICE_URL`. PowerShell also accepts
`-PairingCode`, `-ErpAuthBaseUrl`, `-AiIngestToken`, and `-AuthServiceUrl`.
Use HTTPS for connections across servers; `localhost` inside AI is not the host.

Go ERP document-ingestion jobs use a separate, endpoint-scoped `AI_INGEST_TOKEN`.
The ERP installer generates it and passes it only to the accounting, CRM, HRM,
and documents ingestion jobs. The AI installer reuses it from a sibling ERP
`.env`; for a separate AI host, configure the same value on both deployments.
Leaving it blank disables cron-driven ingestion; it does not grant access to
chat, sessions, or admin APIs.

For managed chat, configure `MANAGED_OPENAI_API_KEY` on Platform API and recreate
Platform API after changing it. Platform returns the managed key in the trusted
wallet reservation. Setting `OPENAI_API_KEY` on AI alone does not configure this
remote managed-chat path. Never publish install credentials or provider keys.

`KORO_AI_WALLET_MODE=local` (PowerShell: `-WalletMode local`) explicitly selects
the existing offline wallet path without Platform credentials. This is not a
billing bypass: its offline licence requirements still apply. Runtime settings
already in `.env`, including `AI_OFFLINE_*`, are passed into the AI container;
any referenced licence files must also be mounted at the configured paths.

## BYOK Encryption and Reinstallation

The installer generates a random 32-byte `BYOK_ENCRYPTION_KEY` once and stores
it in `.env`. Reruns preserve it, as well as existing database, storage,
Platform, tuning, proxy, and custom settings. Environment overrides use the
`KORO_` prefix. Existing environment files are parsed by Docker Compose, never
executed as shell scripts. An explicit different encryption key is rejected
instead of silently making saved tenant provider credentials unreadable.

You may supply an existing 32-byte key (raw or base64) using
`KORO_BYOK_ENCRYPTION_KEY` or PowerShell's `-ByokEncryptionKey` on first setup.
Back up `.env` securely and restrict access to its secrets. This key is
separate from Auth's RSA JWT key and ERP's `DATA_ENCRYPTION_KEY`.

For an older independent deployment, download the updated bundle and rerun the
AI installer. It reloads existing settings, prompts for missing Platform
settings or a pairing code, and generates a BYOK key only when none exists. If a BYOK key was
previously supplied outside `.env`, supply that same key during this upgrade;
do not generate a replacement. Recreate the AI container to apply the settings.

## ERP JWT Public Key

Before installing AI, ask your Go ERP installation's administrator for its
existing JWT public key: `keys/public.pem` in the main ERP deployment directory.
Copy that file securely to `keys/public.pem` in the AI deployment directory,
or supply its local path through `KORO_JWT_PUBLIC_KEY_PATH`.
Do not generate a replacement key pair, copy `private.pem`, or use
`licence_public.pem`; these are not the key AI needs to verify ERP JWTs.

From the main Go ERP deployment directory, export a validated copy with:

```sh
./manage.sh export-public-key
```

On Windows:

```powershell
.\manage.ps1 -Command export-public-key
```

Both commands create `erp-public.pem` without changing the installed keys or
requiring running containers. Transfer that file to the AI server and provide
its local path to the installer, or save it as `ai/keys/public.pem`. To choose
another export path, use `./manage.sh export-public-key /path/to/public.pem` or
`-OutputPath C:\path\public.pem` in PowerShell. Existing outputs are not overwritten.

The main ERP installer generates one RSA key pair in its `keys` directory. Auth
uses the private key to sign tokens; the other services mount that directory
read-only and use `public.pem` to verify them. AI on a separate server needs a
copy of that same public key, not a new key pair or the licence signing key.

The AI installer preserves and validates `keys/public.pem`. If it is absent,
it looks in `../apis/keys/public.pem`, then asks for a local source path or a
pasted public PEM. Paste the complete block from `-----BEGIN PUBLIC KEY-----`
through `-----END PUBLIC KEY-----`; the installer reads multiple lines,
validates the RSA public key, and creates `keys/public.pem` automatically.
An invalid or private PEM is rejected without replacing an existing key.
You can also set `KORO_JWT_PUBLIC_KEY_PATH` or `KORO_JWT_PUBLIC_KEY_PEM`, or pass
`-JwtPublicKeyPath` or `-JwtPublicKeyPem` to PowerShell. Supply a path or PEM,
not both. This public PEM is not a provider API key or Platform token.
Transfer only the public PEM from your trusted ERP server before installation.
Never copy `private.pem` onto the AI server. Linux validation needs OpenSSL;
PowerShell validation needs OpenSSL or PowerShell 7 with .NET 5 or newer.
If Auth's signing key is rotated, copy its new public key and restart AI.

## Storage

- `local`: no object-storage service or credentials; this is the default.
- `s3`: external S3-compatible storage. Set the base endpoint without the bucket
  path. For Google Cloud Storage use `https://storage.googleapis.com`, a separate
  bucket name, and GCS HMAC access credentials.
- `minio`: requires an explicitly built or operator-approved image. Set
  `KORO_MINIO_IMAGE` or pass `-MinioImage` to PowerShell. The installer records it
  as `MINIO_IMAGE`; it does not assume `minio/minio:latest` is available. MinIO's
  community repository is unmaintained, so assess support and security before
  choosing a community build for production.

The installers do not migrate existing object-storage data between modes.
Local models and datasets now use the persistent `ai_storage` volume. If you
have files inside an older container's `/app/storage`, back them up before
recreating it and restore them into the new volume; the installer does not
automatically migrate files from an old container's writable layer.

## Start and Inspect

```sh
docker compose --env-file .env -f docker-compose.yml up -d
docker compose --env-file .env -f docker-compose.yml logs --tail 100 ai
```

Add `--profile minio` when using the MinIO storage mode. If AI is on a separate
host, the PostgreSQL address must resolve and be reachable from that host;
Docker service names from the main ERP stack will not resolve automatically.
The installer reports container startup, not application health. Check the AI
logs and `/health` after startup to confirm migrations and external connections
succeeded.