name: nodedc-platform

# This is intentionally an overlay, not a provider service. It is composed
# with docker-compose.platform-http.yml by the canonical deploy runner.
services:
  external-data-plane-postgres:
    image: ${EXTERNAL_DATA_PLANE_TIMESCALE_IMAGE:-timescale/timescaledb-ha:pg16.14-ts2.28.2-all}
    restart: unless-stopped
    env_file:
      - ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
    environment:
      POSTGRES_DB: ${EXTERNAL_DATA_PLANE_PG_DB:-nodedc_data_plane}
      POSTGRES_USER: ${EXTERNAL_DATA_PLANE_PG_USER:-nodedc_data_plane}
      POSTGRES_PASSWORD: ${EXTERNAL_DATA_PLANE_PG_PASS:?external data plane database password required}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      interval: 30s
      timeout: 5s
      retries: 5
      start_period: 30s
    volumes:
      - external-data-plane-database:/home/postgres/pgdata/data
    networks:
      - external-data-plane

  external-data-plane:
    image: nodedc/external-data-plane:local
    restart: unless-stopped
    env_file:
      - ${NODEDC_SYNOLOGY_ENV_FILE:-.env.synology}
    environment:
      NODE_ENV: production
      PORT: 18106
      EXTERNAL_DATA_PLANE_DATABASE_URL: postgresql://${EXTERNAL_DATA_PLANE_PG_USER:-nodedc_data_plane}:${EXTERNAL_DATA_PLANE_PG_PASS:?external data plane database password required}@external-data-plane-postgres:5432/${EXTERNAL_DATA_PLANE_PG_DB:-nodedc_data_plane}
      EXTERNAL_DATA_PLANE_DATABASE_POOL_SIZE: ${EXTERNAL_DATA_PLANE_DATABASE_POOL_SIZE:-10}
      EXTERNAL_DATA_PLANE_RAW_RETENTION_DAYS: ${EXTERNAL_DATA_PLANE_RAW_RETENTION_DAYS:-14}
      EXTERNAL_DATA_PLANE_MAX_BATCH_BYTES: ${EXTERNAL_DATA_PLANE_MAX_BATCH_BYTES:-5242880}
      EXTERNAL_DATA_PLANE_MAX_FACTS_PER_PUBLISH: ${EXTERNAL_DATA_PLANE_MAX_FACTS_PER_PUBLISH:-5000}
      EXTERNAL_DATA_PLANE_MAX_ATTRIBUTES_BYTES_PER_FACT: ${EXTERNAL_DATA_PLANE_MAX_ATTRIBUTES_BYTES_PER_FACT:-65536}
      EXTERNAL_DATA_PLANE_MAX_PATCH_OPERATIONS: ${EXTERNAL_DATA_PLANE_MAX_PATCH_OPERATIONS:-500}
      EXTERNAL_DATA_PLANE_MAX_PATCH_BYTES: ${EXTERNAL_DATA_PLANE_MAX_PATCH_BYTES:-262144}
      EXTERNAL_DATA_PLANE_PATCH_RETENTION_MS: ${EXTERNAL_DATA_PLANE_PATCH_RETENTION_MS:-3600000}
      EXTERNAL_DATA_PLANE_RECEIPT_RETENTION_MS: ${EXTERNAL_DATA_PLANE_RECEIPT_RETENTION_MS:-604800000}
      EXTERNAL_DATA_PLANE_RETENTION_DELETE_LIMIT: ${EXTERNAL_DATA_PLANE_RETENTION_DELETE_LIMIT:-10000}
      EXTERNAL_DATA_PLANE_STREAM_HEARTBEAT_MS: ${EXTERNAL_DATA_PLANE_STREAM_HEARTBEAT_MS:-20000}
      EXTERNAL_DATA_PLANE_STREAM_POLL_MS: ${EXTERNAL_DATA_PLANE_STREAM_POLL_MS:-1000}
      EXTERNAL_DATA_PLANE_MAX_READER_STREAMS: ${EXTERNAL_DATA_PLANE_MAX_READER_STREAMS:-10}
      EXTERNAL_DATA_PLANE_WRITER_BINDING_MAX_TTL_DAYS: ${EXTERNAL_DATA_PLANE_WRITER_BINDING_MAX_TTL_DAYS:-90}
      EXTERNAL_DATA_PLANE_MAX_FUTURE_SKEW_SECONDS: ${EXTERNAL_DATA_PLANE_MAX_FUTURE_SKEW_SECONDS:-300}
      EXTERNAL_DATA_PLANE_RETENTION_SWEEP_MS: ${EXTERNAL_DATA_PLANE_RETENTION_SWEEP_MS:-3600000}
      # Migration-only shared-token routes stay closed in a canonical install.
      EXTERNAL_DATA_PLANE_LEGACY_INTAKE_ENABLED: ${EXTERNAL_DATA_PLANE_LEGACY_INTAKE_ENABLED:-false}
      NODEDC_INTERNAL_ACCESS_TOKEN: ${NODEDC_INTERNAL_ACCESS_TOKEN:-}
      # Legacy manual one-time issuance uses this EDP-only bearer. Managed
      # Engine ensure/revoke never accepts it and uses the Ed25519 boundary
      # below. Neither credential is exposed to a graph, MCP client or provider.
      EXTERNAL_DATA_PLANE_PROVISIONER_TOKEN_FILE: /run/nodedc-secrets/external-data-plane-provisioner-token
      # Disabled until the atomic native NDC L2 ensure-grant control-plane
      # operation is present. This legacy API returns plaintext capabilities;
      # a root/UI transfer is emergency diagnostics only.
      EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED: ${EXTERNAL_DATA_PLANE_PROVISIONING_ENABLED:-false}
      # Digest-only, idempotent writer-binding ensure path. Enable only after
      # Engine owns the matching private key and the public trust file exists.
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONING_ENABLED:-false}
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_PUBLIC_KEY_FILE: /run/nodedc-trust/engine-managed-provisioner/public-key.pem
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_SERVICE_ID:-nodedc-engine}
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_KEY_ID:-engine-edp-managed-provisioner-v1}
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_AUDIENCE:-nodedc-external-data-plane.managed-provisioning.v1}
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_MAX_SKEW_SECONDS: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_MAX_SKEW_SECONDS:-60}
      EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES: ${EXTERNAL_DATA_PLANE_MANAGED_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES:-10000}
      # Foundry owns a separate managed-provisioner identity. It submits only
      # a consumer target digest and Data Product id; EDP resolves the active
      # writer scope internally and returns no provider identity.
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONING_ENABLED: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONING_ENABLED:-true}
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_PUBLIC_KEY_FILE: /run/nodedc-trust/foundry-managed-provisioner/public-key.pem
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_SERVICE_ID:-nodedc-module-foundry}
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_KEY_ID:-foundry-edp-managed-provisioner-v1}
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_AUDIENCE:-nodedc-external-data-plane.managed-provisioning.v1}
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_MAX_SKEW_SECONDS: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_MAX_SKEW_SECONDS:-60}
      EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES: ${EXTERNAL_DATA_PLANE_FOUNDRY_PROVISIONER_REPLAY_CACHE_MAX_ENTRIES:-10000}
    volumes:
      - type: bind
        source: /volume1/docker/nodedc-platform/secrets/external-data-plane-provisioner/token
        target: /run/nodedc-secrets/external-data-plane-provisioner-token
        read_only: true
        bind:
          create_host_path: false
      - type: bind
        source: /volume1/docker/nodedc-platform/trust/engine-managed-provisioner
        target: /run/nodedc-trust/engine-managed-provisioner
        read_only: true
        bind:
          create_host_path: false
      - type: bind
        source: /volume1/docker/nodedc-platform/trust/foundry-managed-provisioner
        target: /run/nodedc-trust/foundry-managed-provisioner
        read_only: true
        bind:
          create_host_path: false
    expose:
      - "18106"
    ports:
      - "${EXTERNAL_DATA_PLANE_HOST_BIND:-127.0.0.1:18106}:18106"
    depends_on:
      external-data-plane-postgres:
        condition: service_healthy
    healthcheck:
      test:
        - CMD
        - node
        - -e
        - "fetch('http://127.0.0.1:18106/healthz').then((response) => process.exit(response.ok ? 0 : 1)).catch(() => process.exit(1))"
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
    networks:
      - engine
      - external-data-plane

networks:
  engine:
    external: true
    name: ${NODEDC_ENGINE_DOCKER_NETWORK:-nodedc-demo_default}
  external-data-plane:
    internal: true

volumes:
  external-data-plane-database:
