# Per-Service CI Compilation & Selective Deploy — Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Replace monolithic compile-services with 8 per-service compile jobs and selective deploy, plus rename all service binaries to kebab-case.

**Architecture:** Each service gets its own GitLab CI compile job with dependency-aware `changes:` filters. The deploy job only uploads and restarts services whose binaries were actually built. Service crate names change from snake_case to kebab-case so binary names match k8s deployment names directly.

**Tech Stack:** GitLab CI YAML, Cargo workspace, Kubernetes manifests, shell scripts

**Key insight:** Rust auto-maps hyphens to underscores for crate identifiers, so `use trading_service::...` still compiles after renaming the package to `trading-service`. No Rust source `use` statements need changing.

---

### Task 1: Rename api_gateway crate to api-gateway

**Files:**
- Modify: `services/api_gateway/Cargo.toml` (lines 2, 11)
- Modify: `infra/k8s/services/api-gateway.yaml` (lines 49, 50, 57, 88)

**Step 1: Update Cargo.toml package and bin names**

In `services/api_gateway/Cargo.toml`, change:
```toml
name = "api_gateway"
```
to:
```toml
name = "api-gateway"
```

And the `[[bin]]` section:
```toml
name = "api_gateway"
```
to:
```toml
name = "api-gateway"
```

**Step 2: Update K8s manifest S3 path and command**

In `infra/k8s/services/api-gateway.yaml`, update the initContainer rclone line:
```
":s3:foxhunt-binaries/services/api_gateway"  →  ":s3:foxhunt-binaries/services/api-gateway"
"/binaries/api_gateway"  →  "/binaries/api-gateway"
```
And the container command:
```yaml
command: ["/binaries/api_gateway"]  →  command: ["/binaries/api-gateway"]
```
And the chmod+echo lines in the initContainer script.

**Step 3: Update cross-crate dependency in trading_service**

In `services/trading_service/Cargo.toml`, the dev-dependency:
```toml
api_gateway = { path = "../api_gateway" }
```
becomes:
```toml
api-gateway = { path = "../api_gateway" }
```
(Path stays the same — only the package key changes. Rust still uses `use api_gateway::` because of hyphen-to-underscore mapping.)

**Step 4: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p api-gateway`
Expected: compiles successfully

**Step 5: Commit**

```bash
git add services/api_gateway/Cargo.toml infra/k8s/services/api-gateway.yaml services/trading_service/Cargo.toml
git commit -m "refactor: rename api_gateway crate to api-gateway (kebab-case)"
```

---

### Task 2: Rename trading_service crate to trading-service

**Files:**
- Modify: `services/trading_service/Cargo.toml` (lines 2, 11)
- Modify: `infra/k8s/services/trading-service.yaml` (initContainer + command)
- Modify: `testing/integration/Cargo.toml` (line 22: `trading_service` dep)

**Step 1: Update Cargo.toml package and bin names**

```toml
name = "trading_service"  →  name = "trading-service"
[[bin]] name = "trading_service"  →  [[bin]] name = "trading-service"
```
Note: keep the second `[[bin]] name = "latency_validator"` unchanged (it's a separate binary).

**Step 2: Update K8s manifest**

In `infra/k8s/services/trading-service.yaml`:
```
":s3:foxhunt-binaries/services/trading_service"  →  ":s3:foxhunt-binaries/services/trading-service"
"/binaries/trading_service"  →  "/binaries/trading-service"
command: ["/binaries/trading_service"]  →  command: ["/binaries/trading-service"]
```

**Step 3: Update testing crate dependency**

In `testing/integration/Cargo.toml`:
```toml
trading_service = { path = "../../services/trading_service" }
```
→
```toml
trading-service = { path = "../../services/trading_service" }
```

**Step 4: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p trading-service`

**Step 5: Commit**

```bash
git commit -m "refactor: rename trading_service crate to trading-service (kebab-case)"
```

---

### Task 3: Rename broker_gateway_service crate to broker-gateway

**Files:**
- Modify: `services/broker_gateway_service/Cargo.toml` (lines 2, 11)
- Modify: `infra/k8s/services/broker-gateway.yaml` (initContainer + command)

**Step 1: Update Cargo.toml**

```toml
name = "broker_gateway_service"  →  name = "broker-gateway"
[[bin]] name = "broker_gateway_service"  →  [[bin]] name = "broker-gateway"
```

**Step 2: Update K8s manifest**

```
":s3:foxhunt-binaries/services/broker_gateway_service"  →  ":s3:foxhunt-binaries/services/broker-gateway"
"/binaries/broker_gateway_service"  →  "/binaries/broker-gateway"
command: ["/binaries/broker_gateway_service"]  →  command: ["/binaries/broker-gateway"]
```

