# Agencio Predict - Infrastructure & Deployment

## Overview

Agencio Predict runs as a fully containerised application via Docker, designed
for local development and AWS production deployment. Authentication supports
both AWS Cognito and the Agencio Authentication Service. Secrets are managed
via AWS Secrets Manager. Object storage uses MinIO (local) or S3 (production).

---

## Container Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                        Docker Compose / ECS                         │
│                                                                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐    │
│  │  predict-web    │  │  predict-intel   │  │  predict-trust  │    │
│  │  (Next.js)      │  │  (Python/Node)   │  │  (Python)       │    │
│  │                 │  │                  │  │                 │    │
│  │  - Frontend     │  │  - Signal ingest │  │  - Trust scoring│    │
│  │  - API routes   │  │  - Aggregation   │  │  - Manipulation │    │
│  │  - BFF layer    │  │  - Explanations  │  │  - Audit trail  │    │
│  │                 │  │  - Feed pipeline │  │                 │    │
│  │  Port: 3000     │  │  Port: 8001      │  │  Port: 8002     │    │
│  └────────┬────────┘  └────────┬─────────┘  └────────┬────────┘    │
│           │                    │                      │             │
│  ┌────────┴────────┐  ┌───────┴──────────┐  ┌───────┴──────────┐  │
│  │ predict-mktg    │  │ predict-triggers │  │ predict-worker   │  │
│  │ (Python/Node)   │  │ (Node)           │  │ (Python/Node)    │  │
│  │                 │  │                  │  │                  │  │
│  │ - Campaign pred │  │ - Rule eval      │  │ - Cron scheduler │  │
│  │ - Variant score │  │ - Action exec    │  │ - Feed polling   │  │
│  │ - Persona link  │  │ - Webhook send   │  │ - Cleanup jobs   │  │
│  │                 │  │                  │  │ - Calibration    │  │
│  │ Port: 8003      │  │ Port: 8004       │  │ (no public port) │  │
│  └─────────────────┘  └──────────────────┘  └──────────────────┘  │
│                                                                     │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐    │
│  │  PostgreSQL     │  │  Redis          │  │  MinIO / S3     │    │
│  │  (Supabase or   │  │                 │  │                 │    │
│  │   RDS)          │  │  - Signal cache │  │  - Uploads      │    │
│  │                 │  │  - Rate limits  │  │  - Reports      │    │
│  │  - All tables   │  │  - Job queue    │  │  - Exports      │    │
│  │  - Realtime     │  │  - Session      │  │  - Backups      │    │
│  │                 │  │                 │  │                 │    │
│  │  Port: 5432     │  │  Port: 6379     │  │  Port: 9000     │    │
│  └─────────────────┘  └─────────────────┘  └─────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
```

---

## Docker Compose (Development)

```yaml
# docker-compose.yml
version: "3.9"

x-common: &common
  restart: unless-stopped
  networks:
    - predict-network
  env_file:
    - .env

