# Backend API Dockerfile
# Multi-stage: keep runtime image small and predictable.
# Multi-arch: builds natively on arm64 (Apple Silicon, AWS Graviton) and
# amd64 (S15 #17668 publishes the EKS API image as a two-platform index).

FROM python:3.14-slim@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS builder

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    PIP_CACHE_DIR=/root/.cache/pip \
    VIRTUAL_ENV=/opt/venv

# Build deps only (not shipped in final image).
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    curl \
    libpq-dev \
    libffi-dev \
    libssl-dev \
    pkg-config \
    libxmlsec1-dev \
    xmlsec1 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /build

RUN python -m venv "${VIRTUAL_ENV}"
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"

COPY apps/backend/requirements.runtime.txt ./requirements.runtime.txt
# Committed cp314 biscuit wheels for BOTH aarch64 and x86_64, resolved by
# `--find-links vendor/wheels` (relative to this WORKDIR /build). Must land
# before the pip install below.
COPY vendor/wheels ./vendor/wheels

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --upgrade pip && \
    pip install -r requirements.runtime.txt


FROM python:3.14-slim@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456 AS runtime

LABEL maintainer="workweaver-prod"
LABEL description="Backend API for Workweaver"

ARG WORKWEAVER_BUILD_REF=local

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    VIRTUAL_ENV=/opt/venv \
    WORKWEAVER_SOURCE_VERSION=${WORKWEAVER_BUILD_REF}

# Runtime deps (keep minimal).
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    libpq5 \
    libxmlsec1-openssl \
    xmlsec1 \
    && rm -rf /var/lib/apt/lists/*

# Verify xmlsec1 is functional (SAML signature verification backend).
RUN xmlsec1 --version

COPY --from=builder "${VIRTUAL_ENV}" "${VIRTUAL_ENV}"
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"

# Set working directory to repository root inside the image.
WORKDIR /usr/src/app

LABEL org.opencontainers.image.revision="${WORKWEAVER_BUILD_REF}"

# Copy backend, shared code, admin portal SPA, and canonical Zoho registry seed from the repository root context.
# The admin SPA shell lives at apps/dashboard/admin/ and is copied into the
# expected apps/admin-portal/ path for backward compatibility with
# main.py's _ADMIN_PORTAL_DIR reference.
RUN mkdir -p apps/backend apps/admin-portal skills/business/zoho-cli docs/reference/runtime tools/portability
COPY --chmod=755 apps/backend/ ./apps/backend/
COPY --chmod=755 apps/shared/ ./apps/shared/
COPY --chmod=755 tools/portability/ ./tools/portability/
COPY --chmod=644 apps/dashboard/admin/ ./apps/admin-portal/
COPY --chmod=644 skills/business/zoho-cli/registry.json ./skills/business/zoho-cli/registry.json
# Repo skills tree consumed by factory-worker catalog resolve and typed-Skill
# runtime: skills/index.json (generated catalog at /usr/src/app/skills/index.json),
# aliases.json, and the business/ + core/ packages the catalog exposes.
# Same tree Dockerfile.lambda already ships (#12629). Without index.json the
# worker execute_failed (t05: missing /usr/src/app/skills/index.json).
COPY --chmod=755 skills/index.json ./skills/index.json
COPY --chmod=755 skills/aliases.json ./skills/aliases.json
COPY --chmod=755 skills/business/ ./skills/business/
COPY --chmod=755 skills/core/ ./skills/core/
# Canonical capability registry, loaded at runtime by the entitlement resolver
# (TenantEntitlementResolver -> load_capability_registry). docs/ is otherwise
# .dockerignore'd; the negation in .dockerignore re-includes this one file.
# WORKDIR here is /usr/src/app, so this matches the loader's repo-relative path.
COPY --chmod=644 docs/reference/runtime/capabilities.registry.yaml ./docs/reference/runtime/capabilities.registry.yaml

# Versioned doctrine packs, loaded at runtime by the operating-doctrine
# loader (load_kernel_pack / load_company_default_pack). docs/ is otherwise
# .dockerignore'd; the negation re-includes the packs. Without them
# /api/v1/doctrine and /api/v1/doctrine/diff 500 (#17483). No --chmod here:
# it applies recursively, so --chmod=644 strips the directory's traverse
# bit and the packs become unreadable in-image (proven on the #17483 build).
# WORKDIR here is /usr/src/app, so this matches the loader's repo-relative path.
COPY docs/doctrine/ ./docs/doctrine/

# Keep the revision available to both the health contract and a fresh process
# after restart.  Deploy proof reads this value; it is never caller supplied.
RUN printf '%s\n' "${WORKWEAVER_BUILD_REF}" > /usr/src/app/.workweaver-source-version

# Set PYTHONPATH to include the repository-root package layout.
ENV PYTHONPATH=/usr/src/app:/usr/src/app/apps/backend

# Create non-root user.
RUN groupadd -r apiuser && useradd -r -g apiuser apiuser && \
    chown -R apiuser:apiuser /usr/src/app

USER apiuser
WORKDIR /usr/src/app/apps/backend

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
    CMD curl -fsS http://localhost:8000/health || exit 1

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