No cross-crate dependencies reference this crate.

**Step 3: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p broker-gateway`

**Step 4: Commit**

```bash
git commit -m "refactor: rename broker_gateway_service crate to broker-gateway (kebab-case)"
```

---

### Task 4: Rename ml_training_service crate to ml-training-service

**Files:**
- Modify: `services/ml_training_service/Cargo.toml` (lines 2, 107)
- Modify: `infra/k8s/services/ml-training-service.yaml` (initContainer + command)
- Modify: `testing/e2e/Cargo.toml` (line 68: `ml_training_service` dep)

**Step 1: Update Cargo.toml**

```toml
name = "ml_training_service"  →  name = "ml-training-service"
[[bin]] name = "ml_training_service"  →  [[bin]] name = "ml-training-service"
```

**Step 2: Update K8s manifest**

```
":s3:foxhunt-binaries/services/ml_training_service"  →  ":s3:foxhunt-binaries/services/ml-training-service"
"/binaries/ml_training_service"  →  "/binaries/ml-training-service"
command: ["/binaries/ml_training_service", "serve"]  →  command: ["/binaries/ml-training-service", "serve"]
```

**Step 3: Update testing crate dependency**

In `testing/e2e/Cargo.toml`:
```toml
ml_training_service = { path = "../../services/ml_training_service" }
```
→
```toml
ml-training-service = { path = "../../services/ml_training_service" }
```

**Step 4: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p ml-training-service`

**Step 5: Commit**

```bash
git commit -m "refactor: rename ml_training_service crate to ml-training-service (kebab-case)"
```

---

### Task 5: Rename backtesting_service crate to backtesting-service

**Files:**
- Modify: `services/backtesting_service/Cargo.toml` (lines 2, 11)
- Modify: `infra/k8s/services/backtesting-service.yaml` (initContainer + command)
- Modify: `testing/service-integration/Cargo.toml` (line 52: `backtesting_service` dep)

**Step 1: Update Cargo.toml**

```toml
name = "backtesting_service"  →  name = "backtesting-service"
[[bin]] name = "backtesting_service"  →  [[bin]] name = "backtesting-service"
```

**Step 2: Update K8s manifest**

```
":s3:foxhunt-binaries/services/backtesting_service"  →  ":s3:foxhunt-binaries/services/backtesting-service"
"/binaries/backtesting_service"  →  "/binaries/backtesting-service"
command: ["/binaries/backtesting_service"]  →  command: ["/binaries/backtesting-service"]
```

**Step 3: Update testing crate dependency**

In `testing/service-integration/Cargo.toml`:
```toml
backtesting_service = { path = "../../services/backtesting_service" }
```
→
```toml
backtesting-service = { path = "../../services/backtesting_service" }
```

**Step 4: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p backtesting-service`

**Step 5: Commit**

```bash
git commit -m "refactor: rename backtesting_service crate to backtesting-service (kebab-case)"
```

---

### Task 6: Rename trading_agent_service crate to trading-agent-service

**Files:**
- Modify: `services/trading_agent_service/Cargo.toml` (lines 2, 11)
- Modify: `infra/k8s/services/trading-agent-service.yaml` (initContainer + command)

No cross-crate dependencies reference this crate.

**Step 1: Update Cargo.toml**

```toml
name = "trading_agent_service"  →  name = "trading-agent-service"
[[bin]] name = "trading_agent_service"  →  [[bin]] name = "trading-agent-service"
```

**Step 2: Update K8s manifest**

```
":s3:foxhunt-binaries/services/trading_agent_service"  →  ":s3:foxhunt-binaries/services/trading-agent-service"
"/binaries/trading_agent_service"  →  "/binaries/trading-agent-service"
command: ["/binaries/trading_agent_service"]  →  command: ["/binaries/trading-agent-service"]
```

**Step 3: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p trading-agent-service`

**Step 4: Commit**

```bash
git commit -m "refactor: rename trading_agent_service crate to trading-agent-service (kebab-case)"
```

---

### Task 7: Rename data_acquisition_service crate to data-acquisition-service

**Files:**
- Modify: `services/data_acquisition_service/Cargo.toml` (lines 2, 71)
- Modify: `infra/k8s/services/data-acquisition-service.yaml` (initContainer + command)

No cross-crate dependencies reference this crate.

**Step 1: Update Cargo.toml**

```toml
name = "data_acquisition_service"  →  name = "data-acquisition-service"
[[bin]] name = "data_acquisition_service"  →  [[bin]] name = "data-acquisition-service"
```

**Step 2: Update K8s manifest**