services:
  # ─── Frontend + API Gateway ───────────────────────────
  predict-web:
    build:
      context: .
      dockerfile: docker/Dockerfile.web
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
      - MINIO_ENDPOINT=minio:9000
      - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
      - MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
      - JWT_SECRET=${JWT_SECRET}                 # shared with bertha-auth-service
      - AUTH_SERVICE_URL=${AUTH_SERVICE_URL:-http://auth-service:3001}
      - INTEL_SERVICE_URL=http://predict-intel:8001
      - TRUST_SERVICE_URL=http://predict-trust:8002
      - MKTG_SERVICE_URL=http://predict-mktg:8003
      - TRIGGERS_SERVICE_URL=http://predict-triggers:8004
    volumes:
      - ./src:/app/src
      - ./public:/app/public
    depends_on:
      - postgres
      - redis
      - minio
    <<: *common

  # ─── Intelligence Service ─────────────────────────────
  predict-intel:
    build:
      context: .
      dockerfile: docker/Dockerfile.intel
    ports:
      - "8001:8001"
    environment:
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
      - CLAUDE_API_KEY=${CLAUDE_API_KEY}
      - POLYMARKET_API_KEY=${POLYMARKET_API_KEY}
      - NEWSAPI_KEY=${NEWSAPI_KEY}
      - X_API_BEARER_TOKEN=${X_API_BEARER_TOKEN}
      - REDDIT_CLIENT_ID=${REDDIT_CLIENT_ID}
      - REDDIT_CLIENT_SECRET=${REDDIT_CLIENT_SECRET}
    depends_on:
      - postgres
      - redis
    <<: *common

  # ─── Trust Service ────────────────────────────────────
  predict-trust:
    build:
      context: .
      dockerfile: docker/Dockerfile.trust
    ports:
      - "8002:8002"
    environment:
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
    depends_on:
      - postgres
      - redis
    <<: *common

  # ─── Marketing Service ────────────────────────────────
  predict-mktg:
    build:
      context: .
      dockerfile: docker/Dockerfile.mktg
    ports:
      - "8003:8003"
    environment:
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
      - CLAUDE_API_KEY=${CLAUDE_API_KEY}
      - AGENCIO_PERSONA_API_URL=${AGENCIO_PERSONA_API_URL}
    depends_on:
      - postgres
      - redis
    <<: *common

  # ─── Triggers & Actions Service ────────────────────────
  predict-triggers:
    build:
      context: .
      dockerfile: docker/Dockerfile.triggers
    ports:
      - "8004:8004"
    environment:
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
      - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}
      - SENDGRID_API_KEY=${SENDGRID_API_KEY}
    depends_on:
      - postgres
      - redis
    <<: *common

  # ─── Background Worker ────────────────────────────────
  predict-worker:
    build:
      context: .
      dockerfile: docker/Dockerfile.worker
    environment:
      - DATABASE_URL=postgresql://predict:predict@postgres:5432/predict
      - REDIS_URL=redis://redis:6379
      - MINIO_ENDPOINT=minio:9000
      - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
      - MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
      - INTEL_SERVICE_URL=http://predict-intel:8001
      - TRUST_SERVICE_URL=http://predict-trust:8002
    depends_on:
      - postgres
      - redis
      - minio
      - predict-intel
      - predict-trust
    <<: *common

  # ─── Data Stores ──────────────────────────────────────
  postgres:
    image: supabase/postgres:15.6.1
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=predict
      - POSTGRES_PASSWORD=predict
      - POSTGRES_DB=predict
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./db/migrations:/docker-entrypoint-initdb.d
    <<: *common

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    <<: *common

  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"
      - "9001:9001"  # MinIO Console
    environment:
      - MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minioadmin}
      - MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minioadmin}
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"
    <<: *common

  # ─── MinIO Bucket Init ────────────────────────────────
  minio-init:
    image: minio/mc:latest
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      sleep 5;
      mc alias set local http://minio:9000 $${MINIO_ACCESS_KEY:-minioadmin} $${MINIO_SECRET_KEY:-minioadmin};
      mc mb local/predict-uploads --ignore-existing;
      mc mb local/predict-reports --ignore-existing;
      mc mb local/predict-exports --ignore-existing;
      mc mb local/predict-backups --ignore-existing;
      exit 0;
      "
    <<: *common

volumes:
  postgres_data:
  redis_data:
  minio_data:

networks:
  predict-network:
    driver: bridge
