name: agentteam-delivery-split

# NON-PRODUCTION: local contract for the SaaS delivery topology. It intentionally
# stays separate from docker-compose.local.yml so the default all-profile stack
# keeps its single runtime owner. The delivery-fixtures, repository-fixtures,
# and Ops seed services create synthetic users, tokens, workspaces, placements,
# machines, repositories, and Git data for smoke tests; production must never
# copy these rows or credentials and must use schema jobs plus reviewed control-
# plane configuration and migrations instead.

x-agentteam-server-image: &agentteam-server-image
  image: agentteam-server:delivery-split
  build:
    context: .
    dockerfile: Dockerfile.server
    args:
      AGENTTEAM_SERVER_VERSION: delivery-split
      AGENTTEAM_SERVER_GIT_COMMIT: local

x-agentteam-web-image: &agentteam-web-image
  image: agentteam-web-meshx:delivery-split
  build:
    context: .
    dockerfile: Dockerfile.web
    args:
      AGENTTEAM_DEV_FEATURES: MESHX

x-agentteam-core-environment: &agentteam-core-environment
  AGENTTEAM_ENV: staging
  AGENTTEAM_LOG_LEVEL: debug
  AGENTTEAM_HTTP_ADDRESS: :3001
  AGENTTEAM_PUBLIC_BASE_URL: http://localhost:${AGENTTEAM_DELIVERY_SPLIT_PORT:-28083}
  AGENTTEAM_DAEMON_CONNECT_BASE_URL: http://localhost:${AGENTTEAM_DELIVERY_SPLIT_PORT:-28083}
  AGENTTEAM_DATABASE_DSN: root@tcp(tidb:4000)/agentteam_core?multiStatements=true&parseTime=true&loc=UTC
  AGENTTEAM_ARCHIVE_DRIVER: filesystem
  AGENTTEAM_ARCHIVE_FILESYSTEM_ROOT: /data/archive
  AGENTTEAM_MEDIA_OBJECT_STORAGE_DRIVER: disabled
  AGENTTEAM_MAIL_DRIVER: noop
  AGENTTEAM_MOBILE_VERIFY_DRIVER: noop
  AGENTTEAM_PASSWORD_AUTH_ENABLED: "true"
  AGENTTEAM_AUTH_REGISTRATION_MODE: open
  AGENTTEAM_ORG_CREDENTIALS_KEY_FILE: /run/secrets/org_credentials_master_key
  AGENTTEAM_APP_CREDENTIALS_KEY_FILE: /run/secrets/app_credentials_master_key
  AGENTTEAM_INTERNAL_ADMIN_TOKEN: agentteam-local-admin-token
  AGENTTEAM_MESHX_ENABLED: "true"
  AGENTTEAM_MESHX_KNOWLEDGE_BASES_ENABLED: "true"
  AGENTTEAM_MAAS_BASE_URL: http://agentteam-maas:8080
  AGENTTEAM_MAAS_RUNTIME_BASE_URL: http://localhost:${AGENTTEAM_DELIVERY_SPLIT_MAAS_PORT:-28088}/maas/v1
  AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN: agentteam-maas-local-internal-token
  AGENTTEAM_MAAS_CAPABILITY_ED25519_PRIVATE_KEY_FILE: /run/secrets/agentteam-maas-capability-key.json
  AGENTTEAM_BILLING_ENABLED: "false"