```
":s3:foxhunt-binaries/services/data_acquisition_service"  →  ":s3:foxhunt-binaries/services/data-acquisition-service"
"/binaries/data_acquisition_service"  →  "/binaries/data-acquisition-service"
command: ["/binaries/data_acquisition_service"]  →  command: ["/binaries/data-acquisition-service"]
```

**Step 3: Verify compilation**

Run: `SQLX_OFFLINE=true cargo check -p data-acquisition-service`

**Step 4: Commit**

```bash
git commit -m "refactor: rename data_acquisition_service crate to data-acquisition-service (kebab-case)"
```

---

### Task 8: Update scripts for kebab-case binary names

**Files:**
- Modify: `scripts/start.sh` (lines ~85-118: pkill and cargo run references)
- Modify: `scripts/stop.sh` (lines ~18-43: pid files and pkill references)
- Modify: `scripts/validate_data_quality.sh` (lines 109, 113: `-p backtesting_service`)
- Modify: `scripts/setup-database.sh` (line 112: `--bin trading_service`)
- Modify: `infra/scripts/build-and-push.sh` (lines 26-31: STANDARD_SERVICES array)

**Step 1: Update start.sh**

Replace all `trading_service` → `trading-service`, `backtesting_service` → `backtesting-service`, `ml_training_service` → `ml-training-service` in cargo run/build/pkill commands.

**Step 2: Update stop.sh**

Same pattern: update pid file names and pkill patterns.

**Step 3: Update validate_data_quality.sh**

```bash
cargo build --release -p backtesting_service  →  cargo build --release -p backtesting-service
```

**Step 4: Update setup-database.sh**

```bash
cargo run --bin trading_service  →  cargo run --bin trading-service
```

**Step 5: Update build-and-push.sh**

```bash
STANDARD_SERVICES=(
  api-gateway
  trading-service
  trading-agent-service
  ml-training-service
  backtesting-service
  broker-gateway
)
```

**Step 6: Commit**

```bash
git commit -m "refactor: update scripts for kebab-case service binary names"
```

---

### Task 9: Replace compile-services with per-service compile jobs in .gitlab-ci.yml

**Files:**
- Modify: `.gitlab-ci.yml` (replace compile-services job ~lines 411-459 with base template + 8 per-service jobs)

**Step 1: Add .compile-service-base YAML anchor**

Insert after the `.rust-base-cpu` template (after line 360):

```yaml
# Base template for per-service compile jobs
.compile-service-base:
  extends: .rust-base-cpu
  stage: compile
  needs: []
  interruptible: true
  variables:
    CARGO_BUILD_JOBS: "4"
  script:
    - mkdir -p "$SCCACHE_DIR" && sccache --zero-stats || true
    - PROFILE=${DEV_RELEASE:+dev-release}; PROFILE=${PROFILE:-release}
    - |
      YEAR=$(date -d "$CI_COMMIT_TIMESTAMP" +%Y 2>/dev/null || date +%Y)
      MONTH=$(date -d "$CI_COMMIT_TIMESTAMP" +%m 2>/dev/null || date +%m)
      export FOXHUNT_BUILD_VERSION="${YEAR}.${MONTH}.${CI_PIPELINE_IID:-0}"
    - echo "Building ${SERVICE_PACKAGE} with --profile $PROFILE"
    - TARGET_DIR="target/$PROFILE"
    - cargo build --profile $PROFILE -p ${SERVICE_PACKAGE}
    - sccache --show-stats || true
    - mkdir -p build-out/services
    - cp $TARGET_DIR/${SERVICE_PACKAGE} build-out/services/
    - strip build-out/services/${SERVICE_PACKAGE}
    - ls -lh build-out/services/
  artifacts:
    paths:
      - build-out/services/
    expire_in: 1 day
```

**Step 2: Delete the old compile-services job (lines 411-459)**

**Step 3: Add 8 per-service compile jobs**