```

---

## Authentication

### Agencio Authentication Service Integration

Predict delegates ALL user auth to the existing **bertha-auth-service** (v2.0.0).
No custom auth logic is built into Predict.

**Key environment variables:**
```
JWT_SECRET=<shared with bertha-auth-service>
AUTH_SERVICE_URL=http://auth-service:3001     # internal Docker/VPC URL
```

**How it works:**
1. Frontend sends users to auth service for login/signup
2. Auth service issues HS256 JWT containing user ID, email, org, role, permissions
3. Predict services validate JWT locally using shared `JWT_SECRET`
4. No DB lookup needed — user context extracted from token claims

**Service-to-service auth:** Uses auth service's token manager
(`POST /api/v1/token-manager/service-token/:serviceName`) for short-lived
service tokens. See [11-security-overview.md](./11-security-overview.md) for full details.

**What auth service already provides (we do NOT rebuild):**
- Signup/signin, password reset, MFA (TOTP), SSO
- Organisation management, memberships, invitations
- Session tracking, privacy/GDPR deletion
- Admin user management
- Service token lifecycle

### Role & Permission Mapping

The auth service JWT includes `role` (org-level) and `permissions` (array).
Predict maps these to feature access:

| Auth Service Role | Predict Tier | Feature Access |
|-------------------|-------------|----------------|
| `member` (default) | Free | Delayed data, 3 rules, 5 campaigns/mo |
| `member` + pro subscription | Pro | Realtime, unlimited rules, full explanations |
| `admin` (org) | Team | Pro + team rules, feeds, member management |
| `owner` (org) | Enterprise | Full access + billing |
| `globalRole: admin` | System Admin | All features + system rules + event resolution |

Subscription tier is stored in `prediction_subscriptions` and checked
alongside the auth service role. See [11-security-overview.md](./11-security-overview.md)
for RBAC enforcement details.

---

## AWS Deployment Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                          AWS Account                                │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                        VPC                                   │   │
│  │                                                              │   │
│  │  ┌─── Public Subnets ────────────────────────────────────┐   │   │
│  │  │                                                       │   │   │
│  │  │  ┌─────────────┐    ┌─────────────┐                   │   │   │
│  │  │  │ ALB         │    │ CloudFront  │                   │   │   │
│  │  │  │ (API + Web) │    │ (Static)    │                   │   │   │
│  │  │  └──────┬──────┘    └─────────────┘                   │   │   │
│  │  └─────────┼─────────────────────────────────────────────┘   │   │
│  │            │                                                 │   │
│  │  ┌─── Private Subnets ───────────────────────────────────┐   │   │
│  │  │         │                                             │   │   │
│  │  │  ┌──────▼──────────────────────────────────────────┐  │   │   │
│  │  │  │              ECS Fargate Cluster                │  │   │   │
│  │  │  │                                                 │  │   │   │
│  │  │  │  ┌──────────┐ ┌──────────┐ ┌──────────┐       │  │   │   │
│  │  │  │  │ web (x2) │ │intel (x2)│ │trust (x1)│       │  │   │   │
│  │  │  │  └──────────┘ └──────────┘ └──────────┘       │  │   │   │
│  │  │  │  ┌──────────┐ ┌──────────┐ ┌──────────┐       │  │   │   │
│  │  │  │  │mktg (x1) │ │triggers  │ │worker    │       │  │   │   │
│  │  │  │  │          │ │   (x1)   │ │   (x1)   │       │  │   │   │
│  │  │  │  └──────────┘ └──────────┘ └──────────┘       │  │   │   │
│  │  │  └─────────────────────────────────────────────────┘  │   │   │
│  │  │                                                       │   │   │
│  │  │  ┌──────────┐ ┌──────────────┐ ┌──────────────────┐  │   │   │
│  │  │  │ RDS      │ │ ElastiCache  │ │ S3               │  │   │   │
│  │  │  │ Postgres │ │ Redis        │ │ (predict-*)      │  │   │   │
│  │  │  │ (Multi-  │ │ (Cluster)    │ │                  │  │   │   │
│  │  │  │  AZ)     │ │              │ │ - uploads        │  │   │   │
│  │  │  │          │ │              │ │ - reports        │  │   │   │
│  │  │  │          │ │              │ │ - exports        │  │   │   │
│  │  │  └──────────┘ └──────────────┘ └──────────────────┘  │   │   │
│  │  └───────────────────────────────────────────────────────┘   │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  ┌── Managed Services ──────────────────────────────────────────┐   │
│  │                                                              │   │
│  │  Cognito          Secrets Manager       IAM                  │   │
│  │  (User Pool)      (All secrets)         (Service roles)      │   │
│  │                                                              │   │
│  │  CloudWatch       ECR                   Route 53             │   │
│  │  (Logs/Metrics)   (Container images)    (DNS)                │   │
│  │                                                              │   │
│  │  SES              EventBridge           WAF                  │   │
│  │  (Email)          (Scheduled tasks)     (API protection)     │   │
│  └──────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘
```