x-meshx-environment: &meshx-environment
  MESHX_DATABASE_DSN: root@tcp(tidb:4000)/agentteam_meshx?multiStatements=true&parseTime=true&loc=UTC
  MESHX_AGS_DATABASE_DSN: root@tcp(tidb:4000)/agentteam_ags?multiStatements=true&parseTime=true&loc=UTC
  MESHX_AGS_GIT_REPO_DIR: /data/meshx/ags-repos
  MESHX_CONTENT_STORE_ENABLED: ${MESHX_CONTENT_STORE_ENABLED:-false}
  MESHX_CONTENT_STORE_FILESYSTEM_ROOT: /data/meshx/content-store
  MESHX_AGS_ADMIN_LOGIN: meshx-admin
  MESHX_AGS_ADMIN_TOKEN: meshx-local-admin-token
  MESHX_AGS_ALLOW_ANY_TOKEN: "true"
  MESHX_AUTH_ENABLED: "true"
  MESHX_AUTH_TRUSTED_JWT_CLAIMS: "true"
  MESHX_AUTH_TRUSTED_JWT_CLAIMS_PROTECTED_LISTENER: "true"
  MESHX_AUTH_JWT_HEADER: X-AgentTeam-Verified-JWT
  MESHX_KNOWLEDGE_QA_DEBUG_MODEL_OUTPUT: "true"
  MESHX_AUTOMATION_SERVER_RUNNER_ENABLED: "false"
  MESHX_AUTOMATION_BLOB_FILESYSTEM_ROOT: /data/meshx/automation-blobs
  MESHX_AUTOMATION_CREDENTIALS_MASTER_KEY: nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=
  MESHX_PERSONAL_ACCESS_TOKEN_CREDENTIALS_MASTER_KEY: 11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=
  MESHX_KB_SOURCE_MIRROR_WORK_DIR: /data/meshx/kb-source-mirrors
  MESHX_KB_PUBLIC_REPO_CACHE_DIR: /data/meshx/public-repo-cache
  MESHX_INTERNAL_ADMIN_SERVICE_TOKEN: meshx-local-admin-token

x-agentteam-healthcheck: &agentteam-healthcheck
  test: ["CMD", "bash", "-ec", "exec 3<>/dev/tcp/127.0.0.1/3001; printf 'GET /readyz HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3; grep -q 'HTTP/1.[01] 200' <&3"]
  interval: 3s
  timeout: 3s
  retries: 40
  start_period: 15s

