Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Build and Deploy (Dev)

on:
push:
branches:
- dev
workflow_dispatch:

env:
REGISTRY: ghcr.io
ROUTER_IMAGE: ghcr.io/zbigniewsobiecki/cascade-router
WORKER_IMAGE: ghcr.io/zbigniewsobiecki/cascade-worker
DASHBOARD_IMAGE: ghcr.io/zbigniewsobiecki/cascade-dashboard

jobs:
build-and-deploy:
name: Build and Deploy (Dev)
runs-on: self-hosted
steps:
- uses: actions/checkout@v4

- name: Log in to GitHub Container Registry
run: |
echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build and push router image
run: |
docker build -f Dockerfile.router -t ${{ env.ROUTER_IMAGE }}:dev -t ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }} .
docker push ${{ env.ROUTER_IMAGE }}:dev
docker push ${{ env.ROUTER_IMAGE }}:dev-${{ github.sha }}

- name: Build and push worker image
run: |
docker build -f Dockerfile.worker -t ${{ env.WORKER_IMAGE }}:dev -t ${{ env.WORKER_IMAGE }}:dev-${{ github.sha }} .
docker push ${{ env.WORKER_IMAGE }}:dev
docker push ${{ env.WORKER_IMAGE }}:dev-${{ github.sha }}

- name: Build and push dashboard image
run: |
docker build -f Dockerfile.dashboard -t ${{ env.DASHBOARD_IMAGE }}:dev -t ${{ env.DASHBOARD_IMAGE }}:dev-${{ github.sha }} .
docker push ${{ env.DASHBOARD_IMAGE }}:dev
docker push ${{ env.DASHBOARD_IMAGE }}:dev-${{ github.sha }}

- name: Build and deploy frontend to Cloudflare Pages (dev)
run: |
docker build -f Dockerfile.frontend \
--build-arg VITE_API_URL=https://dev.api.ca.sca.de.com \
-t cascade-frontend-dev:build .
# Ensure the Cloudflare Pages project exists (idempotent)
docker run --rm \
-e CLOUDFLARE_API_TOKEN="${{ secrets.CLOUDFLARE_API_TOKEN }}" \
-e CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" \
cascade-frontend-dev:build \
wrangler pages project create cascade-dashboard-dev --production-branch=main || true
docker run --rm \
-e CLOUDFLARE_API_TOKEN="${{ secrets.CLOUDFLARE_API_TOKEN }}" \
-e CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" \
cascade-frontend-dev:build \
wrangler pages deploy dist/web --project-name=cascade-dashboard-dev --branch=main

- name: Pull and restart cascade-router-dev
run: |
cd /opt/services
docker compose pull cascade-router-dev
docker compose up -d --force-recreate cascade-router-dev

- name: Verify cascade-router-dev is healthy
run: |
echo "Waiting for cascade-router-dev to start..."
for i in $(seq 1 30); do
if docker inspect cascade-router-dev --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; then
echo "cascade-router-dev is healthy"
exit 0
fi
if docker inspect cascade-router-dev --format '{{.State.Status}}' 2>/dev/null | grep -q restarting; then
echo "ERROR: cascade-router-dev is crashlooping!"
docker logs cascade-router-dev --tail 20
exit 1
fi
sleep 5
done
echo "ERROR: cascade-router-dev did not become healthy within 150s"
docker logs cascade-router-dev --tail 20
exit 1

- name: Pull and restart cascade-dashboard-dev
run: |
cd /opt/services
docker compose pull cascade-dashboard-dev
docker compose up -d --force-recreate cascade-dashboard-dev