---

## AWS Secrets Manager

All secrets are stored in AWS Secrets Manager. Services fetch them at startup.
No secrets in env files, Docker images, or source code in production.

### Secret Structure

```
/predict/prod/database
  {
    "host": "predict-db.xxxxx.us-east-1.rds.amazonaws.com",
    "port": 5432,
    "username": "predict_app",
    "password": "...",
    "database": "predict"
  }

/predict/prod/redis
  {
    "host": "predict-redis.xxxxx.cache.amazonaws.com",
    "port": 6379,
    "auth_token": "..."
  }

/predict/prod/auth
  {
    "jwt_secret": "...",
    "auth_service_url": "http://auth-service.internal:3001",
    "credentials_encryption_key": "..."
  }

/predict/prod/api-keys
  {
    "claude_api_key": "...",
    "polymarket_api_key": "...",
    "newsapi_key": "...",
    "x_api_bearer_token": "...",
    "reddit_client_id": "...",
    "reddit_client_secret": "...",
    "sendgrid_api_key": "..."
  }

/predict/prod/storage
  {
    "s3_bucket": "predict-data-prod",
    "s3_region": "us-east-1"
  }
```

### Secret Loading

```typescript
// config/secrets.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

class SecretLoader {
  private client: SecretsManagerClient;
  private cache: Map<string, { value: any; expiresAt: number }> = new Map();
  private cacheTTL = 5 * 60 * 1000; // 5 minutes

  constructor() {
    this.client = new SecretsManagerClient({ region: process.env.AWS_REGION || 'us-east-1' });
  }

  async getSecret(secretName: string): Promise<any> {
    // Check cache
    const cached = this.cache.get(secretName);
    if (cached && cached.expiresAt > Date.now()) return cached.value;

    // Fetch from Secrets Manager
    const command = new GetSecretValueCommand({ SecretId: secretName });
    const response = await this.client.send(command);
    const value = JSON.parse(response.SecretString!);

    // Cache
    this.cache.set(secretName, { value, expiresAt: Date.now() + this.cacheTTL });
    return value;
  }

  // Convenience methods
  async getDatabaseUrl(): Promise<string> {
    if (process.env.NODE_ENV === 'development') return process.env.DATABASE_URL!;
    const db = await this.getSecret('/predict/prod/database');
    return `postgresql://${db.username}:${db.password}@${db.host}:${db.port}/${db.database}`;
  }

  async getRedisUrl(): Promise<string> {
    if (process.env.NODE_ENV === 'development') return process.env.REDIS_URL!;
    const redis = await this.getSecret('/predict/prod/redis');
    return `redis://:${redis.auth_token}@${redis.host}:${redis.port}`;
  }

  async getApiKeys(): Promise<Record<string, string>> {
    if (process.env.NODE_ENV === 'development') {
      return {
        claude_api_key: process.env.CLAUDE_API_KEY!,
        polymarket_api_key: process.env.POLYMARKET_API_KEY!,
        newsapi_key: process.env.NEWSAPI_KEY!,
        // ...
      };
    }
    return this.getSecret('/predict/prod/api-keys');
  }
}