services:
  tidb:
    image: pingcap/tidb:v8.5.6
    command: ["--store=unistore", "--path=/var/lib/tidb", "--host=0.0.0.0", "--status=10080"]
    ports:
      - "127.0.0.1:${AGENTTEAM_DELIVERY_SPLIT_TIDB_PORT:-24003}:4000"
    volumes:
      - tidb-data:/var/lib/tidb
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O- http://127.0.0.1:10080/status >/dev/null 2>&1"]
      interval: 2s
      timeout: 3s
      retries: 60
      start_period: 10s

  databases:
    image: mysql:8.4
    depends_on:
      tidb:
        condition: service_healthy
    entrypoint: ["/bin/sh", "-ec"]
    command:
      - |
        mysql --protocol=TCP --host=tidb --port=4000 --user=root --ssl-mode=DISABLED <<'SQL'
        CREATE DATABASE IF NOT EXISTS agentteam_core CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
        CREATE DATABASE IF NOT EXISTS agentteam_meshx CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
        CREATE DATABASE IF NOT EXISTS agentteam_ags CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
        CREATE DATABASE IF NOT EXISTS agentteam_maas CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
        CREATE DATABASE IF NOT EXISTS agentteam_ops CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
        SQL
    restart: "no"

  agentteam-maas:
    image: agentteam-maas:delivery-split
    build:
      context: ${AGENTTEAM_MAAS_SOURCE_DIR:-${HOME}/work/code/agentteam-maas}
      dockerfile: Dockerfile
    environment:
      AGENTTEAM_MAAS_ADDR: :8080
      AGENTTEAM_MAAS_DATABASE_DSN: root@tcp(tidb:4000)/agentteam_maas?multiStatements=true&parseTime=true&loc=UTC
      AGENTTEAM_MAAS_CAPABILITY_ISSUER: agentteam
      AGENTTEAM_MAAS_CAPABILITY_AUDIENCE: agentteam-maas
      AGENTTEAM_MAAS_CAPABILITY_ED25519_PUBLIC_KEYS: '{"agentteam-local-dev":"11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="}'
      AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN: agentteam-maas-local-internal-token
    depends_on:
      databases:
        condition: service_completed_successfully
    ports:
      - "127.0.0.1:${AGENTTEAM_DELIVERY_SPLIT_MAAS_PORT:-28088}:8080"
    healthcheck:
      test: ["CMD", "bash", "-ec", "exec 3<>/dev/tcp/127.0.0.1/8080; printf 'GET /healthz HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3; grep -q 'HTTP/1.[01] 200' <&3"]
      interval: 3s
      timeout: 3s
      retries: 30
      start_period: 10s
    restart: unless-stopped

  agentteam-schema:
    <<: *agentteam-server-image
    command: ["/app/bin/agentteam-schema", "init"]
    environment:
      <<: [*agentteam-core-environment, *meshx-environment]
    depends_on:
      databases:
        condition: service_completed_successfully
    volumes:
      - meshx-data-default:/data
    restart: "no"

  delivery-fixtures:
    image: mysql:8.4
    depends_on:
      agentteam-schema:
        condition: service_completed_successfully
    entrypoint: ["/bin/sh", "-ec"]
    command:
      - |
        mysql --protocol=TCP --host=tidb --port=4000 --user=root --ssl-mode=DISABLED <<'SQL'
        SET FOREIGN_KEY_CHECKS=0;
        INSERT INTO agentteam_core.workspace_groups (
          workspace_group_id, display_name, state, resource_profile, service_ref, created_at, updated_at
        ) VALUES
          ('workspace-default', 'workspace-default', 'active', 'default', 'agentteam-workspace-default', NOW(), NOW()),
          ('workspace-large-a', 'workspace-large-a', 'active', 'large', 'agentteam-workspace-large-a', NOW(), NOW())
        ON DUPLICATE KEY UPDATE state='active', service_ref=VALUES(service_ref), updated_at=VALUES(updated_at);
        INSERT INTO agentteam_core.workspace_placements (
          workspace_id, workspace_group_id, state, placement_epoch, created_at, updated_at
        ) VALUES
          ('wsp_delivery_a', 'workspace-default', 'active', 1, NOW(), NOW()),
          ('wsp_delivery_b', 'workspace-large-a', 'active', 1, NOW(), NOW())
        ON DUPLICATE KEY UPDATE workspace_group_id=VALUES(workspace_group_id), state='active', placement_epoch=placement_epoch+1, updated_at=VALUES(updated_at);

        INSERT INTO agentteam_core.users (
          id, email, name, display_name, password_hash, default_workspace_bootstrap_status, created_at, updated_at
        ) VALUES (
          'usr_delivery', 'delivery@example.invalid', 'delivery-user', 'Delivery User', 'fixture-not-a-login-secret', 'pending', NOW(), NOW()
        ) ON DUPLICATE KEY UPDATE updated_at=VALUES(updated_at);
        INSERT INTO agentteam_core.workspaces (id, organization_id, slug, name, created_at, updated_at) VALUES
          ('wsp_delivery_a', 'org_delivery', 'delivery-a', 'Delivery A', NOW(), NOW()),
          ('wsp_delivery_b', 'org_delivery', 'delivery-b', 'Delivery B', NOW(), NOW())
        ON DUPLICATE KEY UPDATE name=VALUES(name), updated_at=VALUES(updated_at);
        INSERT INTO agentteam_core.members (
          id, workspace_id, type, display_name, user_id, workspace_role, status, created_at, updated_at
        ) VALUES
          ('mem_delivery_a', 'wsp_delivery_a', 'human', 'Delivery User', 'usr_delivery', 'owner', 'active', NOW(), NOW()),
          ('mem_delivery_b', 'wsp_delivery_b', 'human', 'Delivery User', 'usr_delivery', 'owner', 'active', NOW(), NOW())
        ON DUPLICATE KEY UPDATE status='active', updated_at=VALUES(updated_at);
        INSERT INTO agentteam_core.user_sessions (id, user_id, token_hash, expires_at, created_at)
        VALUES (
          'sess_delivery_route', 'usr_delivery',
          'ea866a757e4c38babfa8127cbe9a409d3e1f93a00ff1488ff735fcf917afffd0',
          DATE_ADD(NOW(), INTERVAL 1 DAY), NOW()
        ) ON DUPLICATE KEY UPDATE token_hash=VALUES(token_hash), expires_at=VALUES(expires_at), revoked_at=NULL;
        INSERT INTO agentteam_core.machines (
          id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at
        ) VALUES (
          'mac_delivery', 'wsp_delivery_a', 'usr_delivery', 'Delivery Machine', 'delivery-machine-api-key', NOW(), NOW()
        ) ON DUPLICATE KEY UPDATE workspace_id=VALUES(workspace_id), api_key=VALUES(api_key), updated_at=VALUES(updated_at);
        SET FOREIGN_KEY_CHECKS=1;

        INSERT INTO agentteam_ags.users (
          id, login, name, email, type, status, user_kind, site_admin, default_repository_permission, created_at, updated_at
        ) VALUES
          (9001, 'delivery-admin', 'Delivery Admin', '', 'User', 'active', 'human', 0, 'none', NOW(), NOW()),
          (9002, 'delivery-a', 'Delivery A', '', 'Organization', 'active', 'human', 0, 'none', NOW(), NOW()),
          (9003, 'delivery-b', 'Delivery B', '', 'Organization', 'active', 'human', 0, 'none', NOW(), NOW())
        ON DUPLICATE KEY UPDATE site_admin=VALUES(site_admin), updated_at=VALUES(updated_at);
        INSERT INTO agentteam_ags.tokens (id, user_id, name, value, created_at)
        VALUES (9001, 9001, 'delivery-route-smoke', 'delivery-route-token', NOW())
        ON DUPLICATE KEY UPDATE user_id=VALUES(user_id), value=VALUES(value);
        INSERT INTO agentteam_ags.user_identities (id, user_id, provider, subject, created_at, updated_at)
        VALUES (9001, 9001, 'agentteam', 'usr_delivery', NOW(), NOW())
        ON DUPLICATE KEY UPDATE user_id=VALUES(user_id), subject=VALUES(subject), updated_at=VALUES(updated_at);
        INSERT INTO agentteam_ags.repositories (
          id, name, full_name, owner_id, private, visibility, default_branch, created_at, updated_at
        ) VALUES
          (9101, 'repo', 'delivery-a/repo', 9002, 1, 'private', 'main', NOW(), NOW()),
          (9102, 'repo', 'delivery-b/repo', 9003, 1, 'private', 'main', NOW(), NOW())
        ON DUPLICATE KEY UPDATE owner_id=VALUES(owner_id), private=VALUES(private), updated_at=VALUES(updated_at);
        INSERT INTO agentteam_ags.collaborators (repository_id, user_id, permission, created_at)
        VALUES (9101, 9001, 'write', NOW())
        ON DUPLICATE KEY UPDATE permission=VALUES(permission);

        INSERT INTO agentteam_meshx.resource_repositories (
          id, workspace_id, resource_type, resource_id, repo_role, owner_kind,
          repo_owner, repo_name, repo_full_name, ags_repo_id, default_ref,
          visibility, user_visible, created_by, updated_by, created_at, updated_at
        ) VALUES
          ('rr_delivery_a', 'wsp_delivery_a', 'automation', 'aut_delivery_a', 'automation_source', 'workspace',
           'delivery-a', 'repo', 'delivery-a/repo', '9101', 'main', 'private', 1, 'fixture', 'fixture', NOW(), NOW()),
          ('rr_delivery_b', 'wsp_delivery_b', 'automation', 'aut_delivery_b', 'automation_source', 'workspace',
           'delivery-b', 'repo', 'delivery-b/repo', '9102', 'main', 'private', 1, 'fixture', 'fixture', NOW(), NOW())
        ON DUPLICATE KEY UPDATE workspace_id=VALUES(workspace_id), repo_full_name=VALUES(repo_full_name), updated_at=VALUES(updated_at);
        SQL
    restart: "no"

  repository-fixtures:
    <<: *agentteam-server-image
    user: "0:0"
    entrypoint: ["/bin/bash", "-ec"]
    command:
      - |
        seed_repository() {
          local full_name="$$1" root="$$2" work
          work="$$(mktemp -d)"
          git init --initial-branch=main "$$work"
          git -C "$$work" config user.name "Delivery Split Fixture"
          git -C "$$work" config user.email "delivery-split@example.invalid"
          printf 'delivery-split-smoke\n' >"$$work/smoke.txt"
          git -C "$$work" add smoke.txt
          git -C "$$work" commit -m "seed delivery split smoke"
          mkdir -p "$$root/$$(dirname "$$full_name")"
          rm -rf "$$root/$$full_name.git"
          git clone --bare "$$work" "$$root/$$full_name.git"
          rm -rf "$$work"
        }
        seed_repository delivery-a/repo /data/default/meshx/ags-repos
        seed_repository delivery-b/repo /data/large-a/meshx/ags-repos
        chown -R 65532:65532 /data/default /data/large-a
    volumes:
      - meshx-data-default:/data/default
      - meshx-data-large-a:/data/large-a
    restart: "no"

  agentteam-central:
    <<: *agentteam-server-image
    environment:
      <<: *agentteam-core-environment
      AGENTTEAM_SERVER_PROFILE: central
    configs:
      - source: org-credentials-key
        target: /run/secrets/org_credentials_master_key
      - source: app-credentials-key
        target: /run/secrets/app_credentials_master_key
      - source: agentteam-maas-capability-key
        target: /run/secrets/agentteam-maas-capability-key.json
        mode: 0444
    depends_on:
      delivery-fixtures:
        condition: service_completed_successfully
      agentteam-maas:
        condition: service_healthy
    healthcheck: *agentteam-healthcheck
    restart: unless-stopped

  agentteam-workspace-default:
    <<: *agentteam-server-image
    environment:
      <<: [*agentteam-core-environment, *meshx-environment]
      AGENTTEAM_SERVER_PROFILE: workspace
      AGENTTEAM_WORKSPACE_GROUP_ID: workspace-default
      AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID: workspace-default
    configs:
      - source: org-credentials-key
        target: /run/secrets/org_credentials_master_key
      - source: app-credentials-key
        target: /run/secrets/app_credentials_master_key
      - source: agentteam-maas-capability-key
        target: /run/secrets/agentteam-maas-capability-key.json
        mode: 0444
    depends_on:
      delivery-fixtures:
        condition: service_completed_successfully
      repository-fixtures:
        condition: service_completed_successfully
      agentteam-maas:
        condition: service_healthy
    volumes:
      - meshx-data-default:/data
    healthcheck: *agentteam-healthcheck
    restart: unless-stopped

  agentteam-workspace-large-a:
    <<: *agentteam-server-image
    environment:
      <<: [*agentteam-core-environment, *meshx-environment]
      AGENTTEAM_SERVER_PROFILE: workspace
      AGENTTEAM_WORKSPACE_GROUP_ID: workspace-large-a
      AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID: workspace-default
    configs:
      - source: org-credentials-key
        target: /run/secrets/org_credentials_master_key
      - source: app-credentials-key
        target: /run/secrets/app_credentials_master_key
      - source: agentteam-maas-capability-key
        target: /run/secrets/agentteam-maas-capability-key.json
        mode: 0444
    depends_on:
      delivery-fixtures:
        condition: service_completed_successfully
      repository-fixtures:
        condition: service_completed_successfully
      agentteam-maas:
        condition: service_healthy
    volumes:
      - meshx-data-large-a:/data
    healthcheck: *agentteam-healthcheck
    restart: unless-stopped

  agentteam-gateway:
    <<: *agentteam-server-image
    command: ["/app/bin/agentteam-gateway-server"]
    environment:
      <<: *agentteam-core-environment
      AGENTTEAM_MESHX_ENABLED: "false"
      AGENTTEAM_GATEWAY_CENTRAL_UPSTREAM_URL: http://agentteam-central:3001
      AGENTTEAM_GATEWAY_WORKSPACE_UPSTREAMS: workspace-default=http://agentteam-workspace-default:3001,workspace-large-a=http://agentteam-workspace-large-a:3001
      AGENTTEAM_GATEWAY_WORKSPACE_IDENTITY_RESOLVER_URL: http://agentteam-workspace-default:3001/internal/meshxruntime/workspace-route-identity
    depends_on:
      agentteam-central:
        condition: service_healthy
      agentteam-workspace-default:
        condition: service_healthy
      agentteam-workspace-large-a:
        condition: service_healthy
    healthcheck: *agentteam-healthcheck
    restart: unless-stopped

  agentteam-web:
    <<: *agentteam-web-image
    restart: unless-stopped

  agentteam-ingress:
    image: nginx:1.27-alpine
    depends_on:
      agentteam-gateway:
        condition: service_healthy
      agentteam-web:
        condition: service_started
    ports:
      - "127.0.0.1:${AGENTTEAM_DELIVERY_SPLIT_PORT:-28083}:80"
    configs:
      - source: agentteam-ingress-nginx
        target: /etc/nginx/conf.d/default.conf
        mode: 0444
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O- http://127.0.0.1/readyz >/dev/null 2>&1"]
      interval: 5s
      timeout: 3s
      retries: 30
      start_period: 20s
    restart: unless-stopped

  agentteam-ops-server:
    image: agentteam-ops-server:delivery-split
    build:
      context: ${AGENTTEAM_OPS_SOURCE_DIR:-${HOME}/work/code/agentteam-ops}
      dockerfile: Dockerfile.server
    environment:
      AGENTTEAM_OPS_HTTP_ADDRESS: :3002
      AGENTTEAM_OPS_STORAGE_DSN: root@tcp(tidb:4000)/agentteam_ops?multiStatements=true&parseTime=true&loc=UTC
      AGENTTEAM_CORE_STORAGE_DSN: root@tcp(tidb:4000)/agentteam_core?multiStatements=true&parseTime=true&loc=UTC
      AGENTTEAM_MAAS_DATABASE_DSN: root@tcp(tidb:4000)/agentteam_maas?multiStatements=true&parseTime=true&loc=UTC
      AGENTTEAM_OPS_AUTH_ISSUER: http://localhost:${AGENTTEAM_DELIVERY_SPLIT_OPS_PORT:-28087}/local-oidc
      AGENTTEAM_OPS_AUTH_CLIENT_ID: agentteam-ops-local
      AGENTTEAM_OPS_AUTH_CLIENT_SECRET: agentteam-ops-local-secret
      AGENTTEAM_OPS_AUTH_REDIRECT_URI: http://localhost:${AGENTTEAM_DELIVERY_SPLIT_OPS_PORT:-28087}/oidc-callback
      AGENTTEAM_ADMIN_BASE_URL: http://agentteam-central:3001
      AGENTTEAM_ADMIN_SERVICE_TOKEN: agentteam-local-admin-token
      MESHX_ADMIN_BASE_URL: http://agentteam-workspace-default:3001
      MESHX_ADMIN_SERVICE_TOKEN: meshx-local-admin-token
      AGENTTEAM_MAAS_BASE_URL: http://agentteam-maas:8080
      AGENTTEAM_MAAS_INTERNAL_BEARER_TOKEN: agentteam-maas-local-internal-token
      AGENTTEAM_MEDIA_OBJECT_STORAGE_DRIVER: disabled
    depends_on:
      agentteam-schema:
        condition: service_completed_successfully
      agentteam-maas:
        condition: service_healthy
      agentteam-gateway:
        condition: service_healthy
      agentteam-central:
        condition: service_healthy
      agentteam-workspace-default:
        condition: service_healthy
    restart: unless-stopped

  agentteam-ops-seed:
    image: mysql:8.4
    environment:
      # Login: admin@agentteam.local / agentteam-local-admin (local development only).
      OPS_ADMIN_PASSWORD_HASH: '$$2a$$10$$6kL87NtF6LcIp5U1gND3tOuudx6Pita/74nONyWs/oJ8qAircBRc2'
    depends_on:
      agentteam-ops-server:
        condition: service_started
    entrypoint: ["/bin/sh", "-ec"]
    command:
      - |
        attempts=0
        until mysql --protocol=TCP --host=tidb --port=4000 --user=root --ssl-mode=DISABLED --database=agentteam_ops --execute='SELECT 1 FROM users LIMIT 1' >/dev/null 2>&1; do
          attempts=$$((attempts + 1))
          if [ "$$attempts" -ge 30 ]; then
            echo 'agentteam-ops users schema did not become ready' >&2
            exit 1
          fi
          sleep 2
        done
        mysql --protocol=TCP --host=tidb --port=4000 --user=root --ssl-mode=DISABLED --database=agentteam_ops --execute="
          INSERT IGNORE INTO users (
            id, email, name, display_name, avatar_url, password_hash, role, status, created_at, updated_at
          ) VALUES (
            'opsusr_local_admin', 'admin@agentteam.local', 'Local Admin', 'Local Admin', '',
            '$${OPS_ADMIN_PASSWORD_HASH}', 'admin', 'active', UTC_TIMESTAMP(), UTC_TIMESTAMP()
          );
        "
    restart: "no"

  agentteam-ops-web:
    image: agentteam-ops-web:delivery-split
    build:
      context: ${AGENTTEAM_OPS_SOURCE_DIR:-${HOME}/work/code/agentteam-ops}
      dockerfile: Dockerfile.web
    depends_on:
      agentteam-ops-seed:
        condition: service_completed_successfully
      agentteam-gateway:
        condition: service_healthy
    ports:
      - "127.0.0.1:${AGENTTEAM_DELIVERY_SPLIT_OPS_PORT:-28087}:80"
    configs:
      - source: agentteam-ops-nginx
        target: /etc/nginx/conf.d/default.conf
        mode: 0444
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O- http://127.0.0.1/readyz >/dev/null 2>&1"]
      interval: 5s
      timeout: 3s
      retries: 30
      start_period: 20s
    restart: unless-stopped

