Skip to content

Commit 712f986

Browse files
committed
feat(deploy): make HF publish reproducible from a clean clone
1 parent 7d55c73 commit 712f986

5 files changed

Lines changed: 1070 additions & 21 deletions

File tree

‎DEPLOY.md‎

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,74 @@ The public Space is <https://liovina-nl-sql.hf.space>, but it is not currently a
44
live demo. The Hugging Face API reports `PAUSED` / `Flagged as abusive`
55
(`Cloudflared`) since 2026-07-22. Restoring or retiring the deployment is an
66
owner-authorized closure action; the policy below describes the safe publish
7-
path if restoration is chosen.
7+
path if restoration is chosen. **Do not re-deploy under an active flag** — wait
8+
until Hugging Face clears the pause, then publish once to prune strays.
9+
10+
## Deploy script (tracked)
11+
12+
The clean-clone-safe entry point is **`scripts/deploy_hf.py`**. It is committed,
13+
needs no local kitchen paths, and authenticates only from the process
14+
environment:
15+
16+
| Variable | Role |
17+
|---|---|
18+
| `HF_TOKEN` | Hugging Face write token for `HfApi` |
19+
| `MISTRAL_API_KEY` | Value written as the Space secret (never read from a file path) |
20+
21+
Remote mutation requires an **explicit** `--apply`. With no flags the script
22+
prints usage and exits nonzero without touching the network.
23+
24+
### Local verification (no network, no credentials)
25+
26+
```powershell
27+
# From a clean clone / this repo root:
28+
.venv/Scripts/python.exe scripts/deploy_hf.py --self-test
29+
.venv/Scripts/python.exe scripts/deploy_hf.py --dry-run
30+
31+
# Focused tests (also no network):
32+
.venv/Scripts/python.exe -m pytest tests/scripts/test_deploy_hf.py -q
33+
```
34+
35+
### Publish (owner-authorized only)
36+
37+
```powershell
38+
$env:HF_TOKEN = "<write token>" # process env only
39+
$env:MISTRAL_API_KEY = "<mistral key>" # process env only
40+
.venv/Scripts/python.exe scripts/deploy_hf.py --apply
41+
```
42+
43+
On `--apply` the script: runs the local safety gate → requires both env vars →
44+
skips `create_repo` when the Space already exists → sets the Space secret →
45+
uploads only the filtered tracked set → uploads a generated HF-frontmatter
46+
`README.md` → prunes remote files that are no longer in the expected set.
847

948
## Deploy policy: only what git tracks, then prune
1049

1150
The deploy publishes **exactly the files git tracks** (`git ls-files`), never the
1251
working tree. Working-kitchen files — internal notes, audits, `.env`, scratch
1352
reports — are gitignored and a repo-hygiene CI gate fails if any slip into the
1453
index, so they cannot reach the Space. The upload also **prunes** the Space: files
15-
removed from the repo are deleted on the host, not left behind from an earlier push.
54+
removed from the repo (or excluded from the publish set) are deleted on the host,
55+
not left behind from an earlier push.
1656

17-
The deploy script itself (`.deploy_hf.py`) is repo-local (it reads a local token
18-
and Mistral key by path) and is intentionally not committed. It:
57+
`scripts/deploy_hf.py` does the following:
1958

2059
1. computes the file set from `git ls-files` minus a publish-exclude list
2160
(`tests/`, `.github/`, `docs/research/`, `reviews/`, `eval/baselines/`,
2261
`eval/reports/`, `scripts/autotune/`);
23-
2. drops anything the hygiene detector flags;
24-
3. scans the bytes of the resulting set for tunnel clients and refuses to upload if any
25-
are found (see below);
26-
4. uploads via `huggingface_hub`, then prunes remote files no longer present;
27-
5. supports `--self-test` (a gate that runs before upload) and `--dry-run`.
62+
2. drops anything the hygiene detector (`scripts/check_repo_hygiene.py`) flags;
63+
3. excludes the repository `README.md` from bulk upload (an HF-frontmatter
64+
version is generated, tunnel-marker terms are sanitized, and it is uploaded
65+
separately);
66+
4. scans the **bytes** of every publish file for tunnel clients and refuses to
67+
upload if any are found (see below);
68+
5. treats generated `README.md`, tracked `Dockerfile`, and `.gitattributes`
69+
(when present) as the expected remote set, then prunes remote strays;
70+
6. supports `--self-test` and `--dry-run` as strictly local modes.
71+
72+
Source-only deployment metadata (`DEPLOY.md`, `docs/PROJECT_CLOSURE.md`, and
73+
`scripts/deploy_hf.py`) is also excluded from the app host: these files document
74+
the tunnel signatures and are not runtime dependencies.
2875

