services:
  mongo:
    image: mongo:7
    restart: unless-stopped
    # modest cache so the whole stack fits comfortably on small hosts
    command: ["mongod", "--wiredTigerCacheSizeGB", "0.5"]
    # WiredTiger needs far more FDs than the 1024 some hosts default to;
    # running out crashes mongod with a WT_PANIC during file creation
    ulimits:
      nofile:
        soft: 64000
        hard: 64000
    # Mongo is reachable only on the compose network; never published to the
    # host (§12).
    volumes:
      # data lives in-repo (gitignored, dot-prefixed so go tooling skips it)
      - ./.volumes/mongo:/data/db
    healthcheck:
      test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  app:
    build: .
    restart: unless-stopped
    env_file: .env
    environment:
      MONGO_URI: mongodb://mongo:27017
      # in-network address handed to the external services for callbacks
      # and signed attachment URLs (§5.2 example: http://app:8787/…)
      APP_INTERNAL_URL: http://app:8787
    # NOTE: published on all interfaces to match this host's pattern (gitea,
    # outline, wekan are exposed the same way; docker-published ports bypass
    # UFW). The spec-default loopback-only binding is the commented line —
    # restore it once the app sits behind the reverse proxy exclusively.
    ports:
      - "${APP_PORT:-8787}:8787"
      # - "127.0.0.1:${APP_PORT:-8787}:8787"
    depends_on:
      mongo:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8787/healthz"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s

  # --- placeholder external services (docker compose --profile mocks up) ---
  # Two fully independent services: separate builds, ports, tokens (§5).
  atomizer-mock:
    build: ./services/atomizer
    profiles: ["mocks"]
    restart: unless-stopped
    environment:
      PORT: "8090"
      ATOMIZER_TOKEN: ${ATOMIZER_TOKEN}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
      ANTHROPIC_OPENAI_BASE_URL: ${ANTHROPIC_OPENAI_BASE_URL:-https://api.anthropic.com/v1}
      ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-6}
      LLM_API_STYLE: ${LLM_API_STYLE:-openai}
    ports:
      - "127.0.0.1:8090:8090"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8090/healthz"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s

  work-performer:
    build: ./services/work-performer
    profiles: ["mocks"]
    restart: unless-stopped
    environment:
      PORT: "8091"
      WORK_PERFORMER_TOKEN: ${WORK_PERFORMER_TOKEN}
      PUBLIC_BASE_URL: http://work-performer:8091
    # host Claude Code config/secrets (§9.2); mounted into the node user's
    # home because claude refuses --dangerously-skip-permissions as root
    volumes:
      - ${HOME}/.claude:/home/node/.claude
      - ${HOME}/.claude.json:/home/node/.claude.json
      - ./.volumes/work:/work
    ports:
      - "127.0.0.1:8091:8091"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    healthcheck:
      test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:8091/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
