# Orca User Acceptance Testing (UAT) Plan **Version**: v0.13 (production hardening round 2) **Gate**: v1.0.0 production-ready tag is deferred until this UAT passes **Signoff**: run `scripts/uat-signoff.sh` on the lead node and paste the output back ## Prerequisites ### Hardware | Role | OS | Requirements | |------|-----|-------------| | **lead** | Ubuntu 22.04 LTS | Operator laptop or VM; SSH key; `orca` binary (built from v0.13 tag) | | **pve01** | Proxmox VE 8/9 | Bare-metal or nested; SSH root access; orca SSH key pre-staged | | **worker01** | Ubuntu 22.04 LTS | VM or bare-metal; SSH root access; orca SSH key pre-staged | ### Alternative topology (3x Ubuntu, no Proxmox) If a Proxmox host is unavailable, run the UAT with 3x Ubuntu hosts. Use `--type linux` for all remote nodes. Proxmox-specific claims (`doctor proxmox`, PVE role, sudoers) are **skipped** in this path. The signoff script reports exercised vs. skipped claims. ## Step-by-step UAT ### Step 1: Install orca + initialize the cluster Install orca (1-liner): ```sh curl -fsSL https://git.cloudinit.dev/coreci/orca/raw/branch/main/scripts/install.sh | bash ``` Initialize the cluster: ```sh export ORCA_HOME=~/orca-uat orca init ``` **Expected**: orca init creates: - CA cert + server cert - SSH keypair (orca_ssh_key + orca_ssh_key.pub) - known_hosts file (empty, for TOFU capture) - Master key (for secrets encryption) - Traefik data-plane ingress (binary + systemd unit + config) - Localhost node registered **Pre-staging remote nodes**: `orca init` interactively prompts for remote host addresses and runs `ssh-copy-id` automatically (password prompt passes through). Enter each host (pve01, worker01) when prompted, or press Enter to skip. The orca public key is deployed to each host; TOFU host-key capture is automatic on the first `orca node join` — no manual fingerprint pinning needed. ### Step 2: Onboard the Proxmox host ```sh orca node join --type proxmox \ --host pve01 \ --ssh-user root ``` **Expected**: SSH bootstrap succeeds, orca user created, PVE role assigned, node registered as `ready` with `kind=proxmox`. ### Step 3: Onboard the Ubuntu worker ```sh orca node join --type linux \ --host worker01 \ --ssh-user root ``` **Expected**: SSH bootstrap succeeds, orca user created, drift-events dir created, node registered as `ready` with `kind=linux`. ### Step 4: Verify nodes ```sh orca node list orca node list --json ``` **Expected**: 3 nodes listed (localhost + pve01 + worker01), all `ready`. ### Step 5: Set capacity on remote nodes ```sh orca node capacity set --node pve01 --cpu 4 --memory 8192 --disk 100000 orca node capacity set --node worker01 --cpu 2 --memory 4096 --disk 50000 orca node capacity list ``` **Expected**: capacity shown for both remote nodes. ### Step 6: Create a namespace ```sh orca ns create prod orca ns list ``` **Expected**: `prod` namespace listed. ### Step 7: Deploy the full stack Deploy each service from `examples/full-stack/`: ```sh orca job run examples/full-stack/web-app.md --target pve01 orca job run examples/full-stack/api.md --target pve01 orca job run examples/full-stack/worker.md --target worker01 orca job run examples/full-stack/postgres.md --target pve01 orca job run examples/full-stack/log-shipper.md --target worker01 ``` **Expected**: each job is scheduled on the target, systemd unit deployed via SSH-push, job status `running` or `complete`. ### Step 8: Verify deployment ```sh orca job list orca job list --json ``` **Expected**: all 5 jobs listed, with correct target nodes. On each remote node: ```sh ssh root@pve01 systemctl status 'orca-alloc-*' ssh root@worker01 systemctl status 'orca-alloc-*' ``` ### Step 9: Verify Traefik routes ```sh ssh root@pve01 ls /etc/traefik/dynamic/ ssh root@worker01 ls /etc/traefik/dynamic/ ``` **Expected**: `traefik-dynamic-*.yaml` files present on nodes where jobs were deployed. ### Step 10: Migrate between hosts Migrate `web-app` from pve01 to worker01: ```sh orca job migrate web-app --to worker01 ``` **Expected**: job drained on pve01, rescheduled on worker01, new systemd unit deployed. Verify: ```sh orca job list ssh root@worker01 systemctl status 'orca-alloc-*web-app*' ssh root@pve01 systemctl status 'orca-alloc-*web-app*' # should be stopped ``` ### Step 11: Aggregate logs ```sh orca logs --all-nodes --job web-app --since 5m ``` **Expected**: log entries from multiple nodes. ### Step 12: ACL enforcement ```sh orca acl grant operator-1 --namespace prod --permissions read,write orca acl check operator-1 --namespace prod --permission read orca acl check operator-1 --namespace prod --permission admin ``` **Expected**: read+write allowed, admin denied (not granted). ### Step 12b: Initialize the OIDC provider (for seal) ```sh orca auth init-idp --rp-id orca.local ``` **Expected**: Dex config + systemd unit + Traefik route rendered. (Dex binary must be installed separately.) ### Step 13: Seal/unseal ```sh orca cluster seal orca cluster unseal orca secrets set prod TEST_KEY=test-value orca secrets get prod TEST_KEY ``` **Expected**: seal succeeds, unseal succeeds, secrets readable post-unseal. ### Step 14: Audit chain ```sh orca doctor audit ``` **Expected**: chain head reported, no tamper detected. ### Step 15: Doctor modes ```sh orca doctor modes ``` **Expected**: all file modes correct, exit 0. ### Step 16: OIDC health ```sh orca doctor oidc ``` **Expected**: Dex unit active, issuer reachable (or WARN if Dex not installed). ### Step 17: Backup and restore ```sh orca backup --out /tmp/uat-backup.tar.gz orca restore --in /tmp/uat-backup.tar.gz --dry-run ``` **Expected**: backup succeeds, restore dry-run succeeds. ### Step 18: Drift detection ```sh orca drift show ``` **Expected**: no error (empty drift is fine). ### Step 19: Transaction idempotency ```sh orca txn apply orca txn apply # re-run ``` **Expected**: second apply is idempotent (exit 5 or "already applied"). ### Step 20: Metrics ```sh orca metrics --addr :9100 & sleep 3 curl -s http://localhost:9100/metrics | grep orca_ ``` **Expected**: expanded metric set present (`orca_jobs_running`, `orca_audit_chain_head`, etc.). ### Step 21: Compat check ```sh orca cluster compat-check ``` **Expected**: exit 0, all nodes compatible. ### Step 22: Run the signoff script ```sh scripts/uat-signoff.sh ``` **Expected**: `UAT SIGNOFF: N/35 assertions passed`, exit 0 iff N==35. ## Claim Matrix | # | Claim | UAT Step | Signoff Assertion | |---|-------|----------|-------------------| | 1 | Cluster initializes from scratch | Step 1 | `assert_orca_version` | | 2 | Proxmox host onboards via SSH | Step 2 | `assert_proxmox_onboarded` | | 3 | Ubuntu worker onboards via `--type linux` | Step 3 | `assert_linux_worker_onboarded` | | 4 | Node list shows all nodes | Step 4 | `assert_cluster_initialized` | | 5 | Capacity is set on remote nodes | Step 5 | `assert_capacity_set` | | 6 | Namespace created | Step 6 | `assert_namespace_created` | | 7 | Full stack deploys to remote nodes | Step 7 | `assert_full_stack_running` | | 8 | Scheduler deploys to remote (not local) | Step 7 | `assert_job_deploys_to_remote` | | 9 | Traefik routes present | Step 9 | `assert_traefik_routes` | | 10 | Job migrates between hosts | Step 10 | `assert_migrate_worked` | | 11 | Logs aggregate from multiple nodes | Step 11 | `assert_logs_aggregate` | | 12 | ACL grant/check works | Step 12 | `assert_acl_enforced` | | 13 | ACL deny-by-default | Step 12 | `assert_acl_deny_default` | | 14 | acl.json mode 0600 | Step 12 | `assert_acl_file_mode` | | 15 | Seal/unseal round-trip | Step 13 | `assert_seal_unseal_roundtrip` | | 16 | Audit chain intact | Step 14 | `assert_audit_chain_intact` | | 17 | Doctor modes passes | Step 15 | `assert_doctor_modes` | | 18 | OIDC health check | Step 16 | `assert_oidc_health` | | 19 | Backup works | Step 17 | `assert_backup_restore_dryrun` | | 20 | Drift visible | Step 18 | `assert_drift_visible` | | 21 | Txn idempotent | Step 19 | `assert_txn_idempotent` | | 22 | Metrics expanded | Step 20 | `assert_metrics_expanded` | | 23 | Compat check passes | Step 21 | `assert_compat_check_passes` | | 24 | No `--password` in docs/examples | — | `assert_no_password_in_docs` | | 25 | Go toolchain current | — | `assert_go_toolchain_current` | | 26 | cli.md matches `orca --help` | — | `assert_cli_md_complete` | | 27 | pprof not on all interfaces | — | `assert_no_pprof_on_all_interfaces` | | 28 | WebAuthn registration requires auth | — | `assert_webauthn_reg_requires_auth` | | 29 | Audit chain survives concurrency | — | `assert_audit_chain_concurrent` | | 30 | Concurrent secrets no data loss | — | `assert_concurrent_secrets_no_loss` | | 31 | Cache invalidated after write | — | `assert_cache_invalidated_after_write` | | 32 | SQLite no lock under concurrency | — | `assert_sqlite_no_lock` | | 33 | No injection in logs --job | — | `assert_no_injection_in_logs` | | 34 | `--type linux` exists as subcommand | Step 3 | `assert_type_linux_available` | | 35 | `orca status` deprecated | — | `assert_status_deprecated` | ## Signoff procedure 1. Run all steps above on the 3-host cluster 2. Run `scripts/uat-signoff.sh` on the lead 3. Paste the output back to the CI agent 4. The CI agent verifies `35/35 PASS` and cuts `v1.0.0` ## Troubleshooting ### ORCA_HOME not set All orca commands use `$ORCA_HOME` (default `~/.orca`). If commands fail with "no such file or directory", verify: ```sh echo $ORCA_HOME ls $ORCA_HOME/orca.db $ORCA_HOME/orca_ssh_key $ORCA_HOME/known_hosts $ORCA_HOME/cluster/master.key ``` ### known_hosts missing If SSH operations fail with "open .../known_hosts: no such file", the known_hosts file was not created during `orca init`. Fix: ```sh touch $ORCA_HOME/known_hosts chmod 600 $ORCA_HOME/known_hosts ``` ### Traefik not running If Traefik routes are not deployed, verify Traefik is running: ```sh systemctl status orca-traefik ls /etc/traefik/dynamic/ ``` If not installed, `orca init` should have installed it. Re-run `orca init` or install manually from https://github.com/traefik/traefik/releases. ### SSH connection refused If the orca SSH key is not pre-staged on the remote host: ```sh ssh-copy-id -i ~/.orca/orca_ssh_key.pub root@ ``` ### Job deployed but not visible in `job list` The remote dispatch path now inserts a DB record (v0.12.16). If you still don't see it, check: ```sh orca job list --json ``` Look for the `"node"` field — it shows which node the job deployed to. ### Proxmox: process runtime rejected Proxmox nodes require `one_of: pve-ct` or `one_of: pve-vm` in the jobspec. `one_of: process` (systemd) is for Linux/Ubuntu workers only.