```yaml
compile-api-gateway:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: api-gateway
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/api_gateway/**
        - crates/common/**
        - crates/config/**
        - crates/trading_engine/**
        - bin/fxt/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-trading-service:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: trading-service
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/trading_service/**
        - services/api_gateway/**
        - crates/common/**
        - crates/config/**
        - crates/data/**
        - crates/database/**
        - crates/ml/**
        - crates/risk/**
        - crates/storage/**
        - crates/trading_engine/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-ml-training-service:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: ml-training-service
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/ml_training_service/**
        - crates/common/**
        - crates/config/**
        - crates/data/**
        - crates/ml/**
        - crates/risk/**
        - crates/storage/**
        - crates/trading_engine/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-backtesting-service:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: backtesting-service
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/backtesting_service/**
        - crates/common/**
        - crates/config/**
        - crates/data/**
        - crates/ml/**
        - crates/model_loader/**
        - crates/risk/**
        - crates/storage/**
        - crates/trading_engine/**
        - bin/fxt/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-trading-agent-service:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: trading-agent-service
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/trading_agent_service/**
        - crates/common/**
        - crates/config/**
        - crates/ml/**
        - crates/risk/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-broker-gateway:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: broker-gateway
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/broker_gateway_service/**
        - crates/common/**
        - crates/config/**
        - crates/trading_engine/**
        - vendor/ctrader-openapi/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-data-acquisition-service:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: data-acquisition-service
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - services/data_acquisition_service/**
        - crates/common/**
        - crates/config/**
        - crates/storage/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"

compile-web-gateway:
  extends: .compile-service-base
  variables:
    SERVICE_PACKAGE: web-gateway
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
      changes:
        - crates/web-gateway/**
        - crates/common/**
        - Cargo.toml
        - Cargo.lock
    - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api"
```

**Step 4: Verify YAML syntax**

Run: `python3 -c "import yaml; yaml.safe_load(open('.gitlab-ci.yml'))"`

**Step 5: Commit**

```bash
git commit -m "ci: replace compile-services with 8 per-service compile jobs"
```

---

### Task 10: Update deploy job for selective restart

**Files:**
- Modify: `.gitlab-ci.yml` (deploy job ~lines 1462-1639)

**Step 1: Update `needs:` to reference per-service jobs**

Replace the single `compile-services` need with 8 optional per-service needs:

```yaml
needs:
  - job: compile-api-gateway
    optional: true
    artifacts: true
  - job: compile-trading-service
    optional: true
    artifacts: true
  - job: compile-ml-training-service
    optional: true
    artifacts: true
  - job: compile-backtesting-service
    optional: true
    artifacts: true
  - job: compile-trading-agent-service
    optional: true
    artifacts: true
  - job: compile-broker-gateway
    optional: true
    artifacts: true
  - job: compile-data-acquisition-service
    optional: true
    artifacts: true
  - job: compile-web-gateway
    optional: true
    artifacts: true
  - job: compile-training
    optional: true
    artifacts: true
  - job: build-web-dashboard
    optional: true
    artifacts: true
```

**Step 2: Replace hardcoded rollout restart loop with selective restart**

Replace the `for svc in trading-service api-gateway ...` loops with:

```bash
# Restart only services whose binary was uploaded
DEPLOYED=""
for bin in build-out/services/*; do
  [ -f "$bin" ] || continue
  name=$(basename "$bin")
  DEPLOYED="$DEPLOYED $name"
done

if [ -n "$DEPLOYED" ]; then
  for svc in $DEPLOYED; do
    kubectl -n foxhunt rollout restart deployment/$svc
    echo "Restarted $svc"
  done
  for svc in $DEPLOYED; do
    kubectl -n foxhunt rollout status deployment/$svc --timeout=300s
  done
  echo "Deployed:$DEPLOYED"
else
  echo "No service binaries changed — manifests-only deploy"
fi
```

**Step 3: Verify YAML syntax**

Run: `python3 -c "import yaml; yaml.safe_load(open('.gitlab-ci.yml'))"`

**Step 4: Commit**

```bash
git commit -m "ci: make deploy job selective — only restart services with new binaries"
```

---

### Task 11: Full workspace compilation check

**Step 1: Run full workspace check**

Run: `SQLX_OFFLINE=true cargo check --workspace 2>&1 | tail -20`
Expected: all 37+ crates compile successfully

**Step 2: Run full workspace tests (lib only, fast)**

Run: `SQLX_OFFLINE=true cargo test --workspace --lib 2>&1 | tail -5`
Expected: all tests pass

**Step 3: Verify no warnings from the renames**

Run: `SQLX_OFFLINE=true cargo check --workspace 2>&1 | grep -i "warning.*rename\|error" | head -10`
Expected: no rename-related warnings or errors

**Step 4: Commit any fixups if needed**

---

### Task 12: Update GPU overlay manifests if they reference old binary names

**Files:**
- Check: `infra/k8s/gpu-overlays/trading-service-gpu.yaml`
- Check: `infra/k8s/gpu-overlays/ml-training-service-gpu.yaml`

**Step 1: Check for old binary name references**

Search for `trading_service`, `ml_training_service` in GPU overlay YAMLs.
These are strategic merge patches — they may reference container names or commands.

**Step 2: Update any found references to kebab-case**

**Step 3: Commit if changes were needed**

```bash
git commit -m "infra: update GPU overlay manifests for kebab-case binary names"
```