configs:
  # RFC 8032 Ed25519 test vector; local development only, never use in production.
  agentteam-maas-capability-key:
    content: |
      {"key_id":"agentteam-local-dev","public_key":"11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=","private_key":"nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2DXWpgBgrEKt9VL/tPJZAc6DuFy89qmIyWvAhpo9wdRGg=="}

  org-credentials-key:
    content: "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="
  app-credentials-key:
    content: "YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODk="

  agentteam-ingress-nginx:
    content: |
      map $$http_upgrade $$connection_upgrade {
          default upgrade;
          "" close;
      }

      # Docker service IPs change when Compose recreates containers.
      resolver 127.0.0.11 valid=5s ipv6=off;
      resolver_timeout 3s;

      upstream agentteam_gateway {
          zone agentteam_gateway 64k;
          server agentteam-gateway:3001 resolve;
      }

      upstream agentteam_web {
          zone agentteam_web 64k;
          server agentteam-web:80 resolve;
      }

      server {
          listen 80;
          server_name _;
          client_max_body_size 100m;

          # /login is a Web SPA route. Keep the exact match ahead of the
          # broader AGS login prefix below; /login/device/* and
          # /login/oauth/* must still reach the Gateway.
          location = /login {
              proxy_pass http://agentteam_web;
          }

          # All API, realtime, raw repository, Git smart-HTTP, avatar, and
          # service health traffic must cross the final workspace Gateway.
          location ~ ^/(api|internal|daemon|socket\.io|avatars|login|auth)(/|$$) {
              proxy_http_version 1.1;
              proxy_set_header Host $$host;
              proxy_set_header X-Real-IP $$remote_addr;
              proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $$scheme;
              proxy_set_header Upgrade $$http_upgrade;
              proxy_set_header Connection $$connection_upgrade;
              proxy_read_timeout 90s;
              proxy_pass http://agentteam_gateway;
          }

          location ~ ^/[^/]+/[^/]+\.git(?:/|$$) {
              proxy_http_version 1.1;
              proxy_set_header Host $$host;
              proxy_set_header X-Real-IP $$remote_addr;
              proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $$scheme;
              proxy_pass http://agentteam_gateway;
          }

          location ~ ^/[^/]+/[^/]+/raw/ {
              proxy_http_version 1.1;
              proxy_set_header Host $$host;
              proxy_set_header X-Real-IP $$remote_addr;
              proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $$scheme;
              proxy_pass http://agentteam_gateway;
          }

          location = /healthz {
              proxy_pass http://agentteam_gateway;
          }

          location = /readyz {
              proxy_pass http://agentteam_gateway;
          }

          location / {
              proxy_pass http://agentteam_web;
          }
      }

  agentteam-ops-nginx:
    content: |
      resolver 127.0.0.11 valid=5s ipv6=off;
      resolver_timeout 3s;

      upstream agentteam_ops_server {
          zone agentteam_ops_server 64k;
          server agentteam-ops-server:3002 resolve;
      }

      upstream agentteam_gateway {
          zone agentteam_gateway 64k;
          server agentteam-gateway:3001 resolve;
      }

      server {
          listen 80;
          server_name _;
          root /usr/share/nginx/html;
          index index.html;
          client_max_body_size 100m;

          location ^~ /api/ops/ {
              proxy_http_version 1.1;
              proxy_set_header Host $$host;
              proxy_set_header X-Real-IP $$remote_addr;
              proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $$scheme;
              proxy_pass http://agentteam_ops_server;
          }

          location ~ ^/(api|internal|daemon|socket\.io)(/|$$) {
              proxy_http_version 1.1;
              proxy_set_header Host $$host;
              proxy_set_header X-Real-IP $$remote_addr;
              proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
              proxy_set_header X-Forwarded-Proto $$scheme;
              proxy_pass http://agentteam_gateway;
          }

          location = /healthz {
              proxy_pass http://agentteam_ops_server;
          }

          location = /readyz {
              proxy_pass http://agentteam_ops_server;
          }

          location / {
              try_files $$uri $$uri/ /index.html;
          }
      }

volumes:
  meshx-data-default:
  meshx-data-large-a:
  tidb-data:
