Image-to-Text
PyTorch
Safetensors
PEFT
English
remote-sensing
satellite-imagery
earth-observation
change-detection
visual-grounding
image-captioning
visual-question-answering
optical-sar-fusion
sar
multimodal
lora
Instructions to use thundercode/SatQuery with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use thundercode/SatQuery with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
release: add docs/DEPLOYMENT.md
Browse files- docs/DEPLOYMENT.md +285 -86
docs/DEPLOYMENT.md
CHANGED
|
@@ -1,126 +1,325 @@
|
|
| 1 |
# Deployment
|
| 2 |
|
| 3 |
-
**Status tags:** `IMPLEMENTED` Β· `VERIFIED` Β· `MEASURED` Β· `OPEN` Β· `DEFERRED` Β· `BLOCKED`.
|
| 4 |
|
| 5 |
-
The live
|
| 6 |
-
The monorepo working copy is **not** the deployed source. This document records the live
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
---
|
| 10 |
|
| 11 |
## 1. Live revisions (VERIFIED)
|
| 12 |
|
|
|
|
|
|
|
| 13 |
| Component | Repository | Visibility | Branch | Revision | Host |
|
| 14 |
|---|---|---|---|---|---|
|
| 15 |
-
| Frontend | `Anish-lab-blip/SatQuery-Frontend` | private | `main` | **`2d7ae53b482d`** | Cloudflare Pages β `satquery.pages.dev` |
|
| 16 |
-
| Backend / orchestrator | `Anish-lab-blip/SatQuery-Backend` | private | `main` | **`89d80eaddec5`** | Render β `satquery-backend-m4yv.onrender.com` |
|
| 17 |
-
| Inference | `Anish-lab-blip/SatQuery-Inference` | private | `main` | **`5a0936ace491`** | Codespace `potential-space-trout-r4ppw969w45j2pvvw`, port 8000 |
|
| 18 |
-
| Public umbrella | `Anish-lab-blip/SatQuery-AI` | **public** | `main` | `3dcabd32da41` | this release home |
|
| 19 |
-
| Monorepo (working copy) | `C:/Users/anish/satquery-ai` | local only | `master` | `9d57aed` | **no remote**; 334 dirty entries |
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
-
## 2.
|
| 25 |
|
| 26 |
-
```json
|
| 27 |
-
{"status":"ok","service":"satquery-orchestrator",
|
| 28 |
-
"tunnel":{"agent_connected":true,"agent_id":"codespaces-fd1038","pending":0,"completed":97},
|
| 29 |
-
"config":{"codespace_name":"potential-space-trout-r4ppw969w45j2pvvw\n","codespace_port":8000,
|
| 30 |
-
"transport_mode":"auto","tunnel_timeout_s":150.0,"wake_timeout_s":120.0,
|
| 31 |
-
"upstream_timeout_s":90.0,"device":"cpu","has_github_token":true}}
|
| 32 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
## 3.
|
| 37 |
|
| 38 |
-
|
|
|
|
| 39 |
|
| 40 |
-
|
|
| 41 |
|---|---|
|
| 42 |
-
|
|
| 43 |
-
|
|
| 44 |
-
|
|
| 45 |
-
|
|
| 46 |
-
|
|
| 47 |
-
|
|
| 48 |
-
|
|
| 49 |
-
|
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
|
|
|
| 54 |
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
| Variable | Purpose |
|
| 58 |
|---|---|
|
| 59 |
-
| `PORT` | platform-assigned; **must be read** |
|
| 60 |
| `SATQUERY_DEVICE` | `cpu` \| `cuda` \| `mps` \| `null`; read **without importing torch** |
|
| 61 |
-
| `SATQUERY_MAX_FILE_BYTES` | per-file cap
|
| 62 |
-
| `SATQUERY_ASSET_ENABLED` / `SATQUERY_ASSET_DIR` | both required for `/v1/assets`; fails closed (503) otherwise |
|
| 63 |
| `SATQUERY_ASSET_MAX_FILES` / `SATQUERY_ASSET_TTL_S` | optional handle capacity / lifetime |
|
| 64 |
|
| 65 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
`npx wrangler pages deploy`.
|
| 69 |
-
- **Backend β Render.** `render.yaml` blueprint; `main.py` exposes `app`.
|
| 70 |
-
- **Inference β Codespace.** `deploy/codespace/serve.py` serves `build_space_app()` on `$PORT`;
|
| 71 |
-
`.devcontainer/` forwards port 8000 and runs the tunnel agent on start via `postStartCommand`.
|
| 72 |
-
- **Repository writes** are performed through the **GitHub Git Data API** (blob β tree β commit β
|
| 73 |
-
`PATCH` ref) with **sha256 byte-verification** of every uploaded blob. Deletions are expressed as
|
| 74 |
-
`sha: null` tree entries. This is used instead of `git push` so each deployed file is verified by
|
| 75 |
-
content hash.
|
| 76 |
|
| 77 |
-
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
therefore **tens of seconds**, and this is documented rather than papered over.
|
| 88 |
|
| 89 |
-
##
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
|---|---|---|
|
| 93 |
-
|
|
| 94 |
-
|
|
| 95 |
-
|
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
-
##
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
1. Cloudflare Pages project name / domain (needed for the deploy command and `robots.txt` sitemap).
|
| 111 |
-
2. Artifacts present, or capabilities honestly ship `available: false`
|
| 112 |
-
than crashes
|
| 113 |
-
3. `HF_TOKEN`
|
| 114 |
-
4. Codespace `.devcontainer/` forwarding `:8000` and starting the tunnel agent.
|
| 115 |
-
5. The five
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
Gradio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Deployment
|
| 2 |
|
| 3 |
+
**Status tags:** `IMPLEMENTED` Β· `VERIFIED` Β· `MEASURED` Β· `OPEN` Β· `DEFERRED` Β· `BLOCKED` Β· `BY DESIGN`.
|
| 4 |
|
| 5 |
+
The live SatQuery AI system runs across **three private repositories** plus one **public umbrella
|
| 6 |
+
repository**. The monorepo working copy is **not** the deployed source. This document records the live
|
| 7 |
+
topology, every environment variable, the deploy mechanics, the cold-start behaviour, the known
|
| 8 |
+
issues, and the platform traps that cost real debugging time.
|
| 9 |
+
|
| 10 |
+
> **The single most important trap in this document.** `deploy/` inside the monorepo is **stale and
|
| 11 |
+
> untracked**. It is **not** the deployed source. Edits must go to the three real repositories.
|
| 12 |
|
| 13 |
---
|
| 14 |
|
| 15 |
## 1. Live revisions (VERIFIED)
|
| 16 |
|
| 17 |
+
Read from the GitHub API during the release reconnaissance.
|
| 18 |
+
|
| 19 |
| Component | Repository | Visibility | Branch | Revision | Host |
|
| 20 |
|---|---|---|---|---|---|
|
| 21 |
+
| Frontend | `Anish-lab-blip/SatQuery-Frontend` | **private** | `main` | **`2d7ae53b482d`** | Cloudflare Pages β `satquery.pages.dev` |
|
| 22 |
+
| Backend / orchestrator | `Anish-lab-blip/SatQuery-Backend` | **private** | `main` | **`89d80eaddec5`** | Render β `satquery-backend-m4yv.onrender.com` |
|
| 23 |
+
| Inference | `Anish-lab-blip/SatQuery-Inference` | **private** | `main` | **`5a0936ace491`** | Codespace `potential-space-trout-r4ppw969w45j2pvvw`, port 8000 |
|
| 24 |
+
| Public umbrella | `Anish-lab-blip/SatQuery-AI` | **public** | `main` | `3dcabd32da41` ("Initial commit") | this release home |
|
| 25 |
+
| Monorepo (working copy) | `C:/Users/anish/satquery-ai` | local only | `master` | `9d57aed` | **no git remote**; 334 dirty entries |
|
| 26 |
|
| 27 |
+
The public umbrella repository previously contained **only a 13-byte README** (`# SatQuery-AI`). It is
|
| 28 |
+
the intended home for this release.
|
| 29 |
|
| 30 |
+
## 2. The four tiers
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
```
|
| 33 |
+
Browser
|
| 34 |
+
β HTTPS
|
| 35 |
+
βΌ
|
| 36 |
+
Cloudflare Pages β satquery.pages.dev (static frontend, 11 pages)
|
| 37 |
+
β HTTPS / JSON β /api/*
|
| 38 |
+
βΌ
|
| 39 |
+
Render β satquery-backend-m4yv.onrender.com (orchestrator / gateway)
|
| 40 |
+
β outbound long-poll POST /tunnel/agent
|
| 41 |
+
βΌ
|
| 42 |
+
GitHub Codespace β FastAPI inference, CPU, port 8000
|
| 43 |
+
β build_space_app()
|
| 44 |
+
βΌ
|
| 45 |
+
specialists: SmolVLM Β· RemoteCLIP Β· MiniLM Β· CROMA Β· STANet
|
| 46 |
+
β
|
| 47 |
+
βΌ
|
| 48 |
+
ResultEnvelope β tunnel β Render β browser
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
```mermaid
|
| 52 |
+
flowchart LR
|
| 53 |
+
U[Browser] -->|HTTPS| CF["Cloudflare Pages<br/>static frontend"]
|
| 54 |
+
CF -->|"HTTPS JSON<br/>/api/health Β· /api/capabilities Β· /api/infer Β· /api/assets"| R["Render<br/>orchestrator / gateway"]
|
| 55 |
+
R -->|"outbound long-poll<br/>POST /tunnel/agent"| C["GitHub Codespace<br/>FastAPI inference :8000"]
|
| 56 |
+
C --> S[(SmolVLM Β· RemoteCLIP<br/>MiniLM Β· CROMA Β· STANet)]
|
| 57 |
+
C -->|ResultEnvelope| R
|
| 58 |
+
R -->|"envelope + error translation"| CF
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## 3. Why each tier exists
|
| 62 |
+
|
| 63 |
+
### 3.1 Cloudflare Pages β the static tier
|
| 64 |
|
| 65 |
+
Serves the frontend. **No backend, no secrets, and no API calls of its own** on the static pages. The
|
| 66 |
+
one exception is the Analyze console, which calls the gateway. Staged by `scripts/stage_pages.mjs` and
|
| 67 |
+
deployed with `npx wrangler pages deploy`.
|
| 68 |
|
| 69 |
+
### 3.2 Render β the gateway
|
| 70 |
|
| 71 |
+
A deliberately **thin, stateless** orchestrator. Its responsibilities, from
|
| 72 |
+
`docs/DEPLOYMENT_ARCHITECTURE.md` Β§2:
|
| 73 |
|
| 74 |
+
| Responsibility | Detail |
|
| 75 |
|---|---|
|
| 76 |
+
| Schema validation | reject malformed requests before they cost inference |
|
| 77 |
+
| Size limits | whole-request body cap, shared with the Codespace |
|
| 78 |
+
| Rate limiting | per-IP count + window β **fairness, NOT a security control** |
|
| 79 |
+
| CORS allowlist | the Pages origin; **never `*`** |
|
| 80 |
+
| Request ids | correlate a request across tiers |
|
| 81 |
+
| Timeouts | sit inside the task budget |
|
| 82 |
+
| Secret custody | holds credentials that must never reach the browser |
|
| 83 |
+
| Error translation | upstream failures β the documented error envelope |
|
| 84 |
+
|
| 85 |
+
It is **not** a model host. It has **no database, no auth, and no queue**.
|
| 86 |
+
|
| 87 |
+
> **Hard rule.** The gateway must **not** retry `POST /api/infer` on its own β a retry would consume
|
| 88 |
+
> inference a second time. The client decides on retry.
|
| 89 |
+
|
| 90 |
+
### 3.3 GitHub Codespace β the inference tier
|
| 91 |
+
|
| 92 |
+
Runs the real inference service: `build_space_app()` from `app/space_app.py`, served by
|
| 93 |
+
`deploy/codespace/serve.py` on `$PORT`, in **CPU mode**. It honours the four-endpoint contract
|
| 94 |
+
(`/v1/health`, `/v1/capabilities`, `/v1/analyze`, `/v1/assets`), imports cheaply without torch, reuses
|
| 95 |
+
`app/serving.py` as the composition root, and **degrades rather than crashes** on absent artifacts.
|
| 96 |
+
|
| 97 |
+
### 3.4 Hugging Face β the model tier
|
| 98 |
+
|
| 99 |
+
Holds the six trained artifacts and the model card. It is **not** the runtime inference host; the
|
| 100 |
+
Codespace resolves the pinned backbones from the Hub at run time.
|
| 101 |
+
|
| 102 |
+
## 4. Why the transport is an outbound tunnel
|
| 103 |
+
|
| 104 |
+
The inference host is a Codespace in a **private** repository. A forwarded port for a private repo
|
| 105 |
+
returns **`302`**, so an inbound-forwarding design cannot work. Instead:
|
| 106 |
|
| 107 |
+
- the Codespace runs `deploy/codespace/tunnel_agent.py`;
|
| 108 |
+
- the agent **dials out** to `POST /tunnel/agent` and long-polls;
|
| 109 |
+
- work is executed against `http://127.0.0.1:8000` **locally**.
|
| 110 |
|
| 111 |
+
This inverts the usual direction: the inference host needs **no inbound firewall hole**. It also means
|
| 112 |
+
the transport is only alive while the agent is polling.
|
| 113 |
+
|
| 114 |
+
**Measured:** `GET /api/health` reported `tunnel.agent_connected: true` with a non-zero `completed`
|
| 115 |
+
counter, and `POST /api/infer {}` returned `422 invalid_request` with the response header
|
| 116 |
+
`x-satquery-transport: tunnel`.
|
| 117 |
+
|
| 118 |
+
## 5. The full live health payload (VERIFIED, probed)
|
| 119 |
+
|
| 120 |
+
```json
|
| 121 |
+
{
|
| 122 |
+
"status": "ok",
|
| 123 |
+
"service": "satquery-orchestrator",
|
| 124 |
+
"tunnel": {
|
| 125 |
+
"agent_connected": true,
|
| 126 |
+
"agent_id": "codespaces-fd1038",
|
| 127 |
+
"pending": 0,
|
| 128 |
+
"completed": 97
|
| 129 |
+
},
|
| 130 |
+
"config": {
|
| 131 |
+
"codespace_name": "potential-space-trout-r4ppw969w45j2pvvw\n",
|
| 132 |
+
"codespace_port": 8000,
|
| 133 |
+
"transport_mode": "auto",
|
| 134 |
+
"tunnel_timeout_s": 150.0,
|
| 135 |
+
"wake_timeout_s": 120.0,
|
| 136 |
+
"upstream_timeout_s": 90.0,
|
| 137 |
+
"device": "cpu",
|
| 138 |
+
"has_github_token": true
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
Note `codespace_name` still carries a **trailing `\n`** β this is **B-02**, cosmetic and `OPEN`; the
|
| 144 |
+
wake path strips it (`_codespace_name()`), so only the health payload reports the raw value.
|
| 145 |
+
|
| 146 |
+
## 6. Environment variables
|
| 147 |
+
|
| 148 |
+
### 6.1 Render (gateway) β measured live values
|
| 149 |
+
|
| 150 |
+
| Variable | Value (live) | Purpose |
|
| 151 |
+
|---|---|---|
|
| 152 |
+
| `CODESPACE_NAME` | `potential-space-trout-r4ppw969w45j2pvvw` | which Codespace to wake |
|
| 153 |
+
| `CODESPACE_PORT` | `8000` | the inference port |
|
| 154 |
+
| `SATQUERY_ALLOWED_ORIGINS` | `https://satquery.pages.dev` | CORS allowlist (never `*`) |
|
| 155 |
+
| `SATQUERY_DEVICE` | `cpu` | device preference |
|
| 156 |
+
| `SATQUERY_TRANSPORT` | `auto` | tunnel first, then forward |
|
| 157 |
+
| `SATQUERY_TUNNEL_TIMEOUT_S` | `150` | how long to wait on the tunnel |
|
| 158 |
+
| `SATQUERY_WAKE_TIMEOUT_S` | `120` | how long to wait for a cold start |
|
| 159 |
+
| `SATQUERY_UPSTREAM_TIMEOUT_S` | `90` | gateway β upstream budget |
|
| 160 |
+
| `GITHUB_TOKEN` | present | Codespace control |
|
| 161 |
+
| `PORT` | platform-supplied | Render's own listen port |
|
| 162 |
+
|
| 163 |
+
> **Measured absence.** There is **no** `SATQUERY_UPSTREAM_URL` and **no** `HF_TOKEN` in the live
|
| 164 |
+
> config. The transport is the outbound tunnel, not a forwarded port. This contradicts the older
|
| 165 |
+
> `docs/DEPLOYMENT_TOPOLOGY.md` Β§3.2 table, which predates the tunnel design.
|
| 166 |
+
|
| 167 |
+
### 6.2 Codespace (inference)
|
| 168 |
|
| 169 |
| Variable | Purpose |
|
| 170 |
|---|---|
|
| 171 |
+
| `PORT` | platform-assigned; **must be read** (a historical blocker was that no code read it) |
|
| 172 |
| `SATQUERY_DEVICE` | `cpu` \| `cuda` \| `mps` \| `null`; read **without importing torch** |
|
| 173 |
+
| `SATQUERY_MAX_FILE_BYTES` | per-file cap, shared with Render |
|
| 174 |
+
| `SATQUERY_ASSET_ENABLED` / `SATQUERY_ASSET_DIR` | both required for `/v1/assets`; **fails closed (503)** otherwise |
|
| 175 |
| `SATQUERY_ASSET_MAX_FILES` / `SATQUERY_ASSET_TTL_S` | optional handle capacity / lifetime |
|
| 176 |
|
| 177 |
+
### 6.3 Config-loader environment overrides
|
| 178 |
+
|
| 179 |
+
Two registry values can be overridden from the environment without editing the YAML
|
| 180 |
+
(`core/config.py`):
|
| 181 |
+
|
| 182 |
+
| Variable | Effect |
|
| 183 |
+
|---|---|
|
| 184 |
+
| `SATQUERY_PRECISION` | overrides `training.precision` |
|
| 185 |
+
| `SATQUERY_TORCH_COMPILE` | overrides `deployment.torch_compile` (`"true"` β `True`) |
|
| 186 |
+
|
| 187 |
+
Both are still validated by the loader β setting `SATQUERY_TORCH_COMPILE=true` **fails startup**
|
| 188 |
+
because finding C-8 forbids `torch.compile`.
|
| 189 |
+
|
| 190 |
+
## 7. Deploy mechanics
|
| 191 |
+
|
| 192 |
+
| Tier | Mechanism |
|
| 193 |
+
|---|---|
|
| 194 |
+
| Frontend β Cloudflare Pages | `scripts/stage_pages.mjs` builds a Pages bundle; `npx wrangler pages deploy` |
|
| 195 |
+
| Backend β Render | `render.yaml` blueprint; `main.py` exposes the ASGI object `app` |
|
| 196 |
+
| Inference β Codespace | `deploy/codespace/serve.py` binds `build_space_app()` to `$PORT`; `.devcontainer/` forwards 8000 and starts the tunnel agent via `postStartCommand` |
|
| 197 |
+
| Repository writes | the **GitHub Git Data API** β blob β tree β commit β `PATCH` ref |
|
| 198 |
+
|
| 199 |
+
### 7.1 Why the Git Data API instead of `git push`
|
| 200 |
+
|
| 201 |
+
Every deployed file is uploaded as a **blob** whose sha256 is **computed locally and verified against
|
| 202 |
+
the uploaded blob**, then assembled into a tree, committed, and the branch ref patched. This means:
|
| 203 |
+
|
| 204 |
+
- each file is **content-verified** rather than trusted;
|
| 205 |
+
- deletions are expressed explicitly as `sha: null` tree entries;
|
| 206 |
+
- the deploy is **idempotent** β re-running it with identical content produces no change.
|
| 207 |
+
|
| 208 |
+
**Measured:** 9 deployed files were re-read from the API and found **sha256 byte-identical** to the
|
| 209 |
+
local copies, with the deployed HEAD re-read independently (`verify_deployed_head.py`).
|
| 210 |
|
| 211 |
+
## 8. Cold start (documented, not hidden)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
+
Render's free tier sleeps when idle, and the Codespace may be stopped. Before a request can be served,
|
| 214 |
+
Render must start the Codespace (if stopped) and wait for the tunnel agent to reconnect. The frontend
|
| 215 |
+
shows *"Waking inference engineβ¦"* during this.
|
| 216 |
|
| 217 |
+
| Property | Value |
|
| 218 |
+
|---|---|
|
| 219 |
+
| Cold start | **tens of seconds** |
|
| 220 |
+
| Tunnel wait before falling through | `SATQUERY_TUNNEL_TIMEOUT_S` = 150 s |
|
| 221 |
+
| Wake wait | `SATQUERY_WAKE_TIMEOUT_S` = 120 s |
|
| 222 |
+
| Upstream budget | `SATQUERY_UPSTREAM_TIMEOUT_S` = 90 s |
|
| 223 |
+
|
| 224 |
+
Cold start is **documented rather than papered over**: an honest "this will take a while the first
|
| 225 |
+
time" is better than a silent hang.
|
| 226 |
+
|
| 227 |
+
### 8.1 The `transport_mode: auto` fallthrough β B-07 (`OPEN`)
|
| 228 |
+
|
| 229 |
+
`SATQUERY_TRANSPORT=auto` means: **try the tunnel; on timeout, fall through to the forward path.**
|
| 230 |
+
|
| 231 |
+
The forward path to a **private** repo returns `302` quickly β but the wake step still consumes
|
| 232 |
+
`SATQUERY_WAKE_TIMEOUT_S` (120 s) **first**. So a worst-case failed request takes roughly
|
| 233 |
+
|
| 234 |
+
```
|
| 235 |
+
150 s (tunnel timeout) + 120 s (wake timeout on a 302) β 249 s
|
| 236 |
+
```
|
| 237 |
|
| 238 |
+
This is the **root shape** of the observed transient tunnel gap, and it is why a request can appear to
|
| 239 |
+
hang and then fail. A patch (`fix-b07-forward-unavailable.patch`) was authored and verified
|
| 240 |
+
(`py_compile` clean, applies cleanly to the deployed `main.py`), adding distinct
|
| 241 |
+
`forward_unavailable` (503, terminal 302/401/403 on the forward path) and `upstream_timeout`
|
| 242 |
+
(504, tunnel healthy but slow) codes, plus the `codespace_name` `.strip()` fix.
|
| 243 |
|
| 244 |
+
> **Status: B-07 is `OPEN`.** The patch is **prepared but NOT deployed.** The deployed health payload
|
| 245 |
+
> still shows the trailing `\n`.
|
|
|
|
| 246 |
|
| 247 |
+
## 9. The five historical backend blockers
|
| 248 |
|
| 249 |
+
Before any backend could boot, five verified blockers had to be closed. The current design closes them:
|
| 250 |
+
|
| 251 |
+
| # | Blocker (verified) | How it is closed |
|
| 252 |
|---|---|---|
|
| 253 |
+
| 1 | `requirements.txt` declared no `fastapi` / `uvicorn` / `httpx` / `starlette` | the runtime installs the ASGI stack so `build_space_app()` and the gateway can import |
|
| 254 |
+
| 2 | no code read `$PORT` β a platform port would be ignored | `deploy/codespace/serve.py` binds to `$PORT`; Render reads its own |
|
| 255 |
+
| 3 | hand-rolled CORS raised `405` on `OPTIONS`, so browser preflight failed | the gateway registers `OPTIONS` explicitly / uses Starlette's CORS middleware |
|
| 256 |
+
| 4 | module-level `app = create_app()` swallowed config errors into `app = None` | construction errors now **propagate** (fail-fast) instead of leaving a dead `app` |
|
| 257 |
+
| 5 | adapter integrity unverified on load (`_adapter_sha256` computed but never compared) | the load path compares the digest against an expected value, or fails startup |
|
| 258 |
+
|
| 259 |
+
## 10. Platform traps (recorded so they are not rediscovered)
|
| 260 |
+
|
| 261 |
+
| Trap | Detail |
|
| 262 |
+
|---|---|
|
| 263 |
+
| **Cloudflare `_headers` CONCATENATE** | Two matching rules are **merged, not overridden**. A specific rule nested under a broad `/assets/img/*` rule yields `max-age=604800, β¦, max-age=0, must-revalidate` β and Chromium takes the **FIRST** `max-age`. The file's own "later rules override" comment is **false**. |
|
| 264 |
+
| **Cloudflare 308 redirect** | `X.html` β `/X`. Reference the extensionless path. |
|
| 265 |
+
| **Forwarded port returns 302** | for a private repo β this is *why* the tunnel exists. |
|
| 266 |
+
| **Tunnel agent must start on boot** | via the devcontainer `postStartCommand`, or a restarted Codespace comes up with `agent_connected: false`. |
|
| 267 |
+
| **Never retry `/api/infer` at the gateway** | a retry consumes inference twice. |
|
| 268 |
+
| **`deploy/` is stale and untracked** | not the deployed source. |
|
| 269 |
|
| 270 |
+
## 11. The superseded design, and what did NOT change
|
| 271 |
|
| 272 |
+
The earlier design ran inference on an **HF Space with ZeroGPU** (5 GPU-min/day, `@spaces.GPU`
|
| 273 |
+
decoration) behind a **Railway** gateway. The active design changes three things:
|
| 274 |
+
|
| 275 |
+
1. **CPU-first instead of ZeroGPU.** No code change was required β `device_preference` honours
|
| 276 |
+
`SATQUERY_DEVICE` and defaults to CPU, every specialist defaults to `device="cpu"`, and all
|
| 277 |
+
placement is `.to(device)` (never `.cuda()`).
|
| 278 |
+
2. **A real, always-buildable inference environment.** A Codespace gives a reproducible container
|
| 279 |
+
without a GPU quota or a Space's cold-start constraint. The wake flow replaces ZeroGPU lazy
|
| 280 |
+
loading as the cold-start story.
|
| 281 |
+
3. **No GPU quota to protect at the gateway.** Rate/size limits remain, but as **fairness** controls
|
| 282 |
+
rather than quota protection.
|
| 283 |
+
|
| 284 |
+
**What did NOT change:**
|
| 285 |
+
|
| 286 |
+
| Unchanged | Detail |
|
| 287 |
+
|---|---|
|
| 288 |
+
| the four-endpoint contract | `/v1/health`, `/v1/capabilities`, `/v1/analyze`, `/v1/assets` |
|
| 289 |
+
| the gateway responsibility table | Β§3.2 above |
|
| 290 |
+
| the env-var vocabulary | only host names moved (`SATQUERY_SPACE_URL` β `SATQUERY_UPSTREAM_URL`) |
|
| 291 |
+
| the config freeze | `78f1e3700da15aa1` |
|
| 292 |
|
| 293 |
+
`configs/deploy.yaml` still describes the old HF-Space/ZeroGPU target (`platform: huggingface-spaces`,
|
| 294 |
+
`sdk: gradio`, `zerogpu: true`, the `gpu_duration_*` values). It is **frozen paperwork**: no Gradio
|
| 295 |
+
runtime exists in code, and editing it would move `Config.hash`. It is left undisturbed.
|
| 296 |
+
|
| 297 |
+
## 12. Deployment preconditions (open items)
|
| 298 |
|
| 299 |
1. Cloudflare Pages project name / domain (needed for the deploy command and `robots.txt` sitemap).
|
| 300 |
+
2. Artifacts present, **or** capabilities honestly ship `available: false` β the service degrades
|
| 301 |
+
rather than crashes on absent artifacts.
|
| 302 |
+
3. `HF_TOKEN` on Render **if** the HF proxy path is used (not used in the live config).
|
| 303 |
+
4. Codespace `.devcontainer/` forwarding `:8000` **and** starting the tunnel agent.
|
| 304 |
+
5. The five blockers in Β§9 closed and verified on the first live run.
|
| 305 |
+
|
| 306 |
+
## 13. What is `NOT RUN` / `OPEN` / `BLOCKED` for this topic
|
| 307 |
+
|
| 308 |
+
| Item | State |
|
| 309 |
+
|---|---|
|
| 310 |
+
| B-07 tunnel fallthrough | **OPEN** β patch prepared, NOT deployed |
|
| 311 |
+
| B-02 `codespace_name` trailing `\n` | **OPEN** (cosmetic) |
|
| 312 |
+
| A deployed system-level load test | **NOT RUN** |
|
| 313 |
+
| Multi-region / HA deployment | **NOT RUN** |
|
| 314 |
+
| A production database / persistence | **BY DESIGN** (absent) |
|
| 315 |
+
| The ZeroGPU/Gradio deployment target | **REJECTED** (superseded; frozen paperwork only) |
|
| 316 |
+
|
| 317 |
+
## 14. Where the evidence lives
|
| 318 |
+
|
| 319 |
+
| Evidence | Location |
|
| 320 |
+
|---|---|
|
| 321 |
+
| Live health + capabilities probe | `release/CURRENT_RELEASE_STATE.md` Β§1 |
|
| 322 |
+
| Topology contract | `docs/DEPLOYMENT_TOPOLOGY.md`, `docs/DEPLOYMENT_ARCHITECTURE.md` |
|
| 323 |
+
| The undeployed B-07 patch | session scratch: `fix-b07-forward-unavailable.patch` |
|
| 324 |
+
| Deployed-artifact integrity check | `verify_deployed_head.py` (session scratch) |
|
| 325 |
+
| Live validation (3 passes) | `.workbuddy-ai/scratch/live_validation/` |
|