export const secrets = new SecretLoader();
```

---

## IAM Roles & Policies

### ECS Task Execution Role

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/ecs/predict-*"
    }
  ]
}
```

### ECS Task Role (Application)

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SecretsManagerAccess",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "arn:aws:secretsmanager:*:*:secret:/predict/*"
    },
    {
      "Sid": "S3Access",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::predict-data-*",
        "arn:aws:s3:::predict-data-*/*"
      ]
    },
    {
      "Sid": "SESAccess",
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ses:FromAddress": "notifications@predict.agencio.com"
        }
      }
    },
    {
      "Sid": "CognitoAccess",
      "Effect": "Allow",
      "Action": [
        "cognito-idp:AdminGetUser",
        "cognito-idp:ListUsers"
      ],
      "Resource": "arn:aws:cognito-idp:*:*:userpool/*"
    }
  ]
}
```

---

## Object Storage (MinIO / S3)

### Abstraction Layer

```typescript
// storage/storage.ts

interface StorageProvider {
  upload(bucket: string, key: string, data: Buffer | ReadableStream, contentType: string): Promise<string>;
  download(bucket: string, key: string): Promise<Buffer>;
  getSignedUrl(bucket: string, key: string, expiresIn: number): Promise<string>;
  delete(bucket: string, key: string): Promise<void>;
  list(bucket: string, prefix: string): Promise<StorageObject[]>;
}

function createStorageProvider(): StorageProvider {
  if (process.env.NODE_ENV === 'development' || process.env.STORAGE_PROVIDER === 'minio') {
    return new MinIOStorageProvider({
      endpoint: process.env.MINIO_ENDPOINT!,
      accessKey: process.env.MINIO_ACCESS_KEY!,
      secretKey: process.env.MINIO_SECRET_KEY!,
      useSSL: false,
    });
  }

  return new S3StorageProvider({
    region: process.env.AWS_REGION || 'us-east-1',
    // IAM role handles credentials in production
  });
}
```

### Bucket Structure

```
predict-uploads/           → User-uploaded files (CSV, images, creatives)
  ├── feeds/{feedId}/      → Feed upload files
  ├── campaigns/{id}/      → Campaign creative assets
  └── avatars/{userId}/    → User avatars

predict-reports/           → Generated reports
  ├── calibration/         → Calibration reports
  ├── trust/               → Trust audit reports
  ├── explanations/        → Detailed explanation PDFs
  └── campaigns/           → Campaign prediction reports

predict-exports/           → Data exports
  ├── signals/             → Signal data exports
  ├── events/              → Event data exports
  └── api/                 → API usage reports

predict-backups/           → Database backups
  ├── daily/
  ├── weekly/
  └── monthly/
```

### S3 Bucket Policies (Production)

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceEncryption",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::predict-data-prod/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "DenyPublicAccess",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": "arn:aws:s3:::predict-data-prod/*",
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}
```

---

## Dockerfile Templates

### Web Service

```dockerfile
# docker/Dockerfile.web
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 predict && \
    adduser --system --uid 1001 predict

COPY --from=builder --chown=predict:predict /app/.next ./.next
COPY --from=builder --chown=predict:predict /app/public ./public
COPY --from=builder --chown=predict:predict /app/package*.json ./
COPY --from=builder --chown=predict:predict /app/node_modules ./node_modules

USER predict
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/api/health || exit 1

CMD ["npm", "start"]
```

### Python Service (Intel / Trust)

```dockerfile
# docker/Dockerfile.intel
FROM python:3.12-slim AS builder

WORKDIR /app
COPY services/intel/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

FROM python:3.12-slim AS runner
WORKDIR /app

RUN groupadd -r predict && useradd -r -g predict predict

COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY services/intel/ .

USER predict
EXPOSE 8001

HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')" || exit 1

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
```

---

## Environment Configuration

### .env.example

