File size: 7,412 Bytes
88d2f2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# =============================================================================
# PolyglotAlpha β€” Hugging Face Spaces deploy image (W23, mock-only)
# =============================================================================
# Single container that ships the FastAPI backend + Next.js production build
# behind a tiny nginx reverse proxy on port 7860 (HF Spaces only exposes one
# port externally). DISABLE_LIVE is hard-set so reviewers can't trip an empty
# API key path; the W5 mock-mode fixtures drive the entire demo lifecycle.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Stage 1: build the Next.js production bundle
# -----------------------------------------------------------------------------
FROM node:20-alpine AS ui-build
WORKDIR /app/ui

# Install deps. NOTE: the repo's `package-lock.json` was generated against
# Indeed's internal npm proxy (`npm.artifacts.indeed.tech`) and `npm ci`
# fails with E401 outside that VPN. We deliberately drop the lockfile and
# regenerate it against the public registry so the HF builder (which has
# no Indeed access) succeeds. For a mock-only demo the small version drift
# is acceptable; switch to `npm ci` once a clean public lockfile lands.
# Cold installs take 5-10 min (network bound on 537 MB of node_modules);
# layer caching makes incremental rebuilds <30 s as long as
# `ui/package.json` doesn't change.
COPY ui/package.json ./
RUN npm install --no-audit --no-fund \
        --registry=https://registry.npmjs.org/ \
        --legacy-peer-deps

# Copy the rest of the UI source and build. `NEXT_PUBLIC_DISABLE_LIVE` is
# inlined into the client bundle at build time, so this MUST be set here β€”
# changing it later via runtime env has no effect on the compiled JS.
COPY ui/ ./

# `next.config.mjs` AND `lib/api.ts` both hardcode a `|| "http://localhost:8000"`
# fallback for `NEXT_PUBLIC_API_BASE`, which would break in the browser when
# the page is served from huggingface.co (the fallback would point at the
# reviewer's own laptop, not the HF Space). Patch both to fall back to an
# empty string so api.ts produces same-origin URLs (`/events`,
# `/trigger/event`, ...) that nginx routes to uvicorn :8000.
# Use `,` as sed delimiter to avoid clashing with the JS `||` operator
# (using `|` here causes sed to see four separator chars in one expression).
RUN sed -i 's,http://localhost:8000,,g' next.config.mjs lib/api.ts

# IMPORTANT: nginx routes `/api/*` to FastAPI (stripping the `/api` prefix);
# everything else goes to Next.js. So all client-side fetches must be
# prefixed `/api/...` to hit the backend instead of being captured by
# the Next.js page router (which has its own `/events/[id]`, `/trigger`,
# `/operators`, etc. routes that would otherwise collide with the API).
ENV NEXT_PUBLIC_API_BASE=/api
ENV NEXT_PUBLIC_DISABLE_LIVE=true
ENV NODE_ENV=production
RUN npm run build

# -----------------------------------------------------------------------------
# Stage 2: runtime image
# -----------------------------------------------------------------------------
FROM python:3.12-slim AS runtime
WORKDIR /app

# System packages: nginx (reverse proxy), curl (entrypoint pre-seed), nodejs
# (Next.js `next start`). `ca-certificates` keeps outbound HTTPS happy if a
# reviewer pokes a live-mode endpoint we forgot to lock down.
RUN apt-get update && apt-get install -y --no-install-recommends \
        nginx \
        curl \
        ca-certificates \
        gnupg \
    && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y --no-install-recommends nodejs \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Backend Python deps β€” install BEFORE copying the app so layer cache survives
# a code edit. We use the pruned mock-only requirements (no torch / comet /
# sentence-transformers) so the HF builder doesn't OOM on the ML wheels.
COPY deploy/requirements-mock.txt /tmp/requirements-mock.txt
RUN pip install --no-cache-dir --upgrade pip \
    && pip install --no-cache-dir -r /tmp/requirements-mock.txt

# Backend code. `pip install -e .` registers the package on the import path;
# pyproject.toml carries no `dependencies` so this is essentially `develop`
# mode without re-installing anything heavy.
COPY pyproject.toml ./
COPY polyglot_alpha/ ./polyglot_alpha/
COPY scripts/ ./scripts/
COPY contracts/out/ ./contracts/out/
# NOTE: the top-level `corpus/` directory (faiss index, polymarket dumps,
# 1.1 GB of training data) is intentionally NOT copied β€” it's only consumed
# by the live RAG / pattern-analysis paths which are dead code in
# DISABLE_LIVE mode. The bundled few-shot exemplars live in the
# `polyglot_alpha.corpus` PYTHON package and ship via the polyglot_alpha
# copy above.
RUN pip install --no-cache-dir --no-deps -e .

# Frontend artifact β€” copy the .next build + node_modules from stage 1. We
# pull node_modules whole because `next start` needs the runtime deps to
# resolve at request time (Next 15 + react-server bundling).
COPY --from=ui-build /app/ui/.next ./ui/.next
# Skip /app/ui/public β€” repo doesn't have one; Next.js handles missing dir fine.
COPY --from=ui-build /app/ui/package.json ./ui/package.json
COPY --from=ui-build /app/ui/node_modules ./ui/node_modules
COPY --from=ui-build /app/ui/next.config.mjs ./ui/next.config.mjs

# Pre-seed SQLite with empty tables + the few-shot exemplars so the first
# reviewer click doesn't hit an empty `events` / `few_shot_exemplars` table.
# `init_db()` is idempotent; the ingest script is too (skips on conflict).
RUN python -c "from polyglot_alpha.persistence import init_db; init_db()" \
    && (python scripts/ingest_few_shots.py || true)

# Reverse-proxy + process supervisor configs.
COPY deploy/nginx.conf /etc/nginx/nginx.conf
COPY deploy/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Runtime env: lock the deploy to mock-only and keep concurrency tame so the
# 2 vCPU HF free tier doesn't thrash under a small audience.
ENV DISABLE_LIVE=true
ENV DEFAULT_EVENT_MODE=mock
ENV LLM_BACKEND=mock
# Dummy operator key so the judge_panel_client + chain modules pass their
# module-level import-time validation. Mock mode short-circuits BEFORE any
# real tx is sent, so this key is NEVER used to sign anything. We pick the
# secp256k1 generator-point private key (literally "1") which is publicly
# known β€” making it OBVIOUS to any auditor that this is a placeholder.
ENV HACKATHON_WALLET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000001
ENV ARC_TESTNET_RPC=https://rpc.testnet.arc.network
# Same fake-key idempotency for the seeder slot derivation
ENV ALPHA_WALLET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000002
ENV BRAVO_WALLET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000003
ENV CHARLIE_WALLET_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000004
ENV LIFECYCLE_MAX_CONCURRENCY=2
ENV PYTHONUNBUFFERED=1
ENV PORT=7860
# DB lives next to the app by default. If HF persistent storage is enabled
# in the Space settings, the entrypoint moves the seeded DB to `/data` and
# overrides `DATABASE_URL` at boot so the events table survives sleeps.
ENV PYTHONDONTWRITEBYTECODE=1

EXPOSE 7860

CMD ["/usr/local/bin/entrypoint.sh"]