- name: Verify cascade-dashboard-dev is healthy
run: |
echo "Waiting for cascade-dashboard-dev to start..."
for i in $(seq 1 30); do
if docker inspect cascade-dashboard-dev --format '{{.State.Health.Status}}' 2>/dev/null | grep -q healthy; then
echo "cascade-dashboard-dev is healthy"
exit 0
fi
if docker inspect cascade-dashboard-dev --format '{{.State.Status}}' 2>/dev/null | grep -q restarting; then
echo "ERROR: cascade-dashboard-dev is crashlooping!"
docker logs cascade-dashboard-dev --tail 20
exit 1
fi
sleep 5
done
echo "ERROR: cascade-dashboard-dev did not become healthy within 150s"
docker logs cascade-dashboard-dev --tail 20
exit 1
45 changes: 24 additions & 21 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Optional (infrastructure):
- `CLAUDE_CODE_OAUTH_TOKEN` - For Claude Code backend (subscription auth)
- `CREDENTIAL_MASTER_KEY` - 64-char hex string (32-byte AES-256 key) for encrypting credentials at rest. Generate with `npm run credentials:generate-key`. When set, all new/updated credentials are encrypted automatically; existing plaintext credentials continue to work.

**Project credentials** (`GITHUB_TOKEN_IMPLEMENTER`, `GITHUB_TOKEN_REVIEWER`, `TRELLO_API_KEY`, `TRELLO_TOKEN`, LLM API keys) are stored in the `credentials` table (org-scoped, encrypted at rest when `CREDENTIAL_MASTER_KEY` is set) with optional per-project overrides via `project_credential_overrides`. There is no env var fallback — the database is the sole source of truth for project-scoped secrets.
**Project credentials** (`GITHUB_TOKEN_IMPLEMENTER`, `GITHUB_TOKEN_REVIEWER`, `TRELLO_API_KEY`, `TRELLO_TOKEN`, LLM API keys) are stored in the `credentials` table (org-scoped, encrypted at rest when `CREDENTIAL_MASTER_KEY` is set). Integration-specific credentials (GitHub tokens, Trello keys, JIRA tokens) are linked to integrations via the `integration_credentials` join table with provider-defined roles. Non-integration credentials (LLM API keys) remain org-scoped defaults. There is no env var fallback — the database is the sole source of truth for project-scoped secrets.

## Database Configuration

Expand All @@ -89,10 +89,10 @@ CASCADE stores all project configuration in PostgreSQL (Supabase). The `config/p
- `organizations` - Organization definitions (multi-tenant support)
- `cascade_defaults` - Global defaults per org (model, iterations, timeouts, budget)
- `projects` - Per-project config (repo, base branch, budget, backend)
- `project_integrations` - Integration configs per project (Trello boards/lists/labels as JSONB)
- `project_integrations` - Integration configs per project with `category` (pm/scm), `provider` (trello/jira/github), `config` JSONB, and `triggers` JSONB. One PM + one SCM per project (enforced by unique constraint)
- `integration_credentials` - Links integration roles to org-scoped credential rows (e.g., `api_key` → credential #5). Roles are provider-specific: trello has `api_key`/`token`, jira has `email`/`api_token`, github has `implementer_token`/`reviewer_token`
- `agent_configs` - Per-agent-type overrides (model, iterations, backend, prompt), scoped globally, per-org, or per-project
- `credentials` - Org-scoped credentials (API keys, tokens)
- `project_credential_overrides` - Per-project credential overrides (optional, falls back to org defaults)
- `users` - Dashboard users (email, bcrypt password hash, org-scoped)
- `sessions` - Session tokens for cookie-based auth (30-day expiry)

Expand All @@ -117,15 +117,13 @@ Migrations are hand-written SQL files in `src/db/migrations/` tracked by drizzle

For databases initially set up with `drizzle-kit push` (no migration journal), run `npm run db:bootstrap-journal` once to register existing migrations in the `drizzle.__drizzle_migrations` tracking table.

### Per-Project Secrets
### Credentials

Credentials are stored in the `credentials` table (org-scoped) with optional per-project overrides via `project_credential_overrides`.
Org-scoped credentials are stored in the `credentials` table. Integration-specific credentials are linked via the `integration_credentials` join table with provider-defined roles.

```bash
npx tsx tools/manage-secrets.ts create <org-id> <env-var-key> <value> [--name "..."] [--default]
npx tsx tools/manage-secrets.ts list <org-id>
npx tsx tools/manage-secrets.ts set-override <project-id> <env-var-key> <credential-id>
npx tsx tools/manage-secrets.ts remove-override <project-id> <env-var-key>
npx tsx tools/manage-secrets.ts resolve <project-id>
```

Expand Down Expand Up @@ -158,11 +156,13 @@ CASCADE uses two dedicated GitHub bot accounts per project to prevent feedback l
- **Reviewer** (`GITHUB_TOKEN_REVIEWER`) — reviews PRs, can approve or request changes
- Agents: `review`

Both tokens are **required** for each project. Configure via the dashboard (Project Settings > Integrations > GitHub tab) or CLI:
Both tokens are **required** for each project. Create org-scoped credentials, then link them to the project's SCM integration via the dashboard (Project Settings > Integrations > Source Control tab) or CLI:

```bash
cascade credentials create --name "Implementer Bot" --key GITHUB_TOKEN_IMPLEMENTER --value ghp_aaa... --default
cascade credentials create --name "Reviewer Bot" --key GITHUB_TOKEN_REVIEWER --value ghp_bbb... --default
cascade projects integration-credential-set <project-id> --category scm --role implementer_token --credential-id 5
cascade projects integration-credential-set <project-id> --category scm --role reviewer_token --credential-id 7
```

**Bot detection**: Both persona usernames are resolved at first use and cached. Trigger handlers use `isCascadeBot(login)` to check if an event came from either persona, preventing self-triggered loops.
Expand All @@ -172,17 +172,22 @@ cascade credentials create --name "Reviewer Bot" --key GITHUB_TOKEN_REVIEWER --v
- `respond-to-pr-comment` skips @mentions from **any** known persona
- `check-suite-success` checks reviews from the **reviewer** persona specifically

### Per-Agent Credential Overrides
### Integration Credential Resolution

Override any credential for a specific agent type. The dual-persona tokens are the primary use case:
Integration credentials are resolved by `(projectId, category, role)`:

```bash
# Per-project overrides (auto-configured by the GitHub integration tab)
cascade projects override-set <id> --key GITHUB_TOKEN_IMPLEMENTER --credential-id 5
cascade projects override-set <id> --key GITHUB_TOKEN_REVIEWER --credential-id 7
```typescript
// Get a specific integration credential
const trelloKey = await getIntegrationCredential(projectId, 'pm', 'api_key');

// Get all integration credentials + org defaults as flat env-var-key map (for worker environments)
const allCreds = await getAllProjectCredentials(projectId);

// Non-integration org-scoped credentials (LLM API keys)
const openrouterKey = await getOrgCredential(projectId, 'OPENROUTER_API_KEY');
```

Resolution order: agent+project override → project override → org default → null.
Role definitions and env-var-key mappings are in `src/config/integrationRoles.ts`.

## Claude Code Backend

Expand Down Expand Up @@ -329,11 +334,9 @@ cascade projects create --id my-project --name "My Project" --repo owner/repo
cascade projects update <id> --model claude-sonnet-4-5-20250929
cascade projects delete <id> --yes
cascade projects integrations <id>
cascade projects integration-set <id> --type trello --config '{"boardId":"..."}'
cascade projects overrides <id>
cascade projects override-set <id> --key GITHUB_TOKEN_IMPLEMENTER --credential-id 5
cascade projects override-set <id> --key GITHUB_TOKEN_REVIEWER --credential-id 7
cascade projects override-rm <id> --key GITHUB_TOKEN_IMPLEMENTER
cascade projects integration-set <id> --category pm --provider trello --config '{"boardId":"..."}'
cascade projects integration-credential-set <id> --category scm --role implementer_token --credential-id 5
cascade projects integration-credential-rm <id> --category scm --role implementer_token

# Credentials
cascade credentials list
Expand Down Expand Up @@ -378,7 +381,7 @@ src/cli/dashboard/
├── logout.ts
├── whoami.ts
├── runs/ # 6 commands
├── projects/ # 10 commands
├── projects/ # 8 commands
├── credentials/ # 4 commands
├── defaults/ # 2 commands
├── org/ # 2 commands
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.router
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ COPY --from=builder /app/dist/config ./dist/config
COPY --from=builder /app/dist/types ./dist/types
COPY --from=builder /app/dist/db ./dist/db
COPY --from=builder /app/dist/utils ./dist/utils
COPY --from=builder /app/dist/trello ./dist/trello

# Copy config
COPY config ./config
Expand Down
Loading