2976
The Space hosts the **app**, not the research record. Eval reports and baselines are
3077
260 files / 31 MB the app never reads; they stay in this repo, which is public, and are
@@ -41,17 +88,20 @@ Space was paused under `rule: Cloudflared` minutes after the first deploy that c
4188
those notebooks.
4289

4390
So the harness is excluded by path, **and** the deploy scans every file it is about to
44-
upload for tunnel markers (`cloudflared`, `ngrok`, `localtunnel`, …) and aborts on a hit —
45-
the path rule only knows the files that broke once, the content scan covers the rest.
46-
`--self-test` proves the scanner is not vacuous by planting a marker in a throwaway file
47-
and requiring it to be caught.
91+
upload for tunnel markers (`cloudflared`, `trycloudflare`, `ngrok`, `localtunnel`,
92+
`bore.pub`, `serveo.net`, `pinggy`, `loca.lt`) in binary chunks with overlap, and aborts
93+
on a hit — the path rule only knows the files that broke once, the content scan covers
94+
the rest. `--self-test` proves the scanner is not vacuous by planting a marker in a
95+
throwaway file and requiring it to be caught.
4896

4997
## What ships to the Space
5098

5199
The Space is **SQLite-only** — `psycopg` is pruned from its `requirements.txt`, so
52100
the Postgres path (see README) is not present there; it is for local/CI use. The
53-
Space carries a subset of the BIRD Mini-Dev databases (the three largest exceed
54-
GitHub's 100 MB/file limit and are gitignored):
101+
tracked root `Dockerfile` (`python:3.13-slim`, `libgomp1`, `PYTHONPATH=/app/src`,
102+
Streamlit on `0.0.0.0:7860`) is part of the publish set. The Space carries a subset
103+
of the BIRD Mini-Dev databases (the three largest exceed GitHub's 100 MB/file limit
104+
and are gitignored):
55105

56106
| DB | size | shipped |
57107
|---|---|---|
@@ -75,10 +125,17 @@ the app doesn't re-embed schema chunks on cold start.
75125

76126
## Secret
77127

78-
The Space needs one secret — `MISTRAL_API_KEY` — set in the Space's Settings →
79-
Variables and secrets. `pydantic-settings` reads it from the environment.
128+
The Space needs one secret — `MISTRAL_API_KEY` — set via `scripts/deploy_hf.py
129+
--apply` from the process environment (or manually in the Space's Settings →
130+
Variables and secrets). `pydantic-settings` reads it from the environment on the
131+
host. The deploy script never reads or prints local credential file paths.
80132

81133
## Updating
82134

83-
Re-run the local deploy script; it re-uploads the tracked set and prunes. CI (tests
84-
+ repo-hygiene) gates `main` before any deploy is worth doing.
135+
1. Confirm the Space is no longer paused/flagged (owner + HF moderation).
136+
2. Run `--self-test` / `--dry-run` locally.
137+
3. Run `--apply` with `HF_TOKEN` and `MISTRAL_API_KEY` in the environment only.
138+
139+
CI (tests + repo-hygiene) gates `main` before any deploy is worth doing.
140+
Pushing to GitHub does **not** update the Space; publish is a separate
141+
`--apply` step.

‎Dockerfile‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Hugging Face Docker Space runtime for the Streamlit NL→SQL demo.
2+
# SQLite-only (psycopg is pruned from requirements.txt).
3+
FROM python:3.13-slim
4+
5+
WORKDIR /app
6+
7+
# onnxruntime (via chromadb) needs libgomp on slim images.
8+
RUN apt-get update \
9+
&& apt-get install -y --no-install-recommends libgomp1 \
10+
&& rm -rf /var/lib/apt/lists/*
11+
12+
COPY requirements.txt .
13+
RUN pip install --no-cache-dir -r requirements.txt
14+
15+
COPY . .
16+
17+
ENV PYTHONPATH=/app/src
18+
19+
EXPOSE 7860
20+
21+
CMD ["streamlit", "run", "app/streamlit_app.py", \
22+
"--server.port=7860", \
23+
"--server.address=0.0.0.0", \
24+
"--server.headless=true", \
25+
"--browser.gatherUsageStats=false"]

‎scripts/check_repo_hygiene.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
All of these are ignored via ``.gitignore`` now, but ``git add -f`` or a future
99
``.gitignore`` edit could silently re-introduce them. This gate fails CI the
1010
moment any such path shows up in ``git ls-files``, so the cleanup can't rot.
11-
The HF Space is guarded separately: ``.deploy_hf.py`` uploads only tracked
12-
files and prunes strays from the Space.
11+
The HF Space is guarded separately: ``scripts/deploy_hf.py`` uploads only
12+
tracked files and prunes strays from the Space.
1313
1414
Usage::
1515

0 commit comments

Comments
 (0)