# Causet local core — self-contained release compose
#
#   docker compose up -d
#
# No .env required. Local-dev defaults only (insecure).
# Override with a sibling .env if needed.
# Image tag default: latest
#
name: causet-local-core

services:
  redpanda:
    image: docker.redpanda.com/redpandadata/redpanda:v23.3.3
    container_name: causet-local-redpanda
    command: redpanda start --smp 2 --memory 1G --overprovisioned --advertise-kafka-addr redpanda:9092
    ports:
      - "9092:9092"
    volumes:
      - redpanda-data:/var/lib/redpanda/data
    healthcheck:
      test: ["CMD-SHELL", "rpk cluster health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 15s
    restart: unless-stopped
    networks:
      - causet-local

  postgres:
    image: postgis/postgis:16-3.5-alpine
    platform: ${DOCKER_DEFAULT_PLATFORM:-linux/amd64}
    container_name: causet-local-postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-verdant}
      POSTGRES_USER: ${POSTGRES_USER:-verdant}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-verdant}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    configs:
      - source: postgres_init
        target: /docker-entrypoint-initdb.d/01-init.sql
      - source: projections_schema
        target: /docker-entrypoint-initdb.d/02-projections-schema.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U verdant -d verdant"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    networks:
      - causet-local

  redis:
    image: redis:7-alpine
    container_name: causet-local-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    networks:
      - causet-local

  minio:
    image: minio/minio
    container_name: causet-local-minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9000/minio/health/ready || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped
    networks:
      - causet-local

  minio-init:
    image: minio/mc
    container_name: causet-local-minio-init
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: ["/bin/sh", "/scripts/create-buckets.sh"]
    configs:
      - source: create_buckets
        target: /scripts/create-buckets.sh
        mode: 0755
    restart: on-failure
    networks:
      - causet-local

  projection-schema-init:
    image: postgres:16-alpine
    container_name: causet-local-projection-schema-init
    environment:
      PGPASSWORD: ${POSTGRES_PASSWORD:-verdant}
    entrypoint: ["/bin/sh", "/scripts/init-projections-schema.sh"]
    configs:
      - source: init_projections_schema
        target: /scripts/init-projections-schema.sh
        mode: 0755
      - source: projection_cursors
        target: /schema/projection_cursors.sql
      - source: projection_dead_letter
        target: /schema/projection_dead_letter.sql
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"
    networks:
      - causet-local

  verdant-migrations:
    image: ${CAUSET_REGISTRY:-839868753911.dkr.ecr.us-east-1.amazonaws.com}/verdant-migrations:${CAUSET_IMAGE_TAG:-latest}
    container_name: causet-local-verdant-migrations
    environment:
      FLYWAY_URL: ${FLYWAY_URL_RUNTIME:-jdbc:postgresql://postgres:5432/verdant}
      POSTGRES_USER: ${POSTGRES_USER:-verdant}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-verdant}
    depends_on:
      postgres:
        condition: service_healthy
    restart: "no"
    networks:
      - causet-local

  verdant-runtime-service:
    image: ${CAUSET_REGISTRY:-839868753911.dkr.ecr.us-east-1.amazonaws.com}/runtime:${CAUSET_IMAGE_TAG:-latest}
    container_name: causet-local-runtime
    ports:
      - "${RUNTIME_HTTP_PORT:-8080}:8080"
      - "${RUNTIME_GRPC_PORT:-9090}:9090"
    environment:
      SERVER_PORT: 8080
      GRPC_SERVER_PORT: 9090
      ENVIRONMENT: ${ENVIRONMENT:-local}

      R2DBC_URL: ${RUNTIME_R2DBC_URL:-r2dbc:postgresql://postgres:5432/verdant}
      POSTGRES_USER: ${POSTGRES_USER:-verdant}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-verdant}

      SPRING_REDIS_HOST: ${REDIS_HOST:-redis}
      SPRING_REDIS_PORT: ${REDIS_PORT:-6379}
      SPRING_DATA_REDIS_HOST: ${REDIS_HOST:-redis}
      SPRING_DATA_REDIS_PORT: ${REDIS_PORT:-6379}
      REDIS_HOST: ${REDIS_HOST:-redis}
      REDIS_PORT: ${REDIS_PORT:-6379}

      KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-redpanda:9092}
      KAFKA_CONSUMER_GROUP_ID: ${KAFKA_CONSUMER_GROUP_ID:-verdant-runtime-service}
      KAFKA_INTENTS_TOPIC: ${KAFKA_INTENTS_TOPIC:-causet.intents.v1}
      KAFKA_PATCHES_TOPIC: ${KAFKA_PATCHES_TOPIC:-causet.patches.v1}
      KAFKA_LEDGER_EVENTS_TOPIC: ${KAFKA_LEDGER_EVENTS_TOPIC:-causet.ledger-events.v1}
      KAFKA_PROJECTION_EVENTS_TOPIC: ${KAFKA_PROJECTION_EVENTS_TOPIC:-causet.projection-events.v1}
      KAFKA_WEBHOOK_EVENTS_TOPIC: ${KAFKA_WEBHOOK_EVENTS_TOPIC:-causet.webhook-events.v1}
      KAFKA_TOPIC_WEBHOOK_DELIVERY_EVENTS: ${KAFKA_TOPIC_WEBHOOK_DELIVERY_EVENTS:-causet.webhook-delivery-events.v1}

      CAUSET_STORAGE_ADAPTER: ${CAUSET_STORAGE_ADAPTER:-s3}
      CAUSET_STORAGE_ENDPOINT: ${CAUSET_STORAGE_ENDPOINT:-http://minio:9000}
      CAUSET_STORAGE_BUCKET: ${CAUSET_STORAGE_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_BUCKET: ${CAUSET_SPEC_ARTIFACTS_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_ENDPOINT: ${CAUSET_SPEC_ARTIFACTS_ENDPOINT:-http://minio:9000}
      AWS_REGION: ${AWS_REGION:-us-east-1}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-minioadmin}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-minioadmin}

      CAUSET_SAAS_CLOUD_URL: ${CAUSET_SAAS_CLOUD_URL:-http://causet-saas-cloud:8085}
      CAUSET_INTERNAL_SERVICE_TOKEN: ${CAUSET_INTERNAL_SERVICE_TOKEN:-074e066690642c3753fd1ff57fa4b9f36b406ca5cc85933f06a9c706682072cc}
      CAUSET_SECRETS_MASTER_KEY: ${CAUSET_SECRETS_MASTER_KEY:-ShY6QZLXFmXJYjKlh+QSpjT136we8YiL/iKtA7skK3Q=}
      CAUSET_OPENAI_API_KEY: ${CAUSET_OPENAI_API_KEY:-}
      OPENAI_API_KEY: ${OPENAI_API_KEY:-}
      CAUSET_MEMORY_STORE: ${CAUSET_MEMORY_STORE:-postgres}
      CAUSET_MEMORY_INGESTION_ENABLED: ${CAUSET_MEMORY_INGESTION_ENABLED:-true}

      VERDANT_RULES_COMPILATION_ENABLED: ${VERDANT_RULES_COMPILATION_ENABLED:-true}
      VERDANT_RULES_COMPILATION_PRECOMPILE: ${VERDANT_RULES_COMPILATION_PRECOMPILE:-true}
      WEBHOOK_DELIVERY_ENABLED: ${WEBHOOK_DELIVERY_ENABLED:-false}

      LOGGING_LEVEL_ROOT: ${LOGGING_LEVEL_ROOT:-INFO}
      LOGGING_LEVEL_COM_VERDANT: ${LOGGING_LEVEL_COM_VERDANT:-INFO}
    depends_on:
      verdant-migrations:
        condition: service_completed_successfully
      redpanda:
        condition: service_healthy
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio:
        condition: service_healthy
      minio-init:
        condition: service_completed_successfully
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 120s
    restart: unless-stopped
    networks:
      - causet-local

  causet-query-service:
    image: ${CAUSET_REGISTRY:-839868753911.dkr.ecr.us-east-1.amazonaws.com}/query-service:${CAUSET_IMAGE_TAG:-latest}
    container_name: causet-local-query
    ports:
      - "${QUERY_HTTP_PORT:-8082}:8082"
    environment:
      SERVER_PORT: 8082
      ENVIRONMENT: ${ENVIRONMENT:-local}

      CONTROL_PLANE_BASE_URL: ${CONTROL_PLANE_BASE_URL:-http://causet-saas-cloud:8085}
      GRPC_RUNTIME_ADDRESS: ${GRPC_RUNTIME_ADDRESS:-static://verdant-runtime-service:9090}
      GRPC_RUNTIME_ENABLED: ${GRPC_RUNTIME_ENABLED:-true}
      CU_REPORTING_ENABLED: ${CU_REPORTING_ENABLED:-true}

      PROJECTION_DB_URL: ${PROJECTION_DB_URL:-r2dbc:postgresql://postgres:5432/projections}
      PROJECTION_DB_USERNAME: ${PROJECTION_DB_USERNAME:-verdant}
      PROJECTION_DB_PASSWORD: ${PROJECTION_DB_PASSWORD:-verdant}

      CAUSET_STORAGE_ADAPTER: ${CAUSET_STORAGE_ADAPTER:-s3}
      CAUSET_STORAGE_ENDPOINT: ${CAUSET_STORAGE_ENDPOINT:-http://minio:9000}
      CAUSET_STORAGE_BUCKET: ${CAUSET_STORAGE_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_BUCKET: ${CAUSET_SPEC_ARTIFACTS_BUCKET:-causet-platform-spec-artifacts}
      AWS_REGION: ${AWS_REGION:-us-east-1}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-minioadmin}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-minioadmin}

      QUERY_CACHE_REDIS_ENABLED: ${QUERY_CACHE_REDIS_ENABLED:-true}
      REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
      QUERY_CACHE_DEFAULT_TTL: ${QUERY_CACHE_DEFAULT_TTL:-15}
      GLOBAL_MAX_LIMIT: ${GLOBAL_MAX_LIMIT:-1000}
      QUERY_TIMEOUT_SECONDS: ${QUERY_TIMEOUT_SECONDS:-15}

      KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-redpanda:9092}
      KAFKA_TOPIC_PROJECTION_EVENTS: ${KAFKA_TOPIC_PROJECTION_EVENTS:-causet.projection-events.v1}
      KAFKA_QUERY_TELEMETRY_TOPIC: ${KAFKA_QUERY_TELEMETRY_TOPIC:-causet.query-telemetry.v1}
      KAFKA_QUERY_LIFECYCLE_GROUP: ${KAFKA_QUERY_LIFECYCLE_GROUP:-causet-query-projection-lifecycle}

      LOGGING_LEVEL_COM_CAUSET: ${LOG_LEVEL_CAUSET:-INFO}
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy
      redis:
        condition: service_healthy
      redpanda:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8082/actuator/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 90s
    restart: unless-stopped
    networks:
      - causet-local

  causet-projection-worker:
    image: ${CAUSET_REGISTRY:-839868753911.dkr.ecr.us-east-1.amazonaws.com}/projection-worker:${CAUSET_IMAGE_TAG:-latest}
    container_name: causet-local-projection
    ports:
      - "${PROJECTION_HTTP_PORT:-8083}:8083"
    environment:
      SERVER_PORT: 8083
      ENVIRONMENT: ${ENVIRONMENT:-local}

      GRPC_RUNTIME_ADDRESS: ${GRPC_RUNTIME_ADDRESS:-static://verdant-runtime-service:9090}
      GRPC_RUNTIME_ENABLED: ${GRPC_RUNTIME_ENABLED:-true}

      KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-redpanda:9092}
      KAFKA_CONSUMER_GROUP_ID: ${KAFKA_CONSUMER_GROUP_ID_PROJECTION:-causet-projection-worker}
      KAFKA_TOPIC_PROJECTION_EVENTS: ${KAFKA_TOPIC_PROJECTION_EVENTS:-causet.projection-events.v1}
      KAFKA_TOPIC_DLQ: ${KAFKA_TOPIC_DLQ:-causet.projection-dlq.v1}
      KAFKA_AUTO_OFFSET_RESET: ${KAFKA_AUTO_OFFSET_RESET:-earliest}

      PROJECTION_DB_URL: ${PROJECTION_DB_URL:-r2dbc:postgresql://postgres:5432/projections}
      PROJECTION_DB_USERNAME: ${PROJECTION_DB_USERNAME:-verdant}
      PROJECTION_DB_PASSWORD: ${PROJECTION_DB_PASSWORD:-verdant}

      CAUSET_STORAGE_ADAPTER: ${CAUSET_STORAGE_ADAPTER:-s3}
      CAUSET_STORAGE_ENDPOINT: ${CAUSET_STORAGE_ENDPOINT:-http://minio:9000}
      CAUSET_STORAGE_BUCKET: ${CAUSET_STORAGE_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_BUCKET: ${CAUSET_SPEC_ARTIFACTS_BUCKET:-causet-platform-spec-artifacts}
      AWS_REGION: ${AWS_REGION:-us-east-1}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-minioadmin}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-minioadmin}

      CAUSET_PROJECTION_DEPLOYMENT_AUTO_REPLAY: ${CAUSET_PROJECTION_DEPLOYMENT_AUTO_REPLAY:-false}
      CAUSET_PROJECTION_INIT_FAIL_FAST: ${CAUSET_PROJECTION_INIT_FAIL_FAST:-false}
      CAUSET_PROJECTION_ACTIVATION_FAIL_FAST: ${CAUSET_PROJECTION_ACTIVATION_FAIL_FAST:-false}

      LOGGING_LEVEL_COM_CAUSET: ${LOG_LEVEL_CAUSET:-INFO}
      LOG_LEVEL_PROJECTION: ${LOG_LEVEL_PROJECTION:-INFO}
    depends_on:
      projection-schema-init:
        condition: service_completed_successfully
      postgres:
        condition: service_healthy
      redpanda:
        condition: service_healthy
      minio:
        condition: service_healthy
      verdant-runtime-service:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8083/actuator/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 90s
    restart: unless-stopped
    networks:
      - causet-local

  causet-saas-cloud:
    image: ${CAUSET_REGISTRY:-839868753911.dkr.ecr.us-east-1.amazonaws.com}/causet-saas-cloud:${CAUSET_IMAGE_TAG:-latest}
    container_name: causet-local-saas
    ports:
      - "${SAAS_HTTP_PORT:-8085}:8085"
    environment:
      SERVER_PORT: 8085
      ENVIRONMENT: ${ENVIRONMENT:-local}

      GRPC_RUNTIME_ADDRESS: ${GRPC_RUNTIME_ADDRESS:-static://verdant-runtime-service:9090}
      GRPC_RUNTIME_ENABLED: ${GRPC_RUNTIME_ENABLED:-true}

      R2DBC_URL: ${SAAS_R2DBC_URL:-r2dbc:postgresql://postgres:5432/verdant_control_plane}
      FLYWAY_URL: ${SAAS_FLYWAY_URL:-jdbc:postgresql://postgres:5432/verdant_control_plane}
      PROJECTION_DB_URL: ${PROJECTION_DB_URL:-r2dbc:postgresql://postgres:5432/projections}
      POSTGRES_USER: ${POSTGRES_USER:-verdant}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-verdant}

      SPRING_REDIS_HOST: ${REDIS_HOST:-redis}
      SPRING_REDIS_PORT: ${REDIS_PORT:-6379}
      REDIS_HOST: ${REDIS_HOST:-redis}
      REDIS_PORT: ${REDIS_PORT:-6379}

      KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-redpanda:9092}
      KAFKA_AUTH_EVENTS_TOPIC: ${KAFKA_AUTH_EVENTS_TOPIC:-causet.auth-events.v1}
      KAFKA_QUERY_TELEMETRY_TOPIC: ${KAFKA_QUERY_TELEMETRY_TOPIC:-causet.query-telemetry.v1}
      KAFKA_QUERY_TELEMETRY_GROUP: ${KAFKA_QUERY_TELEMETRY_GROUP:-causet-saas-query-telemetry}
      KAFKA_PROJECTION_EVENTS_TOPIC: ${KAFKA_PROJECTION_EVENTS_TOPIC:-causet.projection-events.v1}

      JWT_SECRET: ${JWT_SECRET:-causet-local-dev-secret}
      JWT_TTL: ${JWT_TTL:-60}
      AUTH_SIGNING_KEY_PATH: ${AUTH_SIGNING_KEY_PATH:-/app/auth-signing-key.pem}
      AUTH_ISSUER: ${AUTH_ISSUER:-https://auth.causet.local}
      AUTH_KID: ${AUTH_KID:-local}
      AUTH_BASE_URL: ${AUTH_BASE_URL:-http://localhost:8085}

      SECURITY_LOCAL_OPEN: ${SECURITY_LOCAL_OPEN:-true}
      SECURITY_DEV_SKIP_PLATFORMS: ${SECURITY_DEV_SKIP_PLATFORMS:-true}
      SECURITY_REQUIRE_ORG: ${SECURITY_REQUIRE_ORG:-false}

      CAUSET_STORAGE_ADAPTER: ${CAUSET_STORAGE_ADAPTER:-s3}
      CAUSET_STORAGE_ENDPOINT: ${CAUSET_STORAGE_ENDPOINT:-http://minio:9000}
      CAUSET_STORAGE_BUCKET: ${CAUSET_STORAGE_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_BUCKET: ${CAUSET_SPEC_ARTIFACTS_BUCKET:-causet-platform-spec-artifacts}
      CAUSET_SPEC_ARTIFACTS_ENDPOINT: ${CAUSET_SPEC_ARTIFACTS_ENDPOINT:-http://minio:9000}
      CAUSET_SPEC_ARTIFACTS_REGION: ${CAUSET_SPEC_ARTIFACTS_REGION:-us-east-1}
      CAUSET_PROJECTIONS_S3_PREFIX: ${CAUSET_PROJECTIONS_S3_PREFIX:-projections/}
      AWS_REGION: ${AWS_REGION:-us-east-1}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-minioadmin}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-minioadmin}

      CAUSET_SECRETS_MASTER_KEY: ${CAUSET_SECRETS_MASTER_KEY:-ShY6QZLXFmXJYjKlh+QSpjT136we8YiL/iKtA7skK3Q=}
      CAUSET_INTERNAL_SERVICE_TOKEN: ${CAUSET_INTERNAL_SERVICE_TOKEN:-074e066690642c3753fd1ff57fa4b9f36b406ca5cc85933f06a9c706682072cc}
      QUERY_SERVICE_URL: ${QUERY_SERVICE_URL:-http://causet-query-service:8082}
      VERDANT_CORE_URL: ${VERDANT_CORE_URL:-http://verdant-runtime-service:8080}

      DASHBOARD_CACHE_ENABLED: ${DASHBOARD_CACHE_ENABLED:-true}
      DASHBOARD_READ_MODEL_ENABLED: ${DASHBOARD_READ_MODEL_ENABLED:-true}
      LOG_LEVEL_CAUSET: ${LOG_LEVEL_CAUSET:-INFO}
    configs:
      - source: auth_signing_key
        target: /app/auth-signing-key.pem
        mode: 0444
    depends_on:
      verdant-runtime-service:
        condition: service_healthy
      redpanda:
        condition: service_healthy
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio-init:
        condition: service_completed_successfully
      causet-query-service:
        condition: service_started
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8085/actuator/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    restart: unless-stopped
    networks:
      - causet-local

configs:
  postgres_init:
    content: |
      CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
      
      -- Create control plane database safely
      SELECT 'CREATE DATABASE verdant_control_plane'
      WHERE NOT EXISTS (
        SELECT FROM pg_database WHERE datname = 'verdant_control_plane'
      )\gexec;
      
      -- Create projections database for query service
      SELECT 'CREATE DATABASE projections'
      WHERE NOT EXISTS (
        SELECT FROM pg_database WHERE datname = 'projections'
      )\gexec;
      
      -- Grant privileges safely
      GRANT ALL PRIVILEGES ON DATABASE verdant TO verdant;
      GRANT ALL PRIVILEGES ON DATABASE verdant_control_plane TO verdant;
      GRANT ALL PRIVILEGES ON DATABASE projections TO verdant;

  projections_schema:
    content: |
      -- Create projection_cursors table in projections database
      -- Required by causet-projection-worker for idempotency
      
      \connect projections
      
      CREATE TABLE IF NOT EXISTS projection_cursors (
          platform_id    TEXT NOT NULL,
          application_id TEXT NOT NULL,
          fork_id        TEXT NOT NULL,
          "partition"    INT  NOT NULL,
          last_cursor    BIGINT NOT NULL,
          updated_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          PRIMARY KEY (platform_id, application_id, fork_id, "partition")
      );
      
      CREATE INDEX IF NOT EXISTS idx_projection_cursors_updated
      ON projection_cursors (updated_at);
      
      CREATE TABLE IF NOT EXISTS projection_dead_letter (
          id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
          platform_id     TEXT NOT NULL,
          application_id  TEXT NOT NULL,
          fork_id         TEXT NOT NULL,
          projection_name TEXT NOT NULL,
          stream_id       TEXT,
          entity_id       TEXT,
          event_type      TEXT,
          kafka_partition INT,
          kafka_offset    BIGINT,
          payload_json    TEXT,
          error_message   TEXT,
          retry_count     INT NOT NULL DEFAULT 0,
          created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          resolved_at     TIMESTAMPTZ
      );
      
      CREATE INDEX IF NOT EXISTS idx_projection_dlq_unresolved
          ON projection_dead_letter (platform_id, application_id, fork_id, projection_name)
          WHERE resolved_at IS NULL;

  create_buckets:
    content: |
      #!/bin/sh
      
      # MinIO bucket initialization script
      # This script creates required buckets on startup.
      # Intended to run inside the minio-init container: docker compose run --rm minio-init
      
      set -e
      
      # Resolve mc: in minio/mc image it is at /usr/bin/mc; on host it may be on PATH
      if [ -x /usr/bin/mc ]; then
        MC=/usr/bin/mc
      elif command -v mc >/dev/null 2>&1; then
        MC=mc
      else
        echo "MinIO client (mc) not found. Run this script inside the minio-init container:"
        echo "  docker compose run --rm minio-init"
        exit 1
      fi
      
      echo "Waiting for MinIO to be ready..."
      until $$MC alias set myminio http://minio:9000 minioadmin minioadmin; do
        echo "MinIO not ready yet, retrying in 2 seconds..."
        sleep 2
      done
      
      echo "MinIO is ready, creating buckets..."
      
      # =============================================================================
      # PRIMARY BUCKET: causet-platform-spec-artifacts
      # All releases, rules, SDK, and compiled artifacts now use this bucket
      # =============================================================================
      if $$MC ls myminio/causet-platform-spec-artifacts 2>/dev/null; then
        echo "✅ Bucket 'causet-platform-spec-artifacts' already exists (PRIMARY)"
      else
        $$MC mb myminio/causet-platform-spec-artifacts
        echo "✅ Created bucket 'causet-platform-spec-artifacts' (PRIMARY)"
      fi
      $$MC version enable myminio/causet-platform-spec-artifacts
      echo "✅ Enabled versioning on 'causet-platform-spec-artifacts'"
      
      # =============================================================================
      # LEGACY BUCKETS (backward compatibility)
      # =============================================================================
      
      # Create verdant-platform-yaml-definitions bucket if it doesn't exist (LEGACY)
      if $$MC ls myminio/verdant-platform-yaml-definitions 2>/dev/null; then
        echo "✅ Bucket 'verdant-platform-yaml-definitions' already exists (LEGACY)"
      else
        $$MC mb myminio/verdant-platform-yaml-definitions
        echo "✅ Created bucket 'verdant-platform-yaml-definitions' (LEGACY)"
      fi
      $$MC version enable myminio/verdant-platform-yaml-definitions
      echo "✅ Enabled versioning on 'verdant-platform-yaml-definitions' (LEGACY)"
      
      # Create verdant-platform-compiled-ruleset bucket if it doesn't exist (LEGACY)
      if $$MC ls myminio/verdant-platform-compiled-ruleset 2>/dev/null; then
        echo "✅ Bucket 'verdant-platform-compiled-ruleset' already exists (LEGACY)"
      else
        $$MC mb myminio/verdant-platform-compiled-ruleset
        echo "✅ Created bucket 'verdant-platform-compiled-ruleset' (LEGACY)"
      fi
      $$MC version enable myminio/verdant-platform-compiled-ruleset
      echo "✅ Enabled versioning on 'verdant-platform-compiled-ruleset' (LEGACY)"
      
      # Create verdant-artifacts bucket if it doesn't exist (for uploaded .vrd files)
      if $$MC ls myminio/verdant-artifacts 2>/dev/null; then
        echo "✅ Bucket 'verdant-artifacts' already exists"
      else
        $$MC mb myminio/verdant-artifacts
        echo "✅ Created bucket 'verdant-artifacts'"
      fi
      
      # Set versioning if needed
      $$MC version enable myminio/verdant-artifacts
      echo "✅ Enabled versioning on 'verdant-artifacts'"
      
      echo "🎉 MinIO initialization complete!"
      

  init_projections_schema:
    content: |
      #!/bin/sh
      set -e
      # Ensure projections DB exists (postgres init may not have run on existing volumes)
      psql -h postgres -U verdant -d postgres -tc "SELECT 1 FROM pg_database WHERE datname='projections'" | grep -q 1 \
        || psql -h postgres -U verdant -d postgres -c 'CREATE DATABASE projections'
      # Create projection_cursors and projection_dead_letter tables
      psql -h postgres -U verdant -d projections -f /schema/projection_cursors.sql
      psql -h postgres -U verdant -d projections -f /schema/projection_dead_letter.sql

  projection_cursors:
    content: |
      -- Kafka offset tracking table for idempotency.
      -- Stores the last successfully processed Kafka record offset per (platform, application, fork, partition).
      -- Uses Kafka offsets (monotonically increasing per partition) rather than ledger cursors,
      -- because ledger cursors are per-entity and restart from 1 independently for each entity.
      -- Created by control plane, not by worker.
      -- Reference schema for documentation.
      
      CREATE TABLE IF NOT EXISTS projection_cursors (
          platform_id    TEXT NOT NULL,
          application_id TEXT NOT NULL,
          fork_id        TEXT NOT NULL,
          "partition"    INT  NOT NULL,
          last_cursor    BIGINT NOT NULL,  -- stores Kafka offset; column name kept for backward compat
          updated_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          PRIMARY KEY (platform_id, application_id, fork_id, "partition")
      );
      
      CREATE INDEX IF NOT EXISTS idx_projection_cursors_updated
      ON projection_cursors (updated_at);

  projection_dead_letter:
    content: |
      -- Dead-letter table for permanently failed projection applications.
      -- Created by control plane / init scripts, not by the worker at runtime.
      -- Reference schema for documentation (mirrored in infra/postgres/02-projections-schema.sql).
      
      CREATE TABLE IF NOT EXISTS projection_dead_letter (
          id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
          platform_id     TEXT NOT NULL,
          application_id  TEXT NOT NULL,
          fork_id         TEXT NOT NULL,
          projection_name TEXT NOT NULL,
          stream_id       TEXT,
          entity_id       TEXT,
          event_type      TEXT,
          kafka_partition INT,
          kafka_offset    BIGINT,
          payload_json    TEXT,
          error_message   TEXT,
          retry_count     INT NOT NULL DEFAULT 0,
          created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
          resolved_at     TIMESTAMPTZ
      );
      
      CREATE INDEX IF NOT EXISTS idx_projection_dlq_unresolved
          ON projection_dead_letter (platform_id, application_id, fork_id, projection_name)
          WHERE resolved_at IS NULL;

  auth_signing_key:
    content: |
      -----BEGIN PRIVATE KEY-----
      MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwax74I2h8PC81
      78UlGcL/vcVlBM4YR0N7+lM/UF9fkADSkLPEJfUQuJeA32e9lAS15YUwroAqkSr6
      SLoeenqccdxEds0XSn66er67rDqdRtLLm191nX0Hy2eoeWzB2eXXfd/mnsZgREc+
      a8g2CUHJlrl6hXD1wHyokTZ3hPGZIV8ZIzItI4BtHmBooOr4aSHJYG6c0O3XKGrb
      pvMDjfna5YzpRJetSjBaB56tZ/2cuAfGCSK5NcITCBpvKS50FGzQU+C94fOqvCl3
      EdFox+WFnFKXKsFZFDCY3DrN5ekhE5zHqHwqPc1aaDHwuuGwSYCAov3GXX85nzzz
      PyCq3tvJAgMBAAECggEAHw91H+YIBtuHylzBHoOXH/S29M6wRHkDiifsETki2Y3e
      fzQn2lB+gaemUPPEbY3KeIYE+am3egceBHvafPQ+cx3e1JUADeo5PrbOrvcnSLqH
      8X4VA/2q3jzzLqfWPJkTDOomChIJRjivPG+4i778fLQTSt2Eolf1+q26Y7ed28JH
      HYQQMmV6IJyU6YKZW88xsbiBb3NfKhbEH/NmJTwBgqVzNiDsLwWLzUjBG+kCkR44
      JFKa5cVYgxkXpYuyWflFYoPJsI/F2vvVRLsXEAx9sYbA1TQghbS0BjW/HzgZ8XO9
      2LOWXshl9LF6uyx2x0dGL72GvFq7OvkEsYLdpyiy8wKBgQD4lI7vhOfpXAdtmhJU
      /+OMXkKDYZk69vVaVffXuxYoEgLHGbqKrM/gvlTMIXZS3uE/iWD+tNAOJnaU9rli
      QXr8qyZe2F5ijLG9d/KmNs58U0DTRj2ZhhDkqwQL4xslTusgHxbVMXa8pJDjCky5
      phimSHGhxUoLb1P1GsAgZZ0jZwKBgQC1rymPciNUGwv6Bsy7vti6U+ToLn9yl0u+
      DMFltH2/u4fHDSjYKm1yWFa6b/Eft6/ccq/P901CDXrdCbIgcoysPguL2b6ppmgL
      LxehPzrEap+w4cW/Rg5zi5AdJX4pgDrlK3AjRmnjK5kLD6u8gdHcfklgfREFX8VN
      n+hqwSk5TwKBgQDGFZvRQheL3coNjLkPAtpRAMv/5cY1NpZNZjQZtPvy8mdEQTxl
      RVBhx0nLYN7pKrJT7yVb0rn4jK3yBqmzonpe3gO1kLWl0nfQg8TYrtPcPntp4I81
      x5+umJig4FjBYiH7dsq7GK+Fo53RTmPVjIYX+6KRRqW6dvxwVODE8DRIAQKBgESk
      ANepD5/e+A9mMkNcPxVmTL1Lk+r/LiaHOynpnvipKQsS9bN1lBP3PA0D7d1F0LNA
      wRuZGx8xaJHJ7/pCTNNmPYaApYAC1pwjFpJdCR7cCakSI1u4ZA1TVQct+xRC1f1v
      A9bP64FgH1oGeaOmiFr9e0tw9NVK8Yf6OhzbDXARAoGBAKGGrQ1j1VFval1t6r3Y
      XEI8FLSMkNon+kK6MadJkwloQz805pzXBFY2eK1tNUGxNndA+gGecMmjxa1W+/0K
      MfukSJTx612tp8sYPnkPpz5VJoJCiYx/czCMT6jqHoMdIUcH5WHrF3rdZYxviFN0
      1gathauhW2f5A2W3wgm0/AXx
      -----END PRIVATE KEY-----

volumes:
  redpanda-data:
  postgres-data:
  redis-data:
  minio-data:

networks:
  causet-local:
    driver: bridge
