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/REPRODUCIBILITY.md
Browse files- docs/REPRODUCIBILITY.md +1185 -76
docs/REPRODUCIBILITY.md
CHANGED
|
@@ -1,130 +1,1239 @@
|
|
| 1 |
# Reproducibility
|
| 2 |
|
| 3 |
-
**Status tags:** `IMPLEMENTED` · `VERIFIED` · `MEASURED` · `NOT RUN` · `BLOCKED`
|
|
|
|
| 4 |
|
| 5 |
-
This document states exactly what a third party can reproduce, with what, and
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
---
|
| 8 |
|
| 9 |
## 1. The reproducibility contract
|
| 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 |
-
## 2.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
```bash
|
| 37 |
python release/tools/verify_readme_metrics.py
|
| 38 |
```
|
| 39 |
|
| 40 |
-
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
##
|
| 49 |
|
| 50 |
```bash
|
| 51 |
git clone https://github.com/Anish-lab-blip/SatQuery-AI
|
| 52 |
cd SatQuery-AI
|
| 53 |
python -m venv .venv
|
| 54 |
-
source .venv/Scripts/activate # Windows git-bash; .venv/bin/activate on Linux/macOS
|
| 55 |
pip install -r requirements.txt
|
| 56 |
```
|
| 57 |
|
| 58 |
-
|
| 59 |
-
`.to(device)`, never `.cuda()`. Backbones are fetched from the Hugging Face Hub on first use, pinned
|
| 60 |
-
by revision.
|
| 61 |
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|---|---|---|
|
| 66 |
-
|
|
| 67 |
-
|
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
|
| 76 |
|
| 77 |
```bash
|
| 78 |
curl --noproxy '*' https://satquery-backend-m4yv.onrender.com/api/health
|
| 79 |
curl --noproxy '*' https://satquery-backend-m4yv.onrender.com/api/capabilities
|
| 80 |
```
|
| 81 |
|
| 82 |
-
`/api/
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
|
|
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
| Artifact | Where it trains | Guide |
|
| 92 |
|---|---|---|
|
| 93 |
-
| router
|
| 94 |
-
| grounding
|
| 95 |
-
| change
|
| 96 |
-
| optical_sar
|
| 97 |
-
| change_vqa
|
| 98 |
-
| vlm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
-
the external GPU environment described in their guides. The repository reproduces:
|
| 104 |
-
- the **promotion gate** (byte-identity, sha256, zero non-finite tensors);
|
| 105 |
-
- the **evaluation**;
|
| 106 |
-
- the **serving wiring**.
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
| Item | Reason |
|
| 114 |
|---|---|
|
| 115 |
-
| The private deployment
|
| 116 |
-
|
|
| 117 |
-
|
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
- **
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Reproducibility
|
| 2 |
|
| 3 |
+
**Status tags:** `IMPLEMENTED` · `VERIFIED` · `MEASURED` · `ATTEMPTED` · `NOT RUN` · `BLOCKED` ·
|
| 4 |
+
`DEFERRED` · `REJECTED` · `OPEN` · `RESOLVED` · `CLOSED`.
|
| 5 |
|
| 6 |
+
This document states **exactly what a third party can reproduce, with what, and — just as
|
| 7 |
+
importantly — what they cannot**. It is written to the same depth as the rest of this documentation
|
| 8 |
+
set: real commands, real file paths, real hashes, real expected output, and an explicit status for
|
| 9 |
+
every claim.
|
| 10 |
+
|
| 11 |
+
It is deliberately **not** a "quickstart". A quickstart tells you the happy path and hides the
|
| 12 |
+
edges. This document records the edges, because in this project the edges are where the real
|
| 13 |
+
debugging time went — a dead sandbox proxy, a pytest that only exists in one virtualenv, a Chrome
|
| 14 |
+
that silently drops synthetic key events, a Hugging Face helper that returned an *empty file* and
|
| 15 |
+
would have produced a **false failure**. Those are documented in §10 so that a third party does not
|
| 16 |
+
rediscover them.
|
| 17 |
+
|
| 18 |
+
> **The one rule this document obeys, like every document in `docs/`:** *do not fabricate*. Every
|
| 19 |
+
> claim below is grounded in a file that was read (`core/config.py`, `configs/base.yaml`,
|
| 20 |
+
> `release/tools/*.py`, `artifacts/**`, `docs/**`). Where the evidence does not establish something,
|
| 21 |
+
> the text says `UNKNOWN — not established from the available evidence`.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Table of contents
|
| 26 |
+
|
| 27 |
+
1. [The reproducibility contract](#1-the-reproducibility-contract)
|
| 28 |
+
2. [The frozen configuration](#2-the-frozen-configuration)
|
| 29 |
+
3. [Reproduce the metric verification (cheap, no GPU)](#3-reproduce-the-metric-verification-cheap-no-gpu)
|
| 30 |
+
4. [Reproduce the environment](#4-reproduce-the-environment)
|
| 31 |
+
5. [Reproduce the tests](#5-reproduce-the-tests)
|
| 32 |
+
6. [Reproduce a live run](#6-reproduce-a-live-run)
|
| 33 |
+
7. [Reproduce and verify the released artifacts](#7-reproduce-and-verify-the-released-artifacts)
|
| 34 |
+
8. [What "reproduce" means for the two externally-trained artifacts](#8-what-reproduce-means-for-the-two-externally-trained-artifacts)
|
| 35 |
+
9. [What is NOT reproducible from this release — exhaustive](#9-what-is-not-reproducible-from-this-release--exhaustive)
|
| 36 |
+
10. [Environment traps recorded for reproducibility](#10-environment-traps-recorded-for-reproducibility)
|
| 37 |
+
11. [What is NOT RUN / OPEN / BLOCKED for this topic](#11-what-is-not-run--open--blocked-for-this-topic)
|
| 38 |
+
12. [Where the evidence lives](#12-where-the-evidence-lives)
|
| 39 |
|
| 40 |
---
|
| 41 |
|
| 42 |
## 1. The reproducibility contract
|
| 43 |
|
| 44 |
+
Reproducibility here is not a slogan; it is a set of **enforced guarantees**. Each row below states a
|
| 45 |
+
guarantee, how it is enforced in code or in a tool, and how a third party can independently check
|
| 46 |
+
it.
|
| 47 |
+
|
| 48 |
+
| Guarantee | How it is enforced | How to check it |
|
| 49 |
+
|---|---|---|
|
| 50 |
+
| **Frozen configuration** | All tunables live in `configs/base.yaml`; there are no magic numbers in Python. The loader validates invariants and computes a hash. | Read `configs/base.yaml`; run the loader; compare the hash. |
|
| 51 |
+
| **Frozen config hash** | `Config.hash` = `sha256(json.dumps(data, sort_keys=True, default=str))[:16]` (`core/config.py:76-80`). Every evaluation run and every artifact records the hash it was produced against. | `Config.hash` returns `78f1e3700da15aa1`; the same string appears in `models/manifest.json` and each artifact. |
|
| 52 |
+
| **Pinned backbones** | Every backbone is pinned by `repo_id` **and** `revision` (a commit prefix, not a floating tag) in `configs/base.yaml`. | The four pins are listed in §4.3; each resolves to an exact Hub commit. |
|
| 53 |
+
| **Seed** | `project.seed: 42` (`core/config.py:82-84`). | Read the key; it is in the registry. |
|
| 54 |
+
| **Immutable public test** | `evaluation.immutable_public_test: true` and `evaluation.hidden_data_access: false` (`configs/base.yaml:273-277`). | Read the two keys; the corpus module refuses to mutate a sealed corpus. |
|
| 55 |
+
| **Leakage isolation** | `evaluation.leakage_split_key: scene_id` — splits are assigned by *scene*, never by *tile*. | Read the key; `evaluation/leakage.py` audits manifests. |
|
| 56 |
+
| **No official aggregate** | `evaluation.official_aggregate_weights: null`. There is no weighted composite score. | Read the key; `normalize.py` raises on aggregate requests. |
|
| 57 |
+
| **Byte-verified artifacts** | Every released artifact ships with a sha256 in `models/checksums.sha256`, generated from the actual files. | `sha256sum -c models/checksums.sha256`. |
|
| 58 |
+
| **Verified metrics** | Every number quoted in the README is checked against its source artifact by `release/tools/verify_readme_metrics.py`. | Run the tool; it prints `ALL CLAIMS VERIFIED`. |
|
| 59 |
+
| **Independent Hub verification** | `release/tools/hf_verify.py` re-downloads every uploaded file over direct HTTPS and re-hashes the bytes it received. | Run the tool; it prints `artifacts failed : 0`. |
|
| 60 |
+
| **Negative results preserved** | Rejected and open rulings stay in the record (`PHASE7_RESOLUTION_DECISION.md`, `phase6_closure.json`, `threshold_sweep_val.json`). | Read the artifacts; they contain the rejections, not scrubbed versions. |
|
| 61 |
+
|
| 62 |
+
### 1.1 What the contract is *not*
|
| 63 |
+
|
| 64 |
+
The contract does **not** promise:
|
| 65 |
+
|
| 66 |
+
- a single command that retrains every artifact from scratch (§8);
|
| 67 |
+
- an end-to-end system-level accuracy number (§9 — no such benchmark exists);
|
| 68 |
+
- that the two externally-trained artifacts can be regenerated inside this repository (§8);
|
| 69 |
+
- bit-identical *training* on arbitrary hardware (only bit-identical *released artifacts* are
|
| 70 |
+
guaranteed, by hash).
|
| 71 |
+
|
| 72 |
+
### 1.2 The reproducibility ladder
|
| 73 |
+
|
| 74 |
+
Reproduction here comes in four tiers, from cheapest to most expensive. A reviewer who wants to
|
| 75 |
+
spend five minutes gets tier 1; a reviewer who wants to audit the whole thing walks all four.
|
| 76 |
+
|
| 77 |
+
| Tier | What it proves | Cost | Section |
|
| 78 |
+
|---|---|---|---|
|
| 79 |
+
| **1. Metric verification** | Every quoted number matches its artifact | seconds, CPU | §3 |
|
| 80 |
+
| **2. Environment + tests** | The code installs and the suites behave as recorded | minutes, CPU | §4–§5 |
|
| 81 |
+
| **3. Live run** | The deployed stack answers correctly on unseen imagery | minutes (plus cold start), network | §6 |
|
| 82 |
+
| **4. Artifact + Hub verification** | The released weights are byte-identical to what was uploaded | minutes, network | §7 |
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## 2. The frozen configuration
|
| 87 |
+
|
| 88 |
+
### 2.1 What "frozen" means here
|
| 89 |
+
|
| 90 |
+
`configs/base.yaml` is the **single registry** for every tunable in the system. The design rule is:
|
| 91 |
+
*no magic numbers in Python*. If a value can change behaviour — an image size, a channel count, a
|
| 92 |
+
threshold, a precision, a split key — it lives in the registry, not in a function body.
|
| 93 |
+
|
| 94 |
+
This matters for reproducibility because it means the **entire behavioural surface of the system is
|
| 95 |
+
captured by one file**, and that file has a hash.
|
| 96 |
+
|
| 97 |
+
### 2.2 The hash and how it is computed
|
| 98 |
+
|
| 99 |
+
```python
|
| 100 |
+
@property
|
| 101 |
+
def hash(self) -> str:
|
| 102 |
+
"""Stable hash of the whole registry. Recorded in every evaluation run."""
|
| 103 |
+
blob = json.dumps(self._data, sort_keys=True, default=str).encode()
|
| 104 |
+
return hashlib.sha256(blob).hexdigest()[:16]
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
— `core/config.py:76-80`.
|
| 108 |
+
|
| 109 |
+
Three properties make this hash **stable**:
|
| 110 |
+
|
| 111 |
+
1. **`sort_keys=True`** — key order in the YAML file does not change the hash.
|
| 112 |
+
2. **`default=str`** — non-JSON-native values (dates, paths) are stringified deterministically.
|
| 113 |
+
3. **`[:16]`** — the first 16 hex characters are used as the human-readable identifier.
|
| 114 |
+
|
| 115 |
+
The frozen value is:
|
| 116 |
+
|
| 117 |
+
```
|
| 118 |
+
78f1e3700da15aa1
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
It is recorded in `models/manifest.json` (`"config_hash": "78f1e3700da15aa1"`), in each artifact's
|
| 122 |
+
metadata, and in the live evidence (each live run's panel shows the hash). This is the single
|
| 123 |
+
identifier that ties "the thing that produced this number" to "the configuration a reviewer is
|
| 124 |
+
reading".
|
| 125 |
+
|
| 126 |
+
### 2.3 The enforced invariants, with exact arithmetic
|
| 127 |
+
|
| 128 |
+
Editing `configs/base.yaml` moves the hash and **invalidates every artifact keyed to it**. To make
|
| 129 |
+
that failure loud rather than silent, the loader rejects a config that violates a set of recorded
|
| 130 |
+
invariants. Two of them exist specifically because their violation is a *silent* error that torch
|
| 131 |
+
only surfaces much later.
|
| 132 |
+
|
| 133 |
+
#### Invariant 1 — fusion input dimension (finding C-1)
|
| 134 |
+
|
| 135 |
+
```
|
| 136 |
+
fusion.input_dim == 3 * croma.encoder_dim + croma.optical_channels + croma.sar_channels
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
With the frozen values `encoder_dim = 768`, `optical_channels = 12`, `sar_channels = 2`:
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
+
fusion.input_dim == 3 * 768 + 12 + 2
|
| 143 |
+
== 2304 + 12 + 2
|
| 144 |
+
== 2318
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
This is the concatenation of CROMA's three 768-d GAP vectors (`optical_GAP`, `SAR_GAP`,
|
| 148 |
+
`joint_GAP`) plus the 12 optical channels and 2 SAR channels of the sensor adapter. The loader
|
| 149 |
+
recomputes `expected` from the config and raises `ConfigError` if `fusion.input_dim` disagrees
|
| 150 |
+
(`core/config.py:154-163`). The docstring is explicit about *why*: "CROMA emits optical/SAR/joint
|
| 151 |
+
GAP vectors; the [declared value must match]". The upstream contract was independently reproduced
|
| 152 |
+
in `docs/PHASE14_OPTICAL_SAR_DECISIONS.md §1`, where the fusion input is recomputed at runtime
|
| 153 |
+
(`fusion_head.py:74`, `:311`) rather than hardcoded.
|
| 154 |
|
| 155 |
+
Two related guards fire alongside it (`core/config.py:165-168`):
|
| 156 |
|
| 157 |
+
```
|
| 158 |
+
croma.optical_channels == 12 # CROMA s2_channels is fixed
|
| 159 |
+
croma.sar_channels == 2 # CROMA s1_channels is fixed
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
#### Invariant 2 — grounding head feature dimension (finding P7-1)
|
| 163 |
|
| 164 |
```
|
| 165 |
+
grounding_head.feature_dim == 4 * grounding.encoder_projected_dim
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
With `encoder_projected_dim = 512`:
|
| 169 |
+
|
| 170 |
+
```
|
| 171 |
+
grounding_head.feature_dim == 4 * 512 == 2048
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
The loader's own comment explains why this is **load-bearing**:
|
| 175 |
+
|
| 176 |
+
> A mismatch here is a SILENT shape error. torch only raises at the [similarity step], after patch
|
| 177 |
+
> features are already cached. It is therefore rejected at load time.
|
| 178 |
+
|
| 179 |
+
— `core/config.py:176-195`.
|
| 180 |
+
|
| 181 |
+
The "4×" arises because the grounding head consumes four projected feature streams; the projected
|
| 182 |
+
dimension is declared in config precisely so this guard can exist without hardcoding 512.
|
| 183 |
+
|
| 184 |
+
#### Further load-time guards
|
| 185 |
+
|
| 186 |
+
The `_validate()` method (`core/config.py:94`) also enforces:
|
| 187 |
+
|
| 188 |
+
| Guard | Rule | Finding |
|
| 189 |
+
|---|---|---|
|
| 190 |
+
| CROMA resolution | `croma.image_resolution` is an int and a multiple of 8 (CROMA asserts `% 8 == 0`) | C-7 |
|
| 191 |
+
| Training precision | `training.precision ∈ {fp16, bf16, fp32}` | C-6 |
|
| 192 |
+
| ZeroGPU compile ban | `deployment.torch_compile` must not be `True` — ZeroGPU does not support `torch.compile` | C-8 |
|
| 193 |
+
| VLM prompt template | `vlm.prompt_must_use_chat_template` must be `true` — SmolVLM raises otherwise | — |
|
| 194 |
+
|
| 195 |
+
These are the invariants a reviewer can verify by reading one method. They are the reason a
|
| 196 |
+
*plausible-looking* config edit fails at load instead of producing a wrong number three hours later.
|
| 197 |
+
|
| 198 |
+
### 2.4 The evaluation invariants
|
| 199 |
+
|
| 200 |
+
```yaml
|
| 201 |
+
evaluation:
|
| 202 |
+
immutable_public_test: true
|
| 203 |
+
hidden_data_access: false
|
| 204 |
+
official_aggregate_weights: null
|
| 205 |
+
leakage_split_key: scene_id
|
| 206 |
```
|
| 207 |
|
| 208 |
+
— `configs/base.yaml:273-277`.
|
| 209 |
+
|
| 210 |
+
| Key | Value | Meaning |
|
| 211 |
+
|---|---|---|
|
| 212 |
+
| `immutable_public_test` | `true` | The public-test corpus is sealed; it cannot be mutated by an evaluation. |
|
| 213 |
+
| `hidden_data_access` | `false` | No code path may read hidden data during a scored run. |
|
| 214 |
+
| `official_aggregate_weights` | `null` | **There is no composite score.** A single "overall accuracy" is forbidden by construction. |
|
| 215 |
+
| `leakage_split_key` | `scene_id` | Splits are assigned by scene, so tiles from one scene never straddle train/test. |
|
| 216 |
+
|
| 217 |
+
The evaluation-honesty rules that these keys serve are stated in full in
|
| 218 |
+
[`EVALUATION.md`](EVALUATION.md) §2.
|
| 219 |
|
| 220 |
+
### 2.5 The deployment section is frozen paperwork
|
| 221 |
+
|
| 222 |
+
```yaml
|
| 223 |
+
deployment:
|
| 224 |
+
platform: huggingface-spaces
|
| 225 |
+
sdk: gradio
|
| 226 |
+
zerogpu: true
|
| 227 |
+
torch_compile: false
|
| 228 |
+
...
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
— `configs/base.yaml:282-295`.
|
| 232 |
+
|
| 233 |
+
The deployment target described here is the **superseded** HF-Space/ZeroGPU design. The active
|
| 234 |
+
topology is Cloudflare Pages → Render → Codespace (`docs/DEPLOYMENT.md §1`). This section is
|
| 235 |
+
retained as **frozen paperwork**: editing it would move the config hash, and the hash is what every
|
| 236 |
+
artifact is keyed to. No Gradio runtime exists in code. The `torch_compile: false` line is the
|
| 237 |
+
C-8 guard and remains meaningful regardless of host.
|
| 238 |
+
|
| 239 |
+
### 2.6 The hash-exempt path
|
| 240 |
+
|
| 241 |
+
One decision (DEV-2, the CROMA input-normalisation question) needed to become inspectable **without**
|
| 242 |
+
adding a `base.yaml` key — because adding a key would have moved the hash away from `78f1e3700da15aa1`
|
| 243 |
+
and invalidated every artifact. The resolution was a hash-exempt path
|
| 244 |
+
(`docs/PHASE14_OPTICAL_SAR_DECISIONS.md`, item 7):
|
| 245 |
+
|
| 246 |
+
```
|
| 247 |
+
SATQUERY_CROMA_USE_8_BIT (env) → croma.use_8_bit (config) → default True
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
It is inspectable at runtime via `CROMAEncoder.describe()["input_normalisation"]`. This is recorded
|
| 251 |
+
because it is the one place where "frozen" and "configurable" were reconciled deliberately, and a
|
| 252 |
+
reviewer should know the escape hatch exists and is documented rather than hidden.
|
| 253 |
+
|
| 254 |
+
### 2.7 What invalidates the hash
|
| 255 |
+
|
| 256 |
+
Anything that changes `self._data` when serialised with `sort_keys=True`. Concretely:
|
| 257 |
+
|
| 258 |
+
- editing any value in `configs/base.yaml`;
|
| 259 |
+
- adding or removing a key;
|
| 260 |
+
- changing a value's *type* such that its `default=str` rendering changes.
|
| 261 |
+
|
| 262 |
+
The consequence is not merely a different hash; it is that **every artifact keyed to
|
| 263 |
+
`78f1e3700da15aa1` no longer describes the running configuration**, and the honest action is to
|
| 264 |
+
re-run the evaluation rather than reinterpret the old numbers.
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
## 3. Reproduce the metric verification (cheap, no GPU)
|
| 269 |
+
|
| 270 |
+
This is the cheapest, highest-value reproduction in the whole release. It takes seconds, needs no
|
| 271 |
+
GPU and no network, and it is the check that a skeptical reviewer should run **first**.
|
| 272 |
+
|
| 273 |
+
### 3.1 The verifier
|
| 274 |
|
| 275 |
```bash
|
| 276 |
python release/tools/verify_readme_metrics.py
|
| 277 |
```
|
| 278 |
|
| 279 |
+
`release/tools/verify_readme_metrics.py` is read-only. Its own docstring states its contract:
|
| 280 |
+
|
| 281 |
+
> Verify every metric quoted in the release README against its source artifact. Read-only. Each
|
| 282 |
+
> claim is compared at the precision at which the README states it.
|
| 283 |
+
|
| 284 |
+
The tool is **not** a re-computation of the metrics. It is a **provenance check**: it reads the
|
| 285 |
+
artifact that the README cites, resolves a dotted key into it, and compares the value against the
|
| 286 |
+
literal string the README prints — at the README's own precision.
|
| 287 |
+
|
| 288 |
+
### 3.2 The 20 claims and their exact key paths
|
| 289 |
+
|
| 290 |
+
The claim list is not assumed; the key names were **discovered by walking the artifacts**. Several
|
| 291 |
+
live under nested paths, and the tool's docstring says so:
|
| 292 |
+
|
| 293 |
+
> Key names were discovered by walking the artifacts, NOT assumed: several live under nested paths
|
| 294 |
+
> (e.g. change metrics are `metrics.pooled.iou`, grounding is `results.head_threshold.mean_best_iou`,
|
| 295 |
+
> VLM is `why_usable_verified.adapted_test.*`).
|
| 296 |
+
|
| 297 |
+
| # | Claim | Artifact | Dotted key | README value |
|
| 298 |
+
|---|---|---|---|---|
|
| 299 |
+
| 1 | change pooled IoU | `artifacts/change/eval_test/eval_result.json` | `metrics.pooled.iou` | `0.8122` |
|
| 300 |
+
| 2 | change macro IoU | `artifacts/change/eval_test/eval_result.json` | `metrics.macro.miou` | `0.8457` |
|
| 301 |
+
| 3 | change pooled F1 | `artifacts/change/eval_test/eval_result.json` | `metrics.pooled.f1` | `0.8964` |
|
| 302 |
+
| 4 | grounding canonical head_threshold mean_best_IoU | `artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json` | `results.head_threshold.mean_best_iou` | `0.2838` |
|
| 303 |
+
| 5 | grounding canonical head_threshold recall@0.5 | `…/eval_result_canonical.json` | `results.head_threshold.recall.0.50` | `0.2198` |
|
| 304 |
+
| 6 | grounding matched6 head_threshold mean_best_IoU | `…/eval_result_matched6.json` | `results.head_threshold.mean_best_iou` | `0.2566` |
|
| 305 |
+
| 7 | grounding matched6 head_threshold recall@0.5 | `…/eval_result_matched6.json` | `results.head_threshold.recall.0.50` | `0.1938` |
|
| 306 |
+
| 8 | grounding head_argmax mean_best_IoU (canonical) | `…/eval_result_canonical.json` | `results.head_argmax.mean_best_iou` | `0.1215` |
|
| 307 |
+
| 9 | grounding zero-shot baseline IoU (canonical) | `…/eval_result_canonical.json` | `results.zero_shot_matched.mean_best_iou` | `0.0972` |
|
| 308 |
+
| 10 | optical-SAR fusion accuracy | `artifacts/optical_sar/fusion_head_production_v001/pre_registered_115_metric.json` | `accuracy` | `0.931` |
|
| 309 |
+
| 11 | optical-SAR fusion macro_F1 | `…/pre_registered_115_metric.json` | `macro_f1` | `0.434161` |
|
| 310 |
+
| 12 | change_vqa test accuracy | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test_accuracy` | `0.697626` |
|
| 311 |
+
| 13 | change_vqa test macro_F1 | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test_macro_f1` | `0.378373` |
|
| 312 |
+
| 14 | change_vqa test2 accuracy | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test2_accuracy` | `0.651469` |
|
| 313 |
+
| 15 | change_vqa test2 macro_F1 | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test2_macro_f1` | `0.372309` |
|
| 314 |
+
| 16 | router overall ungated accuracy | `artifacts/router/threshold_sweep_val.json` | `overall_ungated_accuracy` | `0.965116` |
|
| 315 |
+
| 17 | calibration ECE before scaling | `artifacts/calibration_v001.json` | `metrics.ece_before` | `0.013755` |
|
| 316 |
+
| 18 | calibration ECE after scaling | `artifacts/calibration_v001.json` | `metrics.ece_after` | `0.014929` |
|
| 317 |
+
| 19 | VLM adapter exact_match | `artifacts/vlm/phase6_closure.json` | `why_usable_verified.adapted_test.exact_match` | `0.963` |
|
| 318 |
+
| 20 | VLM adapter F1 | `artifacts/vlm/phase6_closure.json` | `why_usable_verified.adapted_test.f1` | `0.96432` |
|
| 319 |
+
|
| 320 |
+
Note rows 1–3, 6–7, 12–15, and 16: these are the rows that keep the honesty rules honest. The
|
| 321 |
+
change numbers are quoted as **pooled and macro together** (rule 4); grounding is quoted under
|
| 322 |
+
**both protocols** (rule 2); change-VQA is quoted under **both test sets** (rule 3); the router
|
| 323 |
+
number is quoted with its **validation-only** caveat (rule 5). A verifier that checked only one
|
| 324 |
+
number per task would be reproducible and *wrong*.
|
| 325 |
+
|
| 326 |
+
### 3.3 The status assertions
|
| 327 |
+
|
| 328 |
+
Metrics are not the only claims. After the 20 numeric comparisons, the tool asserts four statuses
|
| 329 |
+
that the README also states (`verify_readme_metrics.py:117-131`):
|
| 330 |
+
|
| 331 |
+
```
|
| 332 |
+
VLM headline contains ACCEPTANCE-REJECTED : True
|
| 333 |
+
VLM status : CLOSED
|
| 334 |
+
router corpus_limited : True
|
| 335 |
+
router n_val : 86
|
| 336 |
+
calibration temperature (temperature_scaling.temperature) : 0.9772731820958189
|
| 337 |
+
calibration ece_improvement : -0.001174 (negative => calibration did NOT help)
|
| 338 |
+
```
|
| 339 |
+
|
| 340 |
+
These are the checks that prevent the documentation from quietly upgrading a status. In particular:
|
| 341 |
+
|
| 342 |
+
- the **VLM** metric is usable but the artifact's own headline contains `ACCEPTANCE-REJECTED` — so
|
| 343 |
+
`USABLE ≠ ACCEPTED` (rule 7);
|
| 344 |
+
- the **router** number is flagged `corpus_limited: true` with `n_val: 86` — so it can never be
|
| 345 |
+
presented as a test number (rule 5);
|
| 346 |
+
- the **calibration** `ece_improvement` is **negative**, i.e. calibration made ECE *worse*, and the
|
| 347 |
+
tool prints that interpretation inline (rule 6).
|
| 348 |
+
|
| 349 |
+
### 3.4 The nested-key resolution trick
|
| 350 |
+
|
| 351 |
+
Two subtleties in the resolver are worth recording, because they are the kind of thing that turns a
|
| 352 |
+
verifier into a **false-pass** machine if done naively (`verify_readme_metrics.py:69-90`):
|
| 353 |
+
|
| 354 |
+
1. **Keys that themselves contain dots.** The grounding recall dictionary is keyed by the string
|
| 355 |
+
`"0.10"`, `"0.25"`, `"0.50"`. A naive `dotted.split(".")` walk would break
|
| 356 |
+
`results.head_threshold.recall.0.50` into `…recall → 0 → 50` and fail. The resolver therefore
|
| 357 |
+
tries the **longest matching key at each step first**, so `0.50` stays intact.
|
| 358 |
+
2. **Precision matching.** The comparison is
|
| 359 |
+
`round(float(val), decimals(claimed)) == float(claimed)` — i.e. the artifact value is rounded to
|
| 360 |
+
the number of decimals the README prints, then compared. This is why row 15 shows the artifact
|
| 361 |
+
value `0.372308516` matching the README's `0.372309`: the README states six decimals and the
|
| 362 |
+
artifact rounds to it.
|
| 363 |
+
|
| 364 |
+
A verifier that compared raw floats would report false differences on every rounded value. A
|
| 365 |
+
verifier that split dots naively would report false differences on the recall rows. Both failure
|
| 366 |
+
modes are **silent false failures** — the same class of bug discussed in §7.3 and §10.
|
| 367 |
+
|
| 368 |
+
### 3.5 The committed output
|
| 369 |
+
|
| 370 |
+
```
|
| 371 |
+
STATUS claim artifact readme source
|
| 372 |
+
----------------------------------------------------------------------------------------------------------------------
|
| 373 |
+
MATCH change pooled IoU 0.8122 0.8122 artifacts/change/eval_test/eval_result.json#metrics.pooled.iou
|
| 374 |
+
MATCH change macro IoU 0.8457 0.8457 artifacts/change/eval_test/eval_result.json#metrics.macro.miou
|
| 375 |
+
MATCH change pooled F1 0.8964 0.8964 artifacts/change/eval_test/eval_result.json#metrics.pooled.f1
|
| 376 |
+
MATCH grounding canonical head_threshold mean_best_IoU 0.2838 0.2838 artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json#results.head_threshold.mean_best_iou
|
| 377 |
+
...
|
| 378 |
+
MATCH VLM adapter exact_match 0.963 0.963 artifacts/vlm/phase6_closure.json#why_usable_verified.adapted_test.exact_match
|
| 379 |
+
MATCH VLM adapter F1 0.96432 0.96432 artifacts/vlm/phase6_closure.json#why_usable_verified.adapted_test.f1
|
| 380 |
+
|
| 381 |
+
=== status assertions ===
|
| 382 |
+
VLM headline contains ACCEPTANCE-REJECTED : True
|
| 383 |
+
VLM status : CLOSED
|
| 384 |
+
router corpus_limited : True
|
| 385 |
+
router n_val : 86
|
| 386 |
+
calibration temperature (temperature_scaling.temperature) : 0.9772731820958189
|
| 387 |
+
calibration ece_improvement : -0.001174 (negative => calibration did NOT help)
|
| 388 |
+
|
| 389 |
+
RESULT: ALL CLAIMS VERIFIED
|
| 390 |
+
```
|
| 391 |
+
|
| 392 |
+
— `release/tools/readme_metrics_report.txt` (committed).
|
| 393 |
+
|
| 394 |
+
The tool **exits 0** and prints `ALL CLAIMS VERIFIED` only when every numeric comparison is `MATCH`
|
| 395 |
+
and no artifact is missing or lacks the key. Any `NOFILE`, `NOKEY`, or `DIFFER` line increments the
|
| 396 |
+
failure counter, the final line reads `N CLAIM(S) FAILED`, and the process exits 1. This makes the
|
| 397 |
+
check usable in CI: a non-zero exit is a hard failure.
|
| 398 |
+
|
| 399 |
+
### 3.6 Why this is a real check and not a tautology
|
| 400 |
+
|
| 401 |
+
A skeptic might object: "you wrote the README from the artifacts, so of course they match." The
|
| 402 |
+
answer is that the check is not that the README was *derived* from the artifacts, but that the
|
| 403 |
+
README's published claims remain **mechanically traceable** to files a third party also has. If
|
| 404 |
+
someone edits the README to state a nicer number, the tool fails. If someone edits an artifact to a
|
| 405 |
+
different number, the tool fails. The check binds the published prose to the recorded evidence, and
|
| 406 |
+
it does so without trusting either.
|
| 407 |
+
|
| 408 |
+
---
|
| 409 |
+
|
| 410 |
+
## 4. Reproduce the environment
|
| 411 |
+
|
| 412 |
+
### 4.1 What you need
|
| 413 |
+
|
| 414 |
+
- **Python 3.11+** (`README.md` §Installation: "Python 3.11+ and a CPU are sufficient").
|
| 415 |
+
- A **CPU is enough**. There is no CUDA requirement.
|
| 416 |
+
- Network access on first use, to fetch the pinned backbones from the Hugging Face Hub.
|
| 417 |
+
|
| 418 |
+
### 4.2 The two install profiles
|
| 419 |
+
|
| 420 |
+
`requirements.txt` documents two profiles explicitly:
|
| 421 |
+
|
| 422 |
+
```text
|
| 423 |
+
# CPU (local dev / schema / geo / unit tests):
|
| 424 |
+
# pip install -r requirements.txt
|
| 425 |
+
# GPU (Kaggle T4x2 / HF ZeroGPU): torch is preinstalled on both.
|
| 426 |
+
# Do NOT pin torch here — Kaggle and HF ship their own builds.
|
| 427 |
+
```
|
| 428 |
+
|
| 429 |
+
— `requirements.txt` (header comment).
|
| 430 |
+
|
| 431 |
+
The pins, with the contract each one carries (`requirements.txt`):
|
| 432 |
+
|
| 433 |
+
| Package | Pin | Why |
|
| 434 |
+
|---|---|---|
|
| 435 |
+
| `numpy` | `>=1.26,<3` | core |
|
| 436 |
+
| `pyyaml` | `>=6.0` | config loading |
|
| 437 |
+
| `pydantic` | `>=2.6,<3` | schema |
|
| 438 |
+
| `rasterio` | `>=1.3` | geospatial IO |
|
| 439 |
+
| `pyproj` | `>=3.6` | CRS handling |
|
| 440 |
+
| `opencv-python-headless` | `>=4.9` | image ops |
|
| 441 |
+
| `transformers` | `>=4.52` | SmolVLM via `AutoModelForImageTextToText`; `AutoModelForVision2Seq` is deprecated (finding **C-2**) |
|
| 442 |
+
| `open-clip-torch` | `>=2.24` | RemoteCLIP loaded via `pretrained=<path>` so `load_checkpoint()` runs its state-dict fixups (**C-4**) |
|
| 443 |
+
| `sentence-transformers` | `>=2.7` | router embedding |
|
| 444 |
+
| `peft` | `>=0.10` | LoRA adapter |
|
| 445 |
+
| `huggingface_hub` | `>=0.23` | pin RemoteCLIP / SmolVLM / CROMA revisions |
|
| 446 |
+
| `safetensors` | `>=0.4` | adapter serialisation |
|
| 447 |
+
| `einops` | `>=0.7` | **required** by the vendored `use_croma.py` (`from einops import rearrange`) |
|
| 448 |
+
| `gradio` | `>=4.44` | UI (frozen deployment target) |
|
| 449 |
+
| `reportlab` | `>=4.1` | reporting |
|
| 450 |
+
| `pytest` | `>=8.0` | tests |
|
| 451 |
+
| `pytest-cov` | `>=5.0` | coverage |
|
| 452 |
+
|
| 453 |
+
The comment block closes with an instruction a reviewer should heed: *"do not 'fix' by changing these
|
| 454 |
+
pins"* — the pins encode verified contracts, not preferences.
|
| 455 |
+
|
| 456 |
+
### 4.3 The `einops` lesson — a dependency no document declared
|
| 457 |
+
|
| 458 |
+
`requirements.txt` carries an unusually explicit note about `einops`:
|
| 459 |
+
|
| 460 |
+
> REQUIRED by the vendored `specialists/optical_sar/vendor/use_croma.py` (`from einops import
|
| 461 |
+
> rearrange`). Found missing on 2026-09-18 by executing the vendored module: it raised
|
| 462 |
+
> `ModuleNotFoundError`, so the CROMA path was blocked on a dependency that no document declared.
|
| 463 |
+
> Not optional: without it the encoder cannot be imported at all.
|
| 464 |
|
| 465 |
+
This is recorded verbatim because it is a reproducibility fact: a missing dependency that no
|
| 466 |
+
document mentioned was found **by executing the code**, not by reading a spec. A third party
|
| 467 |
+
building the environment will hit the same wall if they install from a partial list.
|
| 468 |
|
| 469 |
+
### 4.4 Create the environment
|
| 470 |
|
| 471 |
```bash
|
| 472 |
git clone https://github.com/Anish-lab-blip/SatQuery-AI
|
| 473 |
cd SatQuery-AI
|
| 474 |
python -m venv .venv
|
| 475 |
+
source .venv/Scripts/activate # Windows git-bash; use .venv/bin/activate on Linux/macOS
|
| 476 |
pip install -r requirements.txt
|
| 477 |
```
|
| 478 |
|
| 479 |
+
— `README.md` §Installation.
|
|
|
|
|
|
|
| 480 |
|
| 481 |
+
> **Trap (Windows, recorded in §10.2).** In the authoring environment, `pytest` exists **only** in
|
| 482 |
+
> the repository virtualenv. Invoking the system `pytest` fails or resolves to a different
|
| 483 |
+
> interpreter. Always invoke the venv interpreter explicitly:
|
| 484 |
+
> `.venv/Scripts/python.exe -m pytest …`.
|
| 485 |
|
| 486 |
+
### 4.5 Backbones are fetched, pinned by revision, and never redistributed
|
| 487 |
+
|
| 488 |
+
The four third-party backbones are pinned by `repo_id` **and** `revision` (`README.md`
|
| 489 |
+
§Reproducibility item 2; `models/manifest.json` per-artifact `backbone` fields):
|
| 490 |
+
|
| 491 |
+
| Model | Revision | Role |
|
| 492 |
|---|---|---|
|
| 493 |
+
| `HuggingFaceTB/SmolVLM-500M-Instruct` | `a7da5b986cb5` | VQA + captioning backbone |
|
| 494 |
+
| `chendelong/RemoteCLIP` | `bf1d8a3ccf2d` | remote-sensing grounding encoder |
|
| 495 |
+
| `sentence-transformers/all-MiniLM-L6-v2` | `1110a243fdf4` | router embedding |
|
| 496 |
+
| `antofuller/CROMA` | `0dd28e3d633b` | optical/SAR fusion encoder |
|
| 497 |
+
|
| 498 |
+
**No backbone weights are redistributed** in this release (`release/HF_RELEASE_VERIFICATION.md §4`).
|
| 499 |
+
The released artifacts are small trained modules *over* these frozen backbones; anyone using them
|
| 500 |
+
must fetch the backbones too. Pinning by commit prefix (not by a floating tag like `main`) is what
|
| 501 |
+
makes "the same backbone" reproducible months later.
|
| 502 |
+
|
| 503 |
+
### 4.6 Device selection
|
| 504 |
+
|
| 505 |
+
Device is selected via `SATQUERY_DEVICE`, and **all placement is `.to(device)`, never `.cuda()`**
|
| 506 |
+
(`README.md` §Installation; `core/config.py:86-91`):
|
| 507 |
+
|
| 508 |
+
```python
|
| 509 |
+
@property
|
| 510 |
+
def device_preference(self) -> str:
|
| 511 |
+
override = os.environ.get("SATQUERY_DEVICE")
|
| 512 |
+
if override:
|
| 513 |
+
return override
|
| 514 |
+
return "cuda" if _torch_cuda_available() else "cpu"
|
| 515 |
+
```
|
| 516 |
+
|
| 517 |
+
This is why a CPU-only reviewer can run everything except the training paths. The env override is
|
| 518 |
+
read **without importing torch** in the deployment context (`docs/DEPLOYMENT.md §3.2`), so a
|
| 519 |
+
misconfigured device cannot crash the service at import time.
|
| 520 |
+
|
| 521 |
+
---
|
| 522 |
+
|
| 523 |
+
## 5. Reproduce the tests
|
| 524 |
+
|
| 525 |
+
### 5.1 The suites and their expected results
|
| 526 |
+
|
| 527 |
+
| Suite | Command | Expected | Status |
|
| 528 |
+
|---|---|---|---|
|
| 529 |
+
| Frontend live-wiring | `python -m pytest tests/unit/test_frontend_live_wiring.py -q` | **106 passed** | `VERIFIED` |
|
| 530 |
+
| Doc/frontend suite | `pytest` on the 5 doc/frontend files | **183 passed** | `VERIFIED` |
|
| 531 |
+
| Full unit suite | `python -m pytest tests/unit` | **5–6 environmental/ordering failures**, rest pass; a re-run passes 137 | `MEASURED` |
|
| 532 |
+
|
| 533 |
+
The three rows tell a story a single number would hide: the two **targeted** suites are clean, and
|
| 534 |
+
the **full** suite has 5–6 failures that are environmental or ordering-related, not regressions. The
|
| 535 |
+
next subsections reproduce each.
|
| 536 |
+
|
| 537 |
+
### 5.2 Frontend live-wiring regression suite — 106 passed
|
| 538 |
+
|
| 539 |
+
```bash
|
| 540 |
+
python -m pytest tests/unit/test_frontend_live_wiring.py -q
|
| 541 |
+
```
|
| 542 |
+
|
| 543 |
+
— `README.md` §Local development.
|
| 544 |
+
|
| 545 |
+
This suite is the regression net for the frontend↔backend wiring: the query box, the intent panel,
|
| 546 |
+
the task tags, the run identifiers, and the evidence rendering. It is the suite that would catch a
|
| 547 |
+
re-introduction of the router defect described in §6.4.
|
| 548 |
+
|
| 549 |
+
**Expected: 106 passed.** Note the historical figure, because a reviewer may see both numbers:
|
| 550 |
+
`docs/FINAL_DELIVERY_REPORT.md §7` records **94 passed** for this file, while
|
| 551 |
+
`CURRENT_RELEASE_STATE.md:121` and `README.md` §The test suites record **106 passed** ("re-run this
|
| 552 |
+
session"). The difference is the suite growing after the delivery report was written — the
|
| 553 |
+
regression tests added for the router fix took this file from 100 → 106 tests
|
| 554 |
+
(`DELIVERY_REPORT_2026-09-25.md`, regression-tests note). This document quotes **106**, the
|
| 555 |
+
re-run-this-session figure, and cites both. (The exact count of tests *collected* in a single
|
| 556 |
+
full-suite invocation is `UNKNOWN — not established from the available evidence`; the per-suite
|
| 557 |
+
counts are what the evidence establishes.)
|
| 558 |
+
|
| 559 |
+
### 5.3 Doc/frontend suite — 183 passed
|
| 560 |
+
|
| 561 |
+
The documentation-and-frontend suite is **five files**, named explicitly in
|
| 562 |
+
`docs/FINAL_DELIVERY_REPORT.md §7`:
|
| 563 |
+
|
| 564 |
+
- `test_frontend_guide_doc`
|
| 565 |
+
- `test_frontend_live_wiring`
|
| 566 |
+
- `test_api_contract_doc`
|
| 567 |
+
- `test_runbook_doc`
|
| 568 |
+
- `test_deploy_config`
|
| 569 |
+
|
| 570 |
+
Together they report **183 passed**. These are the checks that the docs' internal links resolve, the
|
| 571 |
+
frontend assets are present, the API contract document matches the code, the runbook is consistent,
|
| 572 |
+
and the deploy config is coherent.
|
| 573 |
+
|
| 574 |
+
The same three-row test table is published in this release at `README.md` §The test suites
|
| 575 |
+
(`README.md:1063-1074`), so a public reader can see the counts without the project-internal report.
|
| 576 |
+
The underlying record is `docs/FINAL_DELIVERY_REPORT.md §7` (project-internal); the frontend count
|
| 577 |
+
was re-run this session to **106** (`release/CURRENT_RELEASE_STATE.md:121`). See also
|
| 578 |
+
[`EVALUATION.md`](EVALUATION.md) §7.2.
|
| 579 |
+
|
| 580 |
+
### 5.4 Full `tests/unit` — the 5–6 failures, reported honestly
|
| 581 |
+
|
| 582 |
+
Running the entire unit tree trips **5–6** failures. They are reported here rather than hidden, and
|
| 583 |
+
they are classified by cause exactly as `docs/FINAL_DELIVERY_REPORT.md §7` records them:
|
| 584 |
+
|
| 585 |
+
| # | Failure | Cause | Regression? |
|
| 586 |
+
|---|---|---|---|
|
| 587 |
+
| 1–4 | `test_safe_delete_shim` (×4) | the sandbox's bulk-**delete guard** (Windows verbatim-path behaviour) | **No** |
|
| 588 |
+
| 5 | one ordering flake in the **router route test** | passes in isolation; order/collection-dependent | **No** |
|
| 589 |
+
| 6 | one **stale adapter test** | asserts `optical_sar` absent when CROMA is *unshipped* — **CROMA is now shipped** | **No** |
|
| 590 |
+
|
| 591 |
+
**Why these are not regressions.** They were isolated by a re-run of the affected files **together**,
|
| 592 |
+
which passes **137** tests (`docs/FINAL_DELIVERY_REPORT.md §7`;
|
| 593 |
+
[`EVALUATION.md`](EVALUATION.md) §7.3–7.4). The `test_safe_delete_shim` failures are Windows-specific
|
| 594 |
+
verbatim-path behaviour that does not occur on the deployment host; the ordering flake disappears when
|
| 595 |
+
the router route test runs in isolation; the stale adapter test encodes an assumption (CROMA not yet
|
| 596 |
+
shipped) that the codebase has since outgrown. None of them is a behavioural defect in shipped code.
|
| 597 |
+
|
| 598 |
+
> **Honesty note.** The precise full-suite *collected* count is
|
| 599 |
+
> `UNKNOWN — not established from the available evidence`. What the evidence establishes is: the two
|
| 600 |
+
> targeted suites pass (106, 183), the full suite has 5–6 environmental/ordering failures, and a
|
| 601 |
+
> re-run of the affected files passes 137.
|
| 602 |
+
|
| 603 |
+
### 5.5 The evidence-engine determinism suite
|
| 604 |
+
|
| 605 |
+
The evidence engine (the component that turns specialist output into a *proof*) has its own suite —
|
| 606 |
+
`tests/unit/test_evidence_engine.py` (73 tests) — covering **determinism** and **purity**: the same
|
| 607 |
+
input produces the same evidence, and computing evidence does not mutate inputs. This is what makes
|
| 608 |
+
the "evidence is independent of the verdict" property in §6.5 hold.
|
| 609 |
+
|
| 610 |
+
### 5.6 Invocation traps
|
| 611 |
+
|
| 612 |
+
- **Use the venv interpreter.** `.venv/Scripts/python.exe -m pytest …` on Windows.
|
| 613 |
+
- **The full-suite run trips a bulk-delete guard** in the authoring sandbox (§10.3). Run the
|
| 614 |
+
targeted suites to avoid it.
|
| 615 |
+
- **Do not pipe pytest through `grep`** in the authoring sandbox — output is block-buffered and a
|
| 616 |
+
killed pipeline swallows it (§10.6). Redirect to a file instead.
|
| 617 |
+
|
| 618 |
+
---
|
| 619 |
+
|
| 620 |
+
## 6. Reproduce a live run
|
| 621 |
+
|
| 622 |
+
### 6.1 The deployed stack
|
| 623 |
+
|
| 624 |
+
The live topology is **Cloudflare Pages → Render → outbound tunnel → GitHub Codespace**
|
| 625 |
+
(`docs/DEPLOYMENT.md §1`; `README.md` §Deployment):
|
| 626 |
+
|
| 627 |
+
| Layer | Role | Host |
|
| 628 |
+
|---|---|---|
|
| 629 |
+
| Cloudflare Pages | static frontend | `https://satquery.pages.dev` |
|
| 630 |
+
| Render | orchestrator / API gateway (`/api/*`, CORS, wake flow) | `https://satquery-backend-m4yv.onrender.com` |
|
| 631 |
+
| GitHub Codespace | FastAPI inference host, CPU, port 8000 | `potential-space-trout-r4ppw969w45j2pvvw` |
|
| 632 |
+
|
| 633 |
+
Deployed revisions (VERIFIED):
|
| 634 |
|
| 635 |
+
| Component | Revision |
|
| 636 |
+
|---|---|
|
| 637 |
+
| Frontend | `2d7ae53b482d` |
|
| 638 |
+
| Backend / orchestrator | `89d80eaddec5` |
|
| 639 |
+
| Inference | `5a0936ace491` |
|
| 640 |
|
| 641 |
+
> **Trap.** The deployment sources are **private** repositories, separate from this release. The
|
| 642 |
+
> `deploy/` directory inside the monorepo working copy is **stale and untracked** — it is *not* the
|
| 643 |
+
> deployed source (`docs/DEPLOYMENT.md §1`). Reproducing a live run means driving the deployed
|
| 644 |
+
> endpoints, not rebuilding from `deploy/`.
|
| 645 |
|
| 646 |
+
### 6.2 Health and capabilities
|
| 647 |
|
| 648 |
```bash
|
| 649 |
curl --noproxy '*' https://satquery-backend-m4yv.onrender.com/api/health
|
| 650 |
curl --noproxy '*' https://satquery-backend-m4yv.onrender.com/api/capabilities
|
| 651 |
```
|
| 652 |
|
| 653 |
+
The `/api/health` payload (recorded live, `docs/DEPLOYMENT.md §2`):
|
| 654 |
+
|
| 655 |
+
```json
|
| 656 |
+
{"status":"ok","service":"satquery-orchestrator",
|
| 657 |
+
"tunnel":{"agent_connected":true,"agent_id":"codespaces-fd1038","pending":0,"completed":97},
|
| 658 |
+
"config":{"codespace_name":"potential-space-trout-r4ppw969w45j2pvvw\n","codespace_port":8000,
|
| 659 |
+
"transport_mode":"auto","tunnel_timeout_s":150.0,"wake_timeout_s":120.0,
|
| 660 |
+
"upstream_timeout_s":90.0,"device":"cpu","has_github_token":true}}
|
| 661 |
+
```
|
| 662 |
+
|
| 663 |
+
`/api/capabilities` returns **six tasks, all `available: true`**. A live run requires
|
| 664 |
+
`tunnel.agent_connected: true`; if the Codespace is stopped, the request parks until the tunnel
|
| 665 |
+
timeout (`docs/DEPLOYMENT.md §5`).
|
| 666 |
+
|
| 667 |
+
> **`--noproxy '*'` is not optional in the authoring sandbox** (§10.1). The sandbox proxy is dead;
|
| 668 |
+
> without the flag, the request fails before reaching Render. In a normal environment the flag is
|
| 669 |
+
> harmless.
|
| 670 |
+
|
| 671 |
+
### 6.3 What was run, and how to re-run it
|
| 672 |
+
|
| 673 |
+
The behavioural validation is **3 passes × 8 cases = 24 live runs**, each pass **8/8**, with **0
|
| 674 |
+
mock nodes** and a live trace bar at **94.4444 %** (`.workbuddy-ai/scratch/live_validation/
|
| 675 |
+
LIVE_VALIDATION_POSTFIX.md`). The eight cases exercise all six specialist tasks plus the two
|
| 676 |
+
router-defect queries (B1, B2).
|
| 677 |
+
|
| 678 |
+
| pass | HEAD | harness | result |
|
| 679 |
+
|---|---|---|---|
|
| 680 |
+
| 1 | `ff46eba42b18`+`d413d3672311` | v1 (`fill_input`) | 8/8 |
|
| 681 |
+
| 2 | `2d7ae53b482d` | v2 asserting | 8/8 |
|
| 682 |
+
| 3 | `2d7ae53b482d` | v2 asserting (pre-discriminator-fix) | 8/8 (recomputed) |
|
| 683 |
+
|
| 684 |
+
To re-run: drive `https://satquery.pages.dev` in a **headed** browser (the headed requirement is
|
| 685 |
+
explained in §6.4), upload **one asset per case** for single-image tasks and a **pair** for the
|
| 686 |
+
temporal tasks, submit the query, and read the run identifier, the `mock_nodes` count, the trace
|
| 687 |
+
fill, and the answer's `[task]` tag. The eight expected cases are tabulated in
|
| 688 |
+
[`EVALUATION.md`](EVALUATION.md) §6.2.
|
| 689 |
+
|
| 690 |
+
### 6.4 The Chrome focus trap — why the harness asserts
|
| 691 |
+
|
| 692 |
+
Pass 1's harness drove the query box with `fill_input()`, which types with **real CDP key events**.
|
| 693 |
+
A re-run attempt failed on case 1 with `run_id=0002`, `mock_nodes=9`, `answer="No answer yet"`, and
|
| 694 |
+
only a `capabilities` call — the **mock path**. Root cause:
|
| 695 |
+
|
| 696 |
+
> Chrome **drops synthesized key events when the browser window does not hold OS focus**; the harness
|
| 697 |
+
> had **no assertion**, so it clicked Run with the page's *default* query still in the box.
|
| 698 |
+
|
| 699 |
+
Measured directly: with Chrome backgrounded, `press_key("Z")` left `#qtext.value` unchanged, while
|
| 700 |
+
`type_text("Q")` (CDP `Input.insertText`, not focus-gated) inserted fine
|
| 701 |
+
(`LIVE_VALIDATION_POSTFIX.md`, "Why this pass needed a new harness").
|
| 702 |
+
|
| 703 |
+
This is the single most important trap for anyone reproducing a live run, because it produces a
|
| 704 |
+
**silent false pass** — the pipeline "works", it just answered a different question. The fix was to
|
| 705 |
+
**assert the input state before dispatching**, with three pre-dispatch assertions:
|
| 706 |
+
|
| 707 |
+
- `q_ok` — the query box really held the query;
|
| 708 |
+
- `obs_ok` — `#obsTail == 'ready'`;
|
| 709 |
+
- `t0_ok` — both frames present where required.
|
| 710 |
+
|
| 711 |
+
The full account, including the check that pass 1 was **not** infected, is in
|
| 712 |
+
[`EVALUATION.md`](EVALUATION.md) §6.4–6.5. In short: pass 1's intents are query-specific (A1 reads
|
| 713 |
+
`taskvqa…temporalnone`, not the default's `taskchange…temporalrequired`), their answers embed the
|
| 714 |
+
query text, and A6's answer proves two files were uploaded — so pass 1's results are clean.
|
| 715 |
+
|
| 716 |
+
### 6.5 The recompute-verdicts safety property
|
| 717 |
+
|
| 718 |
+
Pass 3 was launched with a harness build that still carried two *discriminator* bugs (the answer
|
| 719 |
+
`[task]` tag exists only for region tasks; the intent panel is a concatenated string needing a
|
| 720 |
+
non-greedy match). Its raw output says `SUMMARY 0/8`; the verdicts are recomputed from the recorded
|
| 721 |
+
evidence by `recompute_verdicts.py`, giving 8/8. This is not a workaround — it is the intended
|
| 722 |
+
safety property:
|
| 723 |
+
|
| 724 |
+
> The recorded evidence (run id, `mock_nodes`, intent, answer, assertions) is **independent of the
|
| 725 |
+
> verdict computation**, so a harness bug can never silently turn a real failure into a pass, and
|
| 726 |
+
> never costs a re-run to correct.
|
| 727 |
+
|
| 728 |
+
— `LIVE_VALIDATION_POSTFIX.md`, Pass 3.
|
| 729 |
+
|
| 730 |
+
Two further harness bugs were found and fixed, both causing **false failures** (not false passes):
|
| 731 |
+
the answer `[task]` tag exists only for region tasks, and the intent panel needs a non-greedy match.
|
| 732 |
+
The harness now computes the dispatched task as `answer_tag` when present, else the reading. Both
|
| 733 |
+
are recorded because a false failure and a false pass are different risks and the project tracks
|
| 734 |
+
them separately.
|
| 735 |
+
|
| 736 |
+
### 6.6 Cold start and tunnel caveats
|
| 737 |
+
|
| 738 |
+
- **Cold start.** Render's free tier sleeps and the Codespace may be stopped. The first request can
|
| 739 |
+
exceed the client timeout while weights are fetched; a retry a few seconds later normally
|
| 740 |
+
succeeds. Warm the stack before any demonstration and confirm `agent_connected: true`
|
| 741 |
+
(`README.md` §Deployment caveats).
|
| 742 |
+
- **Tunnel gaps (B-07, `OPEN`).** The tunnel agent can be briefly absent; a request during a gap may
|
| 743 |
+
hang or return 504. **This is not fixed in production.** Root cause: in `auto` mode a tunnel
|
| 744 |
+
timeout falls through to the forwarded-port path, spending the 120 s wake timeout on a 302 — the
|
| 745 |
+
observed ~249 s failure (`docs/DEPLOYMENT.md §6`). A patch exists and was deliberately **not**
|
| 746 |
+
deployed.
|
| 747 |
+
- **Never retry `POST /api/infer` at the gateway** — a retry consumes inference twice
|
| 748 |
+
(`docs/DEPLOYMENT.md §7`).
|
| 749 |
+
|
| 750 |
+
---
|
| 751 |
+
|
| 752 |
+
## 7. Reproduce and verify the released artifacts
|
| 753 |
+
|
| 754 |
+
### 7.1 The manifest and checksums are generated, not typed
|
| 755 |
+
|
| 756 |
+
`models/manifest.json` and `models/checksums.sha256` are produced by
|
| 757 |
+
`release/tools/generate_model_manifest.py`, which reads the actual files:
|
| 758 |
+
|
| 759 |
+
> Generated by reading the files. No byte count or hash is typed by hand. Backbones are NOT
|
| 760 |
+
> redistributed; they are fetched from the Hugging Face Hub, pinned by revision.
|
| 761 |
+
|
| 762 |
+
— `models/manifest.json` (`"note"` field).
|
| 763 |
+
|
| 764 |
+
Verify the local weights:
|
| 765 |
+
|
| 766 |
+
```bash
|
| 767 |
+
sha256sum -c models/checksums.sha256
|
| 768 |
+
```
|
| 769 |
+
|
| 770 |
+
`models/checksums.sha256` (committed):
|
| 771 |
+
|
| 772 |
+
```
|
| 773 |
+
c5ef31277b67aa01a593aec0eac503eeaccc6d674349fda20ca44c9cc6f8e9fa change/head.pt
|
| 774 |
+
cfae5e43b97ca930f568dc5b8ae4f36b24e9ff717af226159802206ffd63a82a change_vqa/head.pt
|
| 775 |
+
785815729a3a39fc34dc41894efaf00d8739365d970a3f830a326e68ae888dab optical_sar/head.pt
|
| 776 |
+
93432f7034be91a8ffd9c1a84e3eeec00bed7832c043fe7f83d2be230284c6bb grounding/head.pt
|
| 777 |
+
8527c3ed28a293e13293d48601d48e3ceafa137b9acabddaf5de31a58a509b5c router/adapter.pt
|
| 778 |
+
07c76a75fa04624880ed7730590f5fdd7b145a8232e3c0af411c3c545a5adf5e vlm/adapter_model.safetensors
|
| 779 |
+
```
|
| 780 |
+
|
| 781 |
+
The manifest records, per artifact: `id`, `task`, `kind`, `path` (in the project), `hf_path` (on the
|
| 782 |
+
Hub), `backbone`, `architecture`, `source_metric_artifact`, `config_hash`, `bytes`, `sha256`, and
|
| 783 |
+
`status`. The manifest's own header records `config_hash: 78f1e3700da15aa1` and
|
| 784 |
+
`artifact_count: 6`, generated `2026-09-25T18:15:38+00:00`.
|
| 785 |
+
|
| 786 |
+
### 7.2 Independent Hub verification
|
| 787 |
+
|
| 788 |
+
```bash
|
| 789 |
+
export HF_TOKEN=... # token with repo.content.read
|
| 790 |
+
python release/tools/hf_verify.py
|
| 791 |
+
```
|
| 792 |
+
|
| 793 |
+
`release/tools/hf_verify.py` is an **independent** check: it does not trust the upload step. It
|
| 794 |
+
reads the manifest, **re-downloads each artifact from the Hub over direct HTTPS**, hashes the bytes
|
| 795 |
+
it received, and compares against the locally-computed sha256. Its docstring is explicit that it
|
| 796 |
+
uses direct HTTPS *deliberately*:
|
| 797 |
+
|
| 798 |
+
> Note: `hf_hub_download` is deliberately NOT used here — in this environment it returned an empty
|
| 799 |
+
> file (sha256 e3b0c442…), which would have produced a false FAIL. Direct HTTPS is the honest check.
|
| 800 |
+
|
| 801 |
+
Recorded result (`release/tools/hf_verify_report.txt`):
|
| 802 |
+
|
| 803 |
+
```
|
| 804 |
+
repo : thundercode/SatQuery
|
| 805 |
+
private : False
|
| 806 |
+
sha (HEAD) : 55681e0cddb91a4a5655da98a49bc025e537b657
|
| 807 |
+
lastModified : 2026-09-25T18:21:52.000Z
|
| 808 |
+
files on Hub : 22
|
| 809 |
+
|
| 810 |
+
STATUS hf_path remote bytes local bytes
|
| 811 |
+
----------------------------------------------------------------------------------
|
| 812 |
+
MATCH change/head.pt 63,231,009 63,231,009
|
| 813 |
+
MATCH change_vqa/head.pt 5,822,809 5,822,809
|
| 814 |
+
MATCH optical_sar/head.pt 14,427,457 14,427,457
|
| 815 |
+
MATCH grounding/head.pt 12,639,041 12,639,041
|
| 816 |
+
MATCH router/adapter.pt 211,961 211,961
|
| 817 |
+
MATCH vlm/adapter_model.safetensors 34,798,048 34,798,048
|
| 818 |
+
|
| 819 |
+
artifacts verified : 6
|
| 820 |
+
artifacts failed : 0
|
| 821 |
+
|
| 822 |
+
OK README.md
|
| 823 |
+
OK MODEL_CARD.md
|
| 824 |
+
OK models/manifest.json
|
| 825 |
+
OK models/checksums.sha256
|
| 826 |
+
```
|
| 827 |
+
|
| 828 |
+
Total released weight payload: **131,130,325 bytes (~125 MiB)** across the six artifacts
|
| 829 |
+
(`release/HF_RELEASE_VERIFICATION.md §4`). Exit code 0 with `artifacts failed : 0` means the release
|
| 830 |
+
is intact.
|
| 831 |
+
|
| 832 |
+
### 7.3 A verification method that was itself wrong — the empty-file false FAIL
|
| 833 |
+
|
| 834 |
+
This is the most instructive reproducibility incident in the release, and it is recorded because a
|
| 835 |
+
verifier that silently hashes an **empty file** is a verifier that can lie in *both* directions.
|
| 836 |
+
|
| 837 |
+
The **first** verification attempt reported all six artifacts `DIFFER`, with every remote hash equal
|
| 838 |
+
to:
|
| 839 |
+
|
| 840 |
+
```
|
| 841 |
+
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
| 842 |
+
```
|
| 843 |
+
|
| 844 |
+
— the sha256 of **empty content**. The cause was **not** the upload: `hf_hub_download` returned an
|
| 845 |
+
empty file in the authoring environment (a download-path problem), so the verifier hashed nothing.
|
| 846 |
+
|
| 847 |
+
It was caught by a **second, independent method** — a direct `curl` download — which produced the
|
| 848 |
+
correct hash `8527c3ed28a293e13293d48601d48e3ceafa137b9acabddaf5de31a58a509b5c` for
|
| 849 |
+
`router/adapter.pt`, byte-identical to the local file and confirmed to be a real PyTorch zip
|
| 850 |
+
(`PK\x03\x04`, containing `adapter/data.pkl`). The verifier was then rewritten to use direct HTTPS
|
| 851 |
+
with proxies disabled (`ProxyHandler({})`), and now reports 6/6 `MATCH`.
|
| 852 |
+
|
| 853 |
+
`release/HF_RELEASE_VERIFICATION.md §5.1` states the lesson directly:
|
| 854 |
+
|
| 855 |
+
> The failed first attempt is recorded because a verifier that silently hashes an empty file would
|
| 856 |
+
> have produced a **false failure** — and, with a different bug, could just as easily have produced
|
| 857 |
+
> a **false pass**.
|
| 858 |
+
|
| 859 |
+
**What a third party should take from this.** Do not use `hf_hub_download` as the *sole* verification
|
| 860 |
+
path. The honest check is a direct HTTPS download with proxies disabled, hashing the bytes received,
|
| 861 |
+
and comparing sizes as well as hashes. `hf_verify.py` does all three
|
| 862 |
+
(`size_ok = remote_bytes == local_bytes == len(content)`; `hash_ok = remote_hash == local_hash`).
|
| 863 |
+
|
| 864 |
+
### 7.4 Cross-checks against independently-recorded hashes
|
| 865 |
|
| 866 |
+
Two of the six hashes can be checked against values recorded **elsewhere** in the project,
|
| 867 |
+
independent of this release (`release/HF_RELEASE_VERIFICATION.md §6`):
|
| 868 |
|
| 869 |
+
| Artifact | Recorded elsewhere | Computed here | Agree |
|
| 870 |
+
|---|---|---|---|
|
| 871 |
+
| `change_vqa_head` | `cfae5e43…d63a82a` (`artifacts/change_vqa/run/PROMOTION.json`) | `cfae5e43…d63a82a` | **yes** |
|
| 872 |
+
| `vlm_lora_adapter` | `07c76a75…a5adf5e` (adapter provenance) | `07c76a75…a5adf5e` | **yes** |
|
| 873 |
+
|
| 874 |
+
This is a stronger form of verification than self-consistency: it means the released bytes match
|
| 875 |
+
hashes that were recorded by the training/export process, not merely by the release step.
|
| 876 |
+
|
| 877 |
+
### 7.5 The evidence archive
|
| 878 |
+
|
| 879 |
+
`release/tools/build_archive.py` builds the Phase-7 evidence archive. Per owner decision **G3**,
|
| 880 |
+
everything under `artifacts/` is included **verbatim** — including duplicates and caches — so the
|
| 881 |
+
archive is **~4 GB** and requires ZIP64. The layout:
|
| 882 |
+
|
| 883 |
+
```
|
| 884 |
+
SatQuery_AI_Final_Archive_2026-09-25/
|
| 885 |
+
README_ARCHIVE.md (what is inside, what was excluded and why)
|
| 886 |
+
release/ (the curated public release tree, verbatim)
|
| 887 |
+
artifacts/ (VERBATIM, per owner decision G3)
|
| 888 |
+
evidence/
|
| 889 |
+
live_validation/ (3 validation passes + screenshots)
|
| 890 |
+
delivery/ (delivery report + handoff)
|
| 891 |
+
verification/ (state, manifests, verification reports)
|
| 892 |
+
```
|
| 893 |
+
|
| 894 |
+
Weights and caches are stored with `ZIP_STORED` (no recompression) to avoid wasting hours on
|
| 895 |
+
already-compressed data. The archive **excludes** secret/token files, OS junk, virtualenvs,
|
| 896 |
+
`node_modules`, the HF hub cache, and temp browser profiles — documented in `README_ARCHIVE.md`,
|
| 897 |
+
never silent. It is verified by `release/tools/verify_archive.py`, which extracts to a separate temp
|
| 898 |
+
directory and checks the CRC of every member plus the sha256 of every artifact against the live
|
| 899 |
+
files.
|
| 900 |
+
|
| 901 |
+
---
|
| 902 |
+
|
| 903 |
+
## 8. What "reproduce" means for the two externally-trained artifacts
|
| 904 |
+
|
| 905 |
+
Six trained artifacts are released. **Four were trained inside the project's own pipeline; two were
|
| 906 |
+
trained outside this repository** on external GPU. This distinction is the heart of the
|
| 907 |
+
reproducibility contract, so it is stated plainly rather than implied.
|
| 908 |
|
| 909 |
| Artifact | Where it trains | Guide |
|
| 910 |
|---|---|---|
|
| 911 |
+
| `router/adapter.pt` | local CPU | `configs/base.yaml` §router |
|
| 912 |
+
| `grounding/head.pt` | local | `configs/base.yaml` §grounding_training |
|
| 913 |
+
| `change/head.pt` | local | `configs/base.yaml` §change |
|
| 914 |
+
| `optical_sar/head.pt` | local, seed sweep | `docs/PHASE14_OPTICAL_SAR_DECISIONS.md` |
|
| 915 |
+
| `change_vqa/head.pt` | **external GPU (Kaggle)** | `docs/R02_KAGGLE_TRAINING_GUIDE.md` |
|
| 916 |
+
| `vlm/adapter_model.safetensors` | **external GPU** | `configs/base.yaml` §training |
|
| 917 |
+
|
| 918 |
+
### 8.1 What the repository reproduces for the external artifacts
|
| 919 |
+
|
| 920 |
+
For the two externally-trained artifacts, this repository reproduces:
|
| 921 |
+
|
| 922 |
+
1. **The promotion gate** — the checks the returned checkpoint must pass before it is accepted
|
| 923 |
+
(byte-identity, sha256 match against the recorded digest, zero non-finite tensors).
|
| 924 |
+
2. **The evaluation** — the scoring path that turns a checkpoint into the recorded metrics.
|
| 925 |
+
3. **The serving wiring** — the code path that loads the artifact and routes to it.
|
| 926 |
+
|
| 927 |
+
It does **not** ship a one-command retrain for those two artifacts. That is stated, not implied.
|
| 928 |
+
|
| 929 |
+
### 8.2 The change-VQA path (Kaggle)
|
| 930 |
+
|
| 931 |
+
`docs/R02_KAGGLE_TRAINING_GUIDE.md` is the authoritative guide; it states its own scope:
|
| 932 |
+
|
| 933 |
+
> **Audience:** the person running the external GPU job. **Scope:** everything needed to go from
|
| 934 |
+
> this repository to a returned, reviewable checkpoint. **Not in scope:** deciding whether R-02 is
|
| 935 |
+
> done — that happens after the artifact comes back.
|
| 936 |
+
|
| 937 |
+
Its status is `IMPLEMENTATION_READY_FOR_EXTERNAL_TRAINING`, and it is explicit that *"training
|
| 938 |
+
produces an artifact, not a verified capability, and the run record says `TRAINED_UNVERIFIED`."*
|
| 939 |
+
|
| 940 |
+
The reproduction is **byte-scoped**: the upload set is defined by a digest so the external run can be
|
| 941 |
+
shown to have started from exactly this code.
|
| 942 |
+
|
| 943 |
+
| Item | Value |
|
| 944 |
+
|---|---|
|
| 945 |
+
| Upload set | **249 files**, at the root of the archive |
|
| 946 |
+
| Digest-defining subset | **248 files, 67,225,247 bytes** |
|
| 947 |
+
| Manifest hash | `e98d3854db5f3edec76c1748c0c1bc3041d006ae5858debee31701539af7d0d6` |
|
| 948 |
+
| Archive | `satquery-ai.zip` — 250 entries, 60.0 MB compressed |
|
| 949 |
+
| Inside the archive | `UPLOAD_MANIFEST.txt` — every file with size and SHA256, plus the digest |
|
| 950 |
+
|
| 951 |
+
The digest covers **248 files, not 249**, for a principled reason the guide states: the guide itself
|
| 952 |
+
lives in `docs/`, so it is part of the upload set — but it also *records* the digest, and a document
|
| 953 |
+
cannot contain the hash of itself. The digest is therefore defined over the upload set **excluding
|
| 954 |
+
this one file**; that is the only definition that can be reproduced. For the same reason the byte
|
| 955 |
+
total for all 249 files is **not** quoted in the guide — it changes whenever the guide is edited; the
|
| 956 |
+
authoritative value is in `UPLOAD_MANIFEST.txt`.
|
| 957 |
+
|
| 958 |
+
The frozen STANet checkpoint the head depends on is verified by size and SHA256 in the notebook:
|
| 959 |
+
|
| 960 |
+
```
|
| 961 |
+
checkpoint : <CODE_ROOT>/artifacts/change/levir_change_v001/head.pt
|
| 962 |
+
bytes : 63,231,009
|
| 963 |
+
sha256 : c5ef31277b67aa01a593aec0eac503eeaccc6d674349fda20ca44c9cc6f8e9fa
|
| 964 |
+
checkpoint verified against the frozen digest
|
| 965 |
+
```
|
| 966 |
+
|
| 967 |
+
Training knobs are fixed, not tuned-to-pass: `HARD_STOP_SECONDS = 3 * 3600`, `EPOCHS = 40`,
|
| 968 |
+
`BATCH_SIZE = 256`, `DEVICE = "cuda"`, `SEED = 42` (the trainer's own defaults are
|
| 969 |
+
`epochs=40, batch_size=128, seed=42, patience=6, time_limit=10800s`). The guide instructs: *"Do not
|
| 970 |
+
change these to get past an error."*
|
| 971 |
+
|
| 972 |
+
Split integrity is measured, not assumed:
|
| 973 |
+
|
| 974 |
+
```
|
| 975 |
+
measured corpus ground truth (unique scenes / resolved questions):
|
| 976 |
+
Train 1600 scenes 65967 questions
|
| 977 |
+
Val 400 scenes 16441 questions
|
| 978 |
+
Test 968 scenes 39686 questions
|
| 979 |
+
Test2 968 scenes 31036 questions
|
| 980 |
+
integrity clean : True
|
| 981 |
+
scenes/split : {'Train': 1600, 'Val': 400}
|
| 982 |
+
overlap : {'Train|Val': 0}
|
| 983 |
+
```
|
| 984 |
|
| 985 |
+
The two test sets (Test 39686, Test2 31036) are why change-VQA is quoted under **two** test sets
|
| 986 |
+
(rule 3) — and why the promotion artifact carries both.
|
| 987 |
|
| 988 |
+
### 8.3 The VLM path
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
|
| 990 |
+
The VLM artifact is a PEFT LoRA adapter (`r=16, alpha=32, dropout=0.05`) on the text-model
|
| 991 |
+
projections of `HuggingFaceTB/SmolVLM-500M-Instruct` (`models/manifest.json`). Its acceptance status
|
| 992 |
+
is **`ACCEPTANCE-REJECTED`** — the metrics are usable (`exact_match 0.963`, `f1 0.96432`) but the
|
| 993 |
+
adapter was **not promoted**. `USABLE ≠ ACCEPTED` (rule 7). Reproducing this artifact's *training*
|
| 994 |
+
requires the external GPU environment described in `configs/base.yaml` §training; reproducing its
|
| 995 |
+
*evaluation* and its *serving wiring* is done by this repository. The rejection is preserved in the
|
| 996 |
+
record (`artifacts/vlm/phase6_closure.json`), not scrubbed.
|
| 997 |
|
| 998 |
+
### 8.4 The optical-SAR seed sweep
|
| 999 |
+
|
| 1000 |
+
The optical-SAR head was trained locally with a **seed sweep**; the selected production artifact is
|
| 1001 |
+
`fusion_head_production_v001`, and its pre-registered metric is the 11.5 target recorded in
|
| 1002 |
+
`docs/PHASE12_115_METRIC_COMPUTED.md` and `artifacts/optical_sar/fusion_head_production_v001/
|
| 1003 |
+
pre_registered_115_metric.json`. The input dimension is **derived at runtime** (not hardcoded) —
|
| 1004 |
+
`2318 = 3×768 + 12 + 2` (`fusion_head.py:74`, `:311`), independently confirmed in
|
| 1005 |
+
`docs/PHASE14_OPTICAL_SAR_DECISIONS.md §1`. The ruling is **`OPEN`**, and the accuracy (`0.931`) is
|
| 1006 |
+
never quoted without the macro-F1 (`0.434161`) — rare classes are poorly handled (rule 4).
|
| 1007 |
+
|
| 1008 |
+
### 8.5 What is *not* one-command
|
| 1009 |
+
|
| 1010 |
+
There is no script that takes this repository and produces all six artifacts in one invocation. The
|
| 1011 |
+
four locally-trained artifacts have local training paths, and the two external ones require an
|
| 1012 |
+
external GPU environment. This is a deliberate boundary: the release ships **frozen artifacts with
|
| 1013 |
+
provenance**, not a retraining harness.
|
| 1014 |
+
|
| 1015 |
+
---
|
| 1016 |
+
|
| 1017 |
+
## 9. What is NOT reproducible from this release — exhaustive
|
| 1018 |
+
|
| 1019 |
+
This section is the honest counterweight to §1. Every item below is something a reader might
|
| 1020 |
+
reasonably expect to reproduce and **cannot** from this release alone.
|
| 1021 |
+
|
| 1022 |
+
### 9.1 Not reproducible because the source is not released
|
| 1023 |
|
| 1024 |
| Item | Reason |
|
| 1025 |
|---|---|
|
| 1026 |
+
| The three **private deployment repositories** (`SatQuery-Frontend`, `SatQuery-Backend`, `SatQuery-Inference`) | Private by design; their links 404 for an outside audience (`docs/DEPLOYMENT.md §6`). |
|
| 1027 |
+
| The **live deployment** itself | Requires the private sources plus the Render/Codespace/Cloudflare accounts. |
|
| 1028 |
+
| The **monorepo working copy** | Local only, no remote; 334 dirty entries (`docs/DEPLOYMENT.md §1`). |
|
| 1029 |
+
|
| 1030 |
+
### 9.2 Not reproducible because it does not exist or was not run
|
| 1031 |
+
|
| 1032 |
+
| Item | Reason |
|
| 1033 |
+
|---|---|
|
| 1034 |
+
| **System-level end-to-end benchmark** | **No such benchmark exists.** Per-specialist metrics are real; a single end-to-end number is `NOT RUN` (`README.md` §Known limitations 1). |
|
| 1035 |
+
| **Router test-split number** | **The test split was never run.** The only router number is validation, ungated, `n = 86` (rule 5). |
|
| 1036 |
+
| **Captioning benchmark** | `IMPLEMENTED` but the benchmark is `NOT RUN` ([`EVALUATION.md`](EVALUATION.md) §4.8). |
|
| 1037 |
+
| **A composite / "overall accuracy"** | Forbidden by construction: `official_aggregate_weights: null`; `normalize.py` raises on aggregate requests (rule 8). |
|
| 1038 |
+
|
| 1039 |
+
### 9.3 Not reproducible because the data is large or external
|
| 1040 |
+
|
| 1041 |
+
| Item | Reason |
|
| 1042 |
+
|---|---|
|
| 1043 |
+
| **CDVQA / SECOND imagery** | Public but large. The release documents the acquisition + name-verification procedure, not the data. |
|
| 1044 |
+
| **BigEarthNet full corpus** | **Not downloaded** — only a 28k S2 subset was used. |
|
| 1045 |
+
| **LEVIR-CD-256 / VRSBench raw data** | Not redistributed; the splits (7120/1024/2048, and VRSBench `n=16159`) are documented, not shipped. |
|
| 1046 |
+
| **Fusion held-out test (n=4000)** | Documented, not shipped. |
|
| 1047 |
+
| **Feature caches** | Reproducible but included only in the verbatim archive, not in the release. |
|
| 1048 |
+
|
| 1049 |
+
### 9.4 Not reproducible because it was external-GPU training
|
| 1050 |
+
|
| 1051 |
+
| Item | Reason |
|
| 1052 |
+
|---|---|
|
| 1053 |
+
| **change-VQA retrain** | External GPU (Kaggle); the repo reproduces the *promotion gate*, *evaluation*, and *serving wiring*, not a one-command retrain (§8.2). |
|
| 1054 |
+
| **VLM LoRA retrain** | External GPU; and the adapter is `ACCEPTANCE-REJECTED` anyway (§8.3). |
|
| 1055 |
+
|
| 1056 |
+
### 9.5 Not reproducible because it is frozen paperwork
|
| 1057 |
+
|
| 1058 |
+
| Item | Reason |
|
| 1059 |
+
|---|---|
|
| 1060 |
+
| **The historical ZeroGPU / Gradio deploy target** | `configs/deploy.yaml` describes the old HF-Space/ZeroGPU target and is left undisturbed as frozen paperwork; **no Gradio runtime exists in code** (`docs/DEPLOYMENT.md §9`). Editing it would move the config hash. |
|
| 1061 |
+
|
| 1062 |
+
### 9.6 Not reproducible *as a bit-identical training run*
|
| 1063 |
+
|
| 1064 |
+
The released **artifacts** are byte-identical (guaranteed by hash). The **training runs** that
|
| 1065 |
+
produced them are not claimed to be bit-reproducible on arbitrary hardware: floating-point
|
| 1066 |
+
non-determinism, different accelerators, and library-version drift all affect a training run. What is
|
| 1067 |
+
guaranteed is: the *config* is frozen, the *seed* is recorded (`42`), and the *artifact* is hashable.
|
| 1068 |
+
|
| 1069 |
+
### 9.7 Explicit non-claims
|
| 1070 |
+
|
| 1071 |
+
To remove any ambiguity, this release does **not** claim:
|
| 1072 |
+
|
| 1073 |
+
- that the live system achieves any single accuracy number;
|
| 1074 |
+
- that the router's `0.965116` is a test result (it is validation-only, `n = 86`);
|
| 1075 |
+
- that the VLM adapter is accepted (it is rejected);
|
| 1076 |
+
- that calibration improves ECE (it worsens it, `0.013755 → 0.014929`);
|
| 1077 |
+
- that optical-SAR fusion is production-ready (ruling `OPEN`, macro-F1 `0.434161`);
|
| 1078 |
+
- that change-VQA is settled (ruling `OPEN`, two test sets);
|
| 1079 |
+
- that grounding is solved (`0.2838` canonical IoU — useful, not solved);
|
| 1080 |
+
- that B-07 tunnel gaps are fixed (patch prepared, `NOT deployed`).
|
| 1081 |
+
|
| 1082 |
+
---
|
| 1083 |
+
|
| 1084 |
+
## 10. Environment traps recorded for reproducibility
|
| 1085 |
+
|
| 1086 |
+
These are the edges that cost real debugging time in the authoring environment. They are recorded so
|
| 1087 |
+
a third party does not rediscover them — and, more importantly, so that a reviewer can distinguish
|
| 1088 |
+
"this is a documented environment quirk" from "this is a defect in the released code".
|
| 1089 |
+
|
| 1090 |
+
### 10.1 The dead sandbox proxy needs `--noproxy '*'` / `ProxyHandler({})`
|
| 1091 |
+
|
| 1092 |
+
**Symptom.** Outbound HTTP calls fail or hang in the authoring sandbox.
|
| 1093 |
+
**Root cause.** The sandbox proxy is dead; requests are routed to it and never reach the target.
|
| 1094 |
+
**Fix.** Disable proxies for the call:
|
| 1095 |
+
|
| 1096 |
+
```bash
|
| 1097 |
+
curl --noproxy '*' https://satquery-backend-m4yv.onrender.com/api/health
|
| 1098 |
+
```
|
| 1099 |
+
|
| 1100 |
+
```python
|
| 1101 |
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
| 1102 |
+
```
|
| 1103 |
+
|
| 1104 |
+
`release/tools/hf_verify.py:37-38` does exactly this (`opener_no_proxy()`). In a normal environment
|
| 1105 |
+
the flag is harmless; in the sandbox it is mandatory.
|
| 1106 |
+
|
| 1107 |
+
### 10.2 pytest exists only in the repository virtualenv
|
| 1108 |
+
|
| 1109 |
+
**Symptom.** Invoking the system `pytest` fails or resolves to a different interpreter.
|
| 1110 |
+
**Root cause.** pytest is installed only in `.venv`.
|
| 1111 |
+
**Fix.** Always invoke the venv interpreter explicitly:
|
| 1112 |
+
|
| 1113 |
+
```bash
|
| 1114 |
+
.venv/Scripts/python.exe -m pytest tests/unit/test_frontend_live_wiring.py -q
|
| 1115 |
+
```
|
| 1116 |
+
|
| 1117 |
+
### 10.3 The full-suite pytest trips a bulk-delete guard
|
| 1118 |
+
|
| 1119 |
+
**Symptom.** Running the whole `tests/unit` tree trips 4× `test_safe_delete_shim` failures.
|
| 1120 |
+
**Root cause.** Windows **verbatim-path** defects in the sandbox's bulk-delete guard. The precise
|
| 1121 |
+
condition under which the shim intermittently triggers on Windows is
|
| 1122 |
+
`UNKNOWN — not established from the available evidence`.
|
| 1123 |
+
**Fix / workaround.** Run the targeted suites (106 and 183 pass cleanly); treat the 4 shim failures
|
| 1124 |
+
as environmental, not regressions (§5.4).
|
| 1125 |
+
|
| 1126 |
+
### 10.4 Cloudflare 308-redirects `X.html` → `/X`
|
| 1127 |
+
|
| 1128 |
+
**Symptom.** A request to `something.html` returns a 308 redirect.
|
| 1129 |
+
**Root cause.** Cloudflare Pages 308-redirects `X.html` → `/X`.
|
| 1130 |
+
**Fix.** Reference the **extensionless** path (`docs/DEPLOYMENT.md §7`).
|
| 1131 |
+
|
| 1132 |
+
### 10.5 Chrome drops synthetic CDP key events without OS focus
|
| 1133 |
+
|
| 1134 |
+
**Symptom.** A browser-driven run silently answers the *default* query; the query box looks
|
| 1135 |
+
untouched; `mock_nodes` is non-zero.
|
| 1136 |
+
**Root cause.** Chrome **drops synthesized key events when the browser window does not hold OS
|
| 1137 |
+
focus**. `press_key`/`fill_input` (real CDP key events) are focus-gated; `Input.insertText`
|
| 1138 |
+
(`type_text`) is not.
|
| 1139 |
+
**Fix.** Use `type_text` (not `fill_input`), and **assert the input state before dispatch**
|
| 1140 |
+
(`q_ok`/`obs_ok`/`t0_ok`). This is the single most dangerous trap because it produces a **silent
|
| 1141 |
+
false pass** (§6.4; [`EVALUATION.md`](EVALUATION.md) §6.4).
|
| 1142 |
+
|
| 1143 |
+
### 10.6 Buffering hides liveness (two separate causes)
|
| 1144 |
+
|
| 1145 |
+
**Symptom 1.** A redirected browser-harness log stays at 0 bytes until the process exits —
|
| 1146 |
+
indistinguishable from a stall.
|
| 1147 |
+
**Root cause 1.** `browser-use` block-buffers stdout even when redirected.
|
| 1148 |
+
**Fix 1.** Force line-buffering and print a per-case marker (`CASE_START <id>`).
|
| 1149 |
+
|
| 1150 |
+
**Symptom 2.** Piping the harness through `grep` swallows all output if the pipeline is killed.
|
| 1151 |
+
**Root cause 2.** `grep` block-buffers when piped.
|
| 1152 |
+
**Fix 2.** Redirect to a file instead of piping through `grep`.
|
| 1153 |
+
|
| 1154 |
+
### 10.7 `hf_hub_download` returned an EMPTY file → a false FAIL
|
| 1155 |
+
|
| 1156 |
+
**Symptom.** Every artifact hashes to `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
|
| 1157 |
+
(empty content) and the verification reports all `DIFFER`.
|
| 1158 |
+
**Root cause.** `hf_hub_download` returned an empty file in the authoring environment (a download-path
|
| 1159 |
+
problem), so the verifier hashed nothing. The upload was fine.
|
| 1160 |
+
**Fix.** Use a **direct HTTPS download with proxies disabled**, hash the bytes received, and compare
|
| 1161 |
+
**size as well as hash**. `release/tools/hf_verify.py` is the honest check (§7.3).
|
| 1162 |
+
|
| 1163 |
+
### 10.8 `codespace_name` carries a trailing `\n`
|
| 1164 |
+
|
| 1165 |
+
**Symptom.** `/api/health` reports `"codespace_name":"potential-space-trout-r4ppw969w45j2pvvw\n"`.
|
| 1166 |
+
**Root cause.** B-02; the wake path strips it, so it is **cosmetic**.
|
| 1167 |
+
**State.** `OPEN (cosmetic)`; the fix is not deployed (`docs/DEPLOYMENT.md §2, §6`).
|
| 1168 |
+
|
| 1169 |
+
### 10.9 A forwarded Codespace port returns 302 for a private repo
|
| 1170 |
+
|
| 1171 |
+
**Symptom.** A forwarded Codespace port returns HTTP 302 instead of serving.
|
| 1172 |
+
**Root cause.** Private-repo port forwarding returns 302 — which is *why* the outbound tunnel exists
|
| 1173 |
+
(`docs/DEPLOYMENT.md §7`).
|
| 1174 |
+
**Fix.** Use the tunnel, not the forwarded port.
|
| 1175 |
+
|
| 1176 |
+
### 10.10 The tunnel agent must be started by the devcontainer
|
| 1177 |
+
|
| 1178 |
+
**Symptom.** A restarted Codespace comes up with `agent_connected: false`.
|
| 1179 |
+
**Root cause.** The tunnel agent is started by the devcontainer `postStartCommand`; if it does not
|
| 1180 |
+
run, the agent is absent.
|
| 1181 |
+
**Fix.** Ensure `.devcontainer/` starts the agent on start (`docs/DEPLOYMENT.md §7`).
|
| 1182 |
+
|
| 1183 |
+
---
|
| 1184 |
+
|
| 1185 |
+
## 11. What is NOT RUN / OPEN / BLOCKED for this topic
|
| 1186 |
+
|
| 1187 |
+
Per the documentation standard, this section lists, for the topic of reproducibility itself, what is
|
| 1188 |
+
not settled.
|
| 1189 |
+
|
| 1190 |
+
| Item | Status | Note |
|
| 1191 |
+
|---|---|---|
|
| 1192 |
+
| End-to-end system benchmark | **NOT RUN** | No such benchmark exists; nothing to reproduce. |
|
| 1193 |
+
| Router **test**-split number | **NOT RUN** | Only validation (`n = 86`, ungated) exists. |
|
| 1194 |
+
| Captioning benchmark | **NOT RUN** | Implemented; not benchmarked. |
|
| 1195 |
+
| VLM adapter acceptance | **REJECTED** | Metrics usable; not promoted. `USABLE ≠ ACCEPTED`. |
|
| 1196 |
+
| Optical-SAR fusion ruling | **OPEN** | Accuracy without macro-F1 is never quoted. |
|
| 1197 |
+
| Change-VQA ruling | **OPEN** | Two test sets. |
|
| 1198 |
+
| Grounding acceptance | **OPEN** | Two protocols; modest IoU. |
|
| 1199 |
+
| Calibration improvement | **REJECTED (retained in frozen config)** | ECE worsened `0.013755 → 0.014929`. |
|
| 1200 |
+
| B-07 tunnel gaps | **OPEN** | Patch prepared, **NOT deployed**. |
|
| 1201 |
+
| B-02 trailing `\n` | **OPEN (cosmetic)** | Not deployed. |
|
| 1202 |
+
| Licence | **OPEN** | **No LICENSE file exists**; owner decision. |
|
| 1203 |
+
| Exact full-suite *collected* count | **UNKNOWN** | Per-suite counts are known (106, 183, 137 re-run); the single collected total is not established. |
|
| 1204 |
+
| Exact Windows trigger for the delete-shim flake | **UNKNOWN** | The intermittent condition is not established from the available evidence. |
|
| 1205 |
+
| Bit-identical training on arbitrary hardware | **NOT CLAIMED** | Only artifacts are guaranteed byte-identical, by hash. |
|
| 1206 |
+
|
| 1207 |
+
---
|
| 1208 |
+
|
| 1209 |
+
## 12. Where the evidence lives
|
| 1210 |
+
|
| 1211 |
+
| Evidence | Location |
|
| 1212 |
+
|---|---|
|
| 1213 |
+
| The 20-claim metric verifier | `release/tools/verify_readme_metrics.py` |
|
| 1214 |
+
| Its committed output | `release/tools/readme_metrics_report.txt` (`ALL CLAIMS VERIFIED`) |
|
| 1215 |
+
| The Hub verifier | `release/tools/hf_verify.py` |
|
| 1216 |
+
| Its committed output | `release/tools/hf_verify_report.txt` (6/6 `MATCH`) |
|
| 1217 |
+
| Hub release verification write-up | `release/HF_RELEASE_VERIFICATION.md` |
|
| 1218 |
+
| Model manifest (generated) | `models/manifest.json` |
|
| 1219 |
+
| Checksums (generated) | `models/checksums.sha256` |
|
| 1220 |
+
| Manifest generator | `release/tools/generate_model_manifest.py` |
|
| 1221 |
+
| Archive builder / verifier | `release/tools/build_archive.py`, `release/tools/verify_archive.py` |
|
| 1222 |
+
| Config loader + invariants | `core/config.py` (hash `:76-80`; `_validate` `:94`) |
|
| 1223 |
+
| Frozen config registry | `configs/base.yaml` (§evaluation `:273`, §deployment `:282`) |
|
| 1224 |
+
| Dependency manifest + profiles | `requirements.txt` |
|
| 1225 |
+
| Live topology, env vars, traps | `docs/DEPLOYMENT.md` |
|
| 1226 |
+
| Live validation (3 passes, 24 runs) | `.workbuddy-ai/scratch/live_validation/LIVE_VALIDATION_POSTFIX.md`, `results_final.json`, `results_pass3.json`, `run_final2.txt`, `run_final3.txt` |
|
| 1227 |
+
| Verdict recomputation | `recompute_verdicts.py` |
|
| 1228 |
+
| Kaggle training guide (change-VQA) | `docs/R02_KAGGLE_TRAINING_GUIDE.md` |
|
| 1229 |
+
| Optical-SAR decisions | `docs/PHASE14_OPTICAL_SAR_DECISIONS.md` |
|
| 1230 |
+
| Pre-registered 11.5 metric | `docs/PHASE12_115_METRIC_COMPUTED.md` |
|
| 1231 |
+
| Grounding resolution decision | `docs/PHASE7_RESOLUTION_DECISION.md` |
|
| 1232 |
+
| Delivery test results (94/183/5–6/137) | `docs/FINAL_DELIVERY_REPORT.md §7` |
|
| 1233 |
+
| Frontend suite re-run count (106) | `release/CURRENT_RELEASE_STATE.md:121`, `README.md` §The test suites |
|
| 1234 |
+
| Evaluation protocols and honesty rules | [`EVALUATION.md`](EVALUATION.md) |
|
| 1235 |
+
| Benchmarks and their statuses | [`BENCHMARKS.md`](BENCHMARKS.md) |
|
| 1236 |
+
| Model cards and artifact details | [`MODELS.md`](MODELS.md) |
|
| 1237 |
+
| Datasets and splits | [`DATASETS.md`](DATASETS.md) |
|
| 1238 |
+
| Training paths | [`TRAINING.md`](TRAINING.md) |
|
| 1239 |
+
| Known limitations | [`LIMITATIONS.md`](LIMITATIONS.md) |
|