```bash
# ─── Auth (Agencio Authentication Service) ───
JWT_SECRET=                              # shared with bertha-auth-service
AUTH_SERVICE_URL=http://localhost:3001    # auth service URL
CREDENTIALS_ENCRYPTION_KEY=              # 32-byte hex key for encrypting stored credentials

# ─── Database ─────────────────────────────
DATABASE_URL=postgresql://predict:predict@localhost:5432/predict

# ─── Redis ────────────────────────────────
REDIS_URL=redis://localhost:6379

# ─── Storage ──────────────────────────────
STORAGE_PROVIDER=minio                   # 'minio' | 's3'
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin

# S3 (production)
# AWS_REGION=us-east-1
# S3_BUCKET=predict-data-prod

# ─── API Keys (dev only, use Secrets Manager in prod) ───
CLAUDE_API_KEY=
POLYMARKET_API_KEY=
NEWSAPI_KEY=
X_API_BEARER_TOKEN=
REDDIT_CLIENT_ID=
REDDIT_CLIENT_SECRET=
SENDGRID_API_KEY=

# ─── Services ─────────────────────────────
INTEL_SERVICE_URL=http://localhost:8001
TRUST_SERVICE_URL=http://localhost:8002
MKTG_SERVICE_URL=http://localhost:8003
TRIGGERS_SERVICE_URL=http://localhost:8004

# ─── AWS (production) ────────────────────
# AWS_REGION=us-east-1
# AWS_ACCOUNT_ID=
```

---

## CI/CD Pipeline

```yaml
# .github/workflows/deploy.yml (simplified)

name: Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        service: [web, intel, trust, mktg, triggers, worker]
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
      - uses: aws-actions/amazon-ecr-login@v2
      - run: |
          docker build -f docker/Dockerfile.${{ matrix.service }} -t predict-${{ matrix.service }} .
          docker tag predict-${{ matrix.service }} $ECR_REGISTRY/predict-${{ matrix.service }}:${{ github.sha }}
          docker push $ECR_REGISTRY/predict-${{ matrix.service }}:${{ github.sha }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - run: |
          # Update ECS services with new image tags
          aws ecs update-service --cluster predict-prod \
            --service predict-web --force-new-deployment
          # ... repeat for each service
```

---

## Health Checks

Every service exposes a `/health` endpoint:

```json
{
  "status": "healthy",
  "service": "predict-intel",
  "version": "1.2.3",
  "uptime_seconds": 86400,
  "checks": {
    "database": "ok",
    "redis": "ok",
    "storage": "ok",
    "auth": "ok"
  }
}
```

---

## Build Phase

Infrastructure tasks fit across phases:

### Phase 0 (Foundation):
- [ ] Docker Compose setup (postgres, redis, minio, web)
- [ ] Dockerfile.web
- [ ] Auth middleware with Cognito provider
- [ ] Auth middleware with Agencio provider
- [ ] Secret loader (dev: env vars, prod: Secrets Manager)
- [ ] Storage abstraction (MinIO for dev)
- [ ] Health check endpoints
- [ ] `.env.example`

### Phase 1:
- [ ] Dockerfile.intel + Dockerfile.worker
- [ ] Inter-service HTTP client

### Phase 3:
- [ ] Dockerfile.trust

### Phase 3.5:
- [ ] Dockerfile.triggers

### Phase 4:
- [ ] Dockerfile.mktg

### Phase 7 (Production):
- [ ] AWS CDK / Terraform for ECS, RDS, ElastiCache, S3
- [ ] IAM roles and policies
- [ ] Secrets Manager secrets
- [ ] ALB + target groups
- [ ] CloudFront distribution
- [ ] Route 53 DNS
- [ ] CI/CD pipeline (GitHub Actions → ECR → ECS)
- [ ] CloudWatch dashboards + alarms
- [ ] WAF rules
- [ ] Cognito User Pool setup
- [ ] S3 bucket policies + encryption
