# Cassandra Infrastructure Contract

This stack is staged as a parallel replacement for Veles. Do not remove or
mutate Veles data while Cassandra is being validated.

## Cluster Contract

- Namespace: `cassandra`
- Hostname: `https://cassandra.bstein.dev`
- Namespace: `cassandra`; no alternate alpha namespace is used.
- Backend service: `cassandra-backend.cassandra.svc.cluster.local:80`
- Frontend service: `cassandra-frontend.cassandra.svc.cluster.local:80`
- Postgres service: `cassandra-postgres.cassandra.svc.cluster.local:5432`
- Artifact PVC: `cassandra-artifacts`, mounted at `/data/cassandra-artifacts`
- Storage classes: `cassandra-db`, `cassandra-artifacts`
- Images:
  - `registry.bstein.dev/cassandra/cassandra-backend`
  - `registry.bstein.dev/cassandra/cassandra-frontend`
  - `registry.bstein.dev/cassandra/cassandra-sim-worker`
- Backend `http` container port: `8796`
- Frontend `http` container port: `8080`
- Generator and retention workloads start disabled so copied data is not
  consumed or pruned before the migration is verified.

## Auth Contract

Cassandra owns authorization in the app. The `cassandra` Ingress does not use oauth2-proxy or Traefik forward-auth, so no ingress/auth layer should strip OIDC token claims. The app should validate tokens from `https://sso.bstein.dev/realms/cassandra` and expect stable `sub`, `email`, `preferred_username`, `groups`, and `realm_access.roles` claims. Do not scale Cassandra for real user traffic until native OIDC login/session flow is implemented and smoke-tested.

The Keycloak realm setup creates both groups and realm roles named `alpha` and `admin`. Members of the `alpha` group receive the `alpha` realm role; members of `admin` receive both `alpha` and `admin`. Built-in/meta strategies can stay universal, while runs and user-created strategies should remain user-scoped in the Cassandra database.

## Runtime Env

Cassandra should consume:

- `CASSANDRA_PUBLIC_BASE_URL=https://cassandra.bstein.dev`
- `CASSANDRA_OIDC_ISSUER=https://sso.bstein.dev/realms/cassandra`
- `CASSANDRA_OIDC_CLIENT_ID=cassandra-web`
- `CASSANDRA_OIDC_REQUIRED_GROUPS=alpha,cassandra-tester,admin`
- `CASSANDRA_OIDC_GROUPS_CLAIM=groups`
- `CASSANDRA_OIDC_ROLES_CLAIM=realm_access.roles`
- `DATABASE_URL` from `kv/data/atlas/cassandra/cassandra-db`
- `CASSANDRA_SESSION_SECRET` from `kv/data/atlas/cassandra/app-secrets`
- `CASSANDRA_BYOK_ENCRYPTION_KEY` from `kv/data/atlas/cassandra/app-secrets`

User OpenAI API keys must stay in the Cassandra database encrypted with
`CASSANDRA_BYOK_ENCRYPTION_KEY`; do not store per-user BYOK secrets in Vault.

Backend runtime secrets are synced from Vault by `cassandra-vault` into the generated Kubernetes Secret `cassandra-runtime-secrets`; no secret values are committed. The backend consumes that secret with `envFrom`.

## Artifact Contract

`cassandra-artifacts` is an RWO Longhorn PVC mounted into backend pods at `/data/cassandra-artifacts`. Backend pods own artifact writes and serving. Simulation Jobs should not mount or write directly to this PVC unless they are explicitly scheduled on Oceanus with the Cassandra toleration and the app has chosen a same-node direct-write model. Queue-mediated upload/copy through the backend remains the safer default until the app contract settles.

Backend, simulation workers, and retention/cleanup workers must run on Oceanus/titan-23 when they need artifact access. Frontend pods must not mount `cassandra-artifacts`.

## Simulation Jobs

The backend service account can create, watch, and delete Jobs only inside the `cassandra` namespace. Simulation pods should use service account `cassandra-sim`, set `automountServiceAccountToken: false`, and use:

```yaml
priorityClassName: cassandra-sim
nodeSelector:
  cassandra.bstein.dev/node-pool: oceanus
tolerations:
  - key: veles.bstein.dev/simulation
    operator: Equal
    value: "true"
    effect: NoSchedule
```

The toleration intentionally references the existing Veles taint while Veles and
Cassandra run side by side. The node carries Cassandra labels for selection, but
adding a Cassandra taint waits until Veles no longer needs the same node.

Retention/cleanup Jobs that touch artifacts should use the same node selector and toleration. If they do not need Kubernetes API access, use `cassandra-sim`; otherwise keep control-plane actions in the backend/controller and run artifact cleanup through a no-token worker.

## Staged Operator Steps

1. Join `titan-23`/Oceanus to Atlas as a worker.
2. Use Metis with `titan-23` in `METIS_FLASH_HOSTS`; the existing node secret placeholder uses `192.168.22.23`.
3. Confirm the node normalizer applies the Cassandra labels and taint.
4. Add Oceanus Longhorn disks at paths tagged by the Longhorn tag ensure job.
5. Let Vault policy reconciliation run, then unsuspend `cassandra-secrets-ensure-2`.
6. Unsuspend `cassandra-realm-ensure-1` in `services/keycloak` to create the realm/client secret, groups, and roles.
7. Create the Harbor `cassandra` project or robot access before image automation is enabled in production.
8. Let `cassandra-postgres` come up on the new `cassandra-db` PVC.
9. Unsuspend `cassandra-db-copy-from-veles-1` once to copy Veles Postgres into
   Cassandra and run table/count/hash verification against the target.
10. Inspect new Cassandra Longhorn volumes and confirm each two-replica volume
    has one titan-23 local replica plus one Asteria replica. If Longhorn places
    replicas differently, fix placement before live use.
11. Copy artifacts from `veles-artifacts` to `cassandra-artifacts` and verify
    file counts/bytes before enabling retention.
12. Smoke-test `https://cassandra.bstein.dev`, only then enable generator and
    retention workers.
13. Redirect `veles.bstein.dev` only after Cassandra is validated. Do not delete
    Veles resources until explicitly approved.

## Copy Commands

The DB copy job is suspended by default. After `cassandra-postgres` is ready:

```bash
kubectl -n cassandra patch job cassandra-db-copy-from-veles-1 \
  --type=merge -p '{"spec":{"suspend":false}}'
kubectl -n cassandra logs job/cassandra-db-copy-from-veles-1 -f
```

The artifact copy is also opt-in. Start the read-only Veles exporter, run the
Cassandra importer, inspect its verification summary, then scale the exporter
back down:

```bash
kubectl -n veles scale deploy/veles-artifact-export-for-cassandra --replicas=1
kubectl -n veles rollout status deploy/veles-artifact-export-for-cassandra
kubectl -n cassandra patch job cassandra-artifact-copy-from-veles-1 \
  --type=merge -p '{"spec":{"suspend":false}}'
kubectl -n cassandra logs job/cassandra-artifact-copy-from-veles-1 -f
kubectl -n veles scale deploy/veles-artifact-export-for-cassandra --replicas=0
```

The artifact importer writes verification details under
`/data/cassandra-artifacts/.cassandra-artifact-migration/`. If the Veles
artifact PVC changes during the copy, verification can fail correctly; rerun
after quiescing artifact writers or after a final delta-copy window.

## Assumptions

- `cassandra-artifacts` is RWO for alpha; simulation workers should either run
  on Oceanus with the backend or stream logs to the backend, which owns writes.
- Cassandra storage classes request two Longhorn replicas with `best-effort`
  data locality. This is intended to keep one replica local to titan-23 and one
  replica on an Asteria disk, but actual placement must be inspected after PVC
  provisioning.
- Longhorn default backup target is `s3://atlas-soteria@us-west-004/` with
  credential secret `longhorn-backup-b2`. As of the migration prep, remote
  backups are failing because the object-store cap is exceeded; local snapshots
  still work, but remote backup protection is not healthy until that cap is
  resolved.
- The Jenkins Cassandra job should publish:
  `registry.bstein.dev/cassandra/cassandra-backend`,
  `registry.bstein.dev/cassandra/cassandra-frontend`,
  `registry.bstein.dev/cassandra/cassandra-sim-worker`, and
  `registry.bstein.dev/cassandra/cassandra-generator-worker`.
