# Reproducibility **Status tags:** `IMPLEMENTED` · `VERIFIED` · `MEASURED` · `ATTEMPTED` · `NOT RUN` · `BLOCKED` · `DEFERRED` · `REJECTED` · `OPEN` · `RESOLVED` · `CLOSED`. This document states **exactly what a third party can reproduce, with what, and — just as importantly — what they cannot**. It is written to the same depth as the rest of this documentation set: real commands, real file paths, real hashes, real expected output, and an explicit status for every claim. It is deliberately **not** a "quickstart". A quickstart tells you the happy path and hides the edges. This document records the edges, because in this project the edges are where the real debugging time went — a dead sandbox proxy, a pytest that only exists in one virtualenv, a Chrome that silently drops synthetic key events, a Hugging Face helper that returned an *empty file* and would have produced a **false failure**. Those are documented in §10 so that a third party does not rediscover them. > **The one rule this document obeys, like every document in `docs/`:** *do not fabricate*. Every > claim below is grounded in a file that was read (`core/config.py`, `configs/base.yaml`, > `release/tools/*.py`, `artifacts/**`, `docs/**`). Where the evidence does not establish something, > the text says `UNKNOWN — not established from the available evidence`. --- ## Table of contents 1. [The reproducibility contract](#1-the-reproducibility-contract) 2. [The frozen configuration](#2-the-frozen-configuration) 3. [Reproduce the metric verification (cheap, no GPU)](#3-reproduce-the-metric-verification-cheap-no-gpu) 4. [Reproduce the environment](#4-reproduce-the-environment) 5. [Reproduce the tests](#5-reproduce-the-tests) 6. [Reproduce a live run](#6-reproduce-a-live-run) 7. [Reproduce and verify the released artifacts](#7-reproduce-and-verify-the-released-artifacts) 8. [What "reproduce" means for the two externally-trained artifacts](#8-what-reproduce-means-for-the-two-externally-trained-artifacts) 9. [What is NOT reproducible from this release — exhaustive](#9-what-is-not-reproducible-from-this-release--exhaustive) 10. [Environment traps recorded for reproducibility](#10-environment-traps-recorded-for-reproducibility) 11. [What is NOT RUN / OPEN / BLOCKED for this topic](#11-what-is-not-run--open--blocked-for-this-topic) 12. [Where the evidence lives](#12-where-the-evidence-lives) --- ## 1. The reproducibility contract Reproducibility here is not a slogan; it is a set of **enforced guarantees**. Each row below states a guarantee, how it is enforced in code or in a tool, and how a third party can independently check it. | Guarantee | How it is enforced | How to check it | |---|---|---| | **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. | | **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. | | **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. | | **Seed** | `project.seed: 42` (`core/config.py:82-84`). | Read the key; it is in the registry. | | **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. | | **Leakage isolation** | `evaluation.leakage_split_key: scene_id` — splits are assigned by *scene*, never by *tile*. | Read the key; `evaluation/leakage.py` audits manifests. | | **No official aggregate** | `evaluation.official_aggregate_weights: null`. There is no weighted composite score. | Read the key; `normalize.py` raises on aggregate requests. | | **Byte-verified artifacts** | Every released artifact ships with a sha256 in `models/checksums.sha256`, generated from the actual files. | `sha256sum -c models/checksums.sha256`. | | **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`. | | **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`. | | **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. | ### 1.1 What the contract is *not* The contract does **not** promise: - a single command that retrains every artifact from scratch (§8); - an end-to-end system-level accuracy number (§9 — no such benchmark exists); - that the two externally-trained artifacts can be regenerated inside this repository (§8); - bit-identical *training* on arbitrary hardware (only bit-identical *released artifacts* are guaranteed, by hash). ### 1.2 The reproducibility ladder Reproduction here comes in four tiers, from cheapest to most expensive. A reviewer who wants to spend five minutes gets tier 1; a reviewer who wants to audit the whole thing walks all four. | Tier | What it proves | Cost | Section | |---|---|---|---| | **1. Metric verification** | Every quoted number matches its artifact | seconds, CPU | §3 | | **2. Environment + tests** | The code installs and the suites behave as recorded | minutes, CPU | §4–§5 | | **3. Live run** | The deployed stack answers correctly on unseen imagery | minutes (plus cold start), network | §6 | | **4. Artifact + Hub verification** | The released weights are byte-identical to what was uploaded | minutes, network | §7 | --- ## 2. The frozen configuration ### 2.1 What "frozen" means here `configs/base.yaml` is the **single registry** for every tunable in the system. The design rule is: *no magic numbers in Python*. If a value can change behaviour — an image size, a channel count, a threshold, a precision, a split key — it lives in the registry, not in a function body. This matters for reproducibility because it means the **entire behavioural surface of the system is captured by one file**, and that file has a hash. ### 2.2 The hash and how it is computed ```python @property def hash(self) -> str: """Stable hash of the whole registry. Recorded in every evaluation run.""" blob = json.dumps(self._data, sort_keys=True, default=str).encode() return hashlib.sha256(blob).hexdigest()[:16] ``` — `core/config.py:76-80`. Three properties make this hash **stable**: 1. **`sort_keys=True`** — key order in the YAML file does not change the hash. 2. **`default=str`** — non-JSON-native values (dates, paths) are stringified deterministically. 3. **`[:16]`** — the first 16 hex characters are used as the human-readable identifier. The frozen value is: ``` 78f1e3700da15aa1 ``` It is recorded in `models/manifest.json` (`"config_hash": "78f1e3700da15aa1"`), in each artifact's metadata, and in the live evidence (each live run's panel shows the hash). This is the single identifier that ties "the thing that produced this number" to "the configuration a reviewer is reading". ### 2.3 The enforced invariants, with exact arithmetic Editing `configs/base.yaml` moves the hash and **invalidates every artifact keyed to it**. To make that failure loud rather than silent, the loader rejects a config that violates a set of recorded invariants. Two of them exist specifically because their violation is a *silent* error that torch only surfaces much later. #### Invariant 1 — fusion input dimension (finding C-1) ``` fusion.input_dim == 3 * croma.encoder_dim + croma.optical_channels + croma.sar_channels ``` With the frozen values `encoder_dim = 768`, `optical_channels = 12`, `sar_channels = 2`: ``` fusion.input_dim == 3 * 768 + 12 + 2 == 2304 + 12 + 2 == 2318 ``` This is the concatenation of CROMA's three 768-d GAP vectors (`optical_GAP`, `SAR_GAP`, `joint_GAP`) plus the 12 optical channels and 2 SAR channels of the sensor adapter. The loader recomputes `expected` from the config and raises `ConfigError` if `fusion.input_dim` disagrees (`core/config.py:154-163`). The docstring is explicit about *why*: "CROMA emits optical/SAR/joint GAP vectors; the [declared value must match]". The upstream contract was independently reproduced in `docs/PHASE14_OPTICAL_SAR_DECISIONS.md §1`, where the fusion input is recomputed at runtime (`fusion_head.py:74`, `:311`) rather than hardcoded. Two related guards fire alongside it (`core/config.py:165-168`): ``` croma.optical_channels == 12 # CROMA s2_channels is fixed croma.sar_channels == 2 # CROMA s1_channels is fixed ``` #### Invariant 2 — grounding head feature dimension (finding P7-1) ``` grounding_head.feature_dim == 4 * grounding.encoder_projected_dim ``` With `encoder_projected_dim = 512`: ``` grounding_head.feature_dim == 4 * 512 == 2048 ``` The loader's own comment explains why this is **load-bearing**: > A mismatch here is a SILENT shape error. torch only raises at the [similarity step], after patch > features are already cached. It is therefore rejected at load time. — `core/config.py:176-195`. The "4×" arises because the grounding head consumes four projected feature streams; the projected dimension is declared in config precisely so this guard can exist without hardcoding 512. #### Further load-time guards The `_validate()` method (`core/config.py:94`) also enforces: | Guard | Rule | Finding | |---|---|---| | CROMA resolution | `croma.image_resolution` is an int and a multiple of 8 (CROMA asserts `% 8 == 0`) | C-7 | | Training precision | `training.precision ∈ {fp16, bf16, fp32}` | C-6 | | ZeroGPU compile ban | `deployment.torch_compile` must not be `True` — ZeroGPU does not support `torch.compile` | C-8 | | VLM prompt template | `vlm.prompt_must_use_chat_template` must be `true` — SmolVLM raises otherwise | — | These are the invariants a reviewer can verify by reading one method. They are the reason a *plausible-looking* config edit fails at load instead of producing a wrong number three hours later. ### 2.4 The evaluation invariants ```yaml evaluation: immutable_public_test: true hidden_data_access: false official_aggregate_weights: null leakage_split_key: scene_id ``` — `configs/base.yaml:273-277`. | Key | Value | Meaning | |---|---|---| | `immutable_public_test` | `true` | The public-test corpus is sealed; it cannot be mutated by an evaluation. | | `hidden_data_access` | `false` | No code path may read hidden data during a scored run. | | `official_aggregate_weights` | `null` | **There is no composite score.** A single "overall accuracy" is forbidden by construction. | | `leakage_split_key` | `scene_id` | Splits are assigned by scene, so tiles from one scene never straddle train/test. | The evaluation-honesty rules that these keys serve are stated in full in [`EVALUATION.md`](EVALUATION.md) §2. ### 2.5 The deployment section is frozen paperwork ```yaml deployment: platform: huggingface-spaces sdk: gradio zerogpu: true torch_compile: false ... ``` — `configs/base.yaml:282-295`. The deployment target described here is the **superseded** HF-Space/ZeroGPU design. The active topology is Cloudflare Pages → Render → Codespace (`docs/DEPLOYMENT.md §1`). This section is retained as **frozen paperwork**: editing it would move the config hash, and the hash is what every artifact is keyed to. No Gradio runtime exists in code. The `torch_compile: false` line is the C-8 guard and remains meaningful regardless of host. ### 2.6 The hash-exempt path One decision (DEV-2, the CROMA input-normalisation question) needed to become inspectable **without** adding a `base.yaml` key — because adding a key would have moved the hash away from `78f1e3700da15aa1` and invalidated every artifact. The resolution was a hash-exempt path (`docs/PHASE14_OPTICAL_SAR_DECISIONS.md`, item 7): ``` SATQUERY_CROMA_USE_8_BIT (env) → croma.use_8_bit (config) → default True ``` It is inspectable at runtime via `CROMAEncoder.describe()["input_normalisation"]`. This is recorded because it is the one place where "frozen" and "configurable" were reconciled deliberately, and a reviewer should know the escape hatch exists and is documented rather than hidden. ### 2.7 What invalidates the hash Anything that changes `self._data` when serialised with `sort_keys=True`. Concretely: - editing any value in `configs/base.yaml`; - adding or removing a key; - changing a value's *type* such that its `default=str` rendering changes. The consequence is not merely a different hash; it is that **every artifact keyed to `78f1e3700da15aa1` no longer describes the running configuration**, and the honest action is to re-run the evaluation rather than reinterpret the old numbers. --- ## 3. Reproduce the metric verification (cheap, no GPU) This is the cheapest, highest-value reproduction in the whole release. It takes seconds, needs no GPU and no network, and it is the check that a skeptical reviewer should run **first**. ### 3.1 The verifier ```bash python release/tools/verify_readme_metrics.py ``` `release/tools/verify_readme_metrics.py` is read-only. Its own docstring states its contract: > Verify every metric quoted in the release README against its source artifact. Read-only. Each > claim is compared at the precision at which the README states it. The tool is **not** a re-computation of the metrics. It is a **provenance check**: it reads the artifact that the README cites, resolves a dotted key into it, and compares the value against the literal string the README prints — at the README's own precision. ### 3.2 The 20 claims and their exact key paths The claim list is not assumed; the key names were **discovered by walking the artifacts**. Several live under nested paths, and the tool's docstring says so: > Key names were discovered by walking the artifacts, NOT assumed: several live under nested paths > (e.g. change metrics are `metrics.pooled.iou`, grounding is `results.head_threshold.mean_best_iou`, > VLM is `why_usable_verified.adapted_test.*`). | # | Claim | Artifact | Dotted key | README value | |---|---|---|---|---| | 1 | change pooled IoU | `artifacts/change/eval_test/eval_result.json` | `metrics.pooled.iou` | `0.8122` | | 2 | change macro IoU | `artifacts/change/eval_test/eval_result.json` | `metrics.macro.miou` | `0.8457` | | 3 | change pooled F1 | `artifacts/change/eval_test/eval_result.json` | `metrics.pooled.f1` | `0.8964` | | 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` | | 5 | grounding canonical head_threshold recall@0.5 | `…/eval_result_canonical.json` | `results.head_threshold.recall.0.50` | `0.2198` | | 6 | grounding matched6 head_threshold mean_best_IoU | `…/eval_result_matched6.json` | `results.head_threshold.mean_best_iou` | `0.2566` | | 7 | grounding matched6 head_threshold recall@0.5 | `…/eval_result_matched6.json` | `results.head_threshold.recall.0.50` | `0.1938` | | 8 | grounding head_argmax mean_best_IoU (canonical) | `…/eval_result_canonical.json` | `results.head_argmax.mean_best_iou` | `0.1215` | | 9 | grounding zero-shot baseline IoU (canonical) | `…/eval_result_canonical.json` | `results.zero_shot_matched.mean_best_iou` | `0.0972` | | 10 | optical-SAR fusion accuracy | `artifacts/optical_sar/fusion_head_production_v001/pre_registered_115_metric.json` | `accuracy` | `0.931` | | 11 | optical-SAR fusion macro_F1 | `…/pre_registered_115_metric.json` | `macro_f1` | `0.434161` | | 12 | change_vqa test accuracy | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test_accuracy` | `0.697626` | | 13 | change_vqa test macro_F1 | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test_macro_f1` | `0.378373` | | 14 | change_vqa test2 accuracy | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test2_accuracy` | `0.651469` | | 15 | change_vqa test2 macro_F1 | `artifacts/change_vqa/run/PROMOTION.json` | `verification.test2_macro_f1` | `0.372309` | | 16 | router overall ungated accuracy | `artifacts/router/threshold_sweep_val.json` | `overall_ungated_accuracy` | `0.965116` | | 17 | calibration ECE before scaling | `artifacts/calibration_v001.json` | `metrics.ece_before` | `0.013755` | | 18 | calibration ECE after scaling | `artifacts/calibration_v001.json` | `metrics.ece_after` | `0.014929` | | 19 | VLM adapter exact_match | `artifacts/vlm/phase6_closure.json` | `why_usable_verified.adapted_test.exact_match` | `0.963` | | 20 | VLM adapter F1 | `artifacts/vlm/phase6_closure.json` | `why_usable_verified.adapted_test.f1` | `0.96432` | Note rows 1–3, 6–7, 12–15, and 16: these are the rows that keep the honesty rules honest. The change numbers are quoted as **pooled and macro together** (rule 4); grounding is quoted under **both protocols** (rule 2); change-VQA is quoted under **both test sets** (rule 3); the router number is quoted with its **validation-only** caveat (rule 5). A verifier that checked only one number per task would be reproducible and *wrong*. ### 3.3 The status assertions Metrics are not the only claims. After the 20 numeric comparisons, the tool asserts four statuses that the README also states (`verify_readme_metrics.py:117-131`): ``` VLM headline contains ACCEPTANCE-REJECTED : True VLM status : CLOSED router corpus_limited : True router n_val : 86 calibration temperature (temperature_scaling.temperature) : 0.9772731820958189 calibration ece_improvement : -0.001174 (negative => calibration did NOT help) ``` These are the checks that prevent the documentation from quietly upgrading a status. In particular: - the **VLM** metric is usable but the artifact's own headline contains `ACCEPTANCE-REJECTED` — so `USABLE ≠ ACCEPTED` (rule 7); - the **router** number is flagged `corpus_limited: true` with `n_val: 86` — so it can never be presented as a test number (rule 5); - the **calibration** `ece_improvement` is **negative**, i.e. calibration made ECE *worse*, and the tool prints that interpretation inline (rule 6). ### 3.4 The nested-key resolution trick Two subtleties in the resolver are worth recording, because they are the kind of thing that turns a verifier into a **false-pass** machine if done naively (`verify_readme_metrics.py:69-90`): 1. **Keys that themselves contain dots.** The grounding recall dictionary is keyed by the string `"0.10"`, `"0.25"`, `"0.50"`. A naive `dotted.split(".")` walk would break `results.head_threshold.recall.0.50` into `…recall → 0 → 50` and fail. The resolver therefore tries the **longest matching key at each step first**, so `0.50` stays intact. 2. **Precision matching.** The comparison is `round(float(val), decimals(claimed)) == float(claimed)` — i.e. the artifact value is rounded to the number of decimals the README prints, then compared. This is why row 15 shows the artifact value `0.372308516` matching the README's `0.372309`: the README states six decimals and the artifact rounds to it. A verifier that compared raw floats would report false differences on every rounded value. A verifier that split dots naively would report false differences on the recall rows. Both failure modes are **silent false failures** — the same class of bug discussed in §7.3 and §10. ### 3.5 The committed output ``` STATUS claim artifact readme source ---------------------------------------------------------------------------------------------------------------------- MATCH change pooled IoU 0.8122 0.8122 artifacts/change/eval_test/eval_result.json#metrics.pooled.iou MATCH change macro IoU 0.8457 0.8457 artifacts/change/eval_test/eval_result.json#metrics.macro.miou MATCH change pooled F1 0.8964 0.8964 artifacts/change/eval_test/eval_result.json#metrics.pooled.f1 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 ... MATCH VLM adapter exact_match 0.963 0.963 artifacts/vlm/phase6_closure.json#why_usable_verified.adapted_test.exact_match MATCH VLM adapter F1 0.96432 0.96432 artifacts/vlm/phase6_closure.json#why_usable_verified.adapted_test.f1 === status assertions === VLM headline contains ACCEPTANCE-REJECTED : True VLM status : CLOSED router corpus_limited : True router n_val : 86 calibration temperature (temperature_scaling.temperature) : 0.9772731820958189 calibration ece_improvement : -0.001174 (negative => calibration did NOT help) RESULT: ALL CLAIMS VERIFIED ``` — `release/tools/readme_metrics_report.txt` (committed). The tool **exits 0** and prints `ALL CLAIMS VERIFIED` only when every numeric comparison is `MATCH` and no artifact is missing or lacks the key. Any `NOFILE`, `NOKEY`, or `DIFFER` line increments the failure counter, the final line reads `N CLAIM(S) FAILED`, and the process exits 1. This makes the check usable in CI: a non-zero exit is a hard failure. ### 3.6 Why this is a real check and not a tautology A skeptic might object: "you wrote the README from the artifacts, so of course they match." The answer is that the check is not that the README was *derived* from the artifacts, but that the README's published claims remain **mechanically traceable** to files a third party also has. If someone edits the README to state a nicer number, the tool fails. If someone edits an artifact to a different number, the tool fails. The check binds the published prose to the recorded evidence, and it does so without trusting either. --- ## 4. Reproduce the environment ### 4.1 What you need - **Python 3.11+** (`README.md` §Installation: "Python 3.11+ and a CPU are sufficient"). - A **CPU is enough**. There is no CUDA requirement. - Network access on first use, to fetch the pinned backbones from the Hugging Face Hub. ### 4.2 The two install profiles `requirements.txt` documents two profiles explicitly: ```text # CPU (local dev / schema / geo / unit tests): # pip install -r requirements.txt # GPU (Kaggle T4x2 / HF ZeroGPU): torch is preinstalled on both. # Do NOT pin torch here — Kaggle and HF ship their own builds. ``` — `requirements.txt` (header comment). The pins, with the contract each one carries (`requirements.txt`): | Package | Pin | Why | |---|---|---| | `numpy` | `>=1.26,<3` | core | | `pyyaml` | `>=6.0` | config loading | | `pydantic` | `>=2.6,<3` | schema | | `rasterio` | `>=1.3` | geospatial IO | | `pyproj` | `>=3.6` | CRS handling | | `opencv-python-headless` | `>=4.9` | image ops | | `transformers` | `>=4.52` | SmolVLM via `AutoModelForImageTextToText`; `AutoModelForVision2Seq` is deprecated (finding **C-2**) | | `open-clip-torch` | `>=2.24` | RemoteCLIP loaded via `pretrained=` so `load_checkpoint()` runs its state-dict fixups (**C-4**) | | `sentence-transformers` | `>=2.7` | router embedding | | `peft` | `>=0.10` | LoRA adapter | | `huggingface_hub` | `>=0.23` | pin RemoteCLIP / SmolVLM / CROMA revisions | | `safetensors` | `>=0.4` | adapter serialisation | | `einops` | `>=0.7` | **required** by the vendored `use_croma.py` (`from einops import rearrange`) | | `gradio` | `>=4.44` | UI (frozen deployment target) | | `reportlab` | `>=4.1` | reporting | | `pytest` | `>=8.0` | tests | | `pytest-cov` | `>=5.0` | coverage | The comment block closes with an instruction a reviewer should heed: *"do not 'fix' by changing these pins"* — the pins encode verified contracts, not preferences. ### 4.3 The `einops` lesson — a dependency no document declared `requirements.txt` carries an unusually explicit note about `einops`: > REQUIRED by the vendored `specialists/optical_sar/vendor/use_croma.py` (`from einops import > rearrange`). Found missing on 2026-09-18 by executing the vendored module: it raised > `ModuleNotFoundError`, so the CROMA path was blocked on a dependency that no document declared. > Not optional: without it the encoder cannot be imported at all. This is recorded verbatim because it is a reproducibility fact: a missing dependency that no document mentioned was found **by executing the code**, not by reading a spec. A third party building the environment will hit the same wall if they install from a partial list. ### 4.4 Create the environment ```bash git clone https://github.com/Anish-lab-blip/SatQuery-AI cd SatQuery-AI python -m venv .venv source .venv/Scripts/activate # Windows git-bash; use .venv/bin/activate on Linux/macOS pip install -r requirements.txt ``` — `README.md` §Installation. > **Trap (Windows, recorded in §10.2).** In the authoring environment, `pytest` exists **only** in > the repository virtualenv. Invoking the system `pytest` fails or resolves to a different > interpreter. Always invoke the venv interpreter explicitly: > `.venv/Scripts/python.exe -m pytest …`. ### 4.5 Backbones are fetched, pinned by revision, and never redistributed The four third-party backbones are pinned by `repo_id` **and** `revision` (`README.md` §Reproducibility item 2; `models/manifest.json` per-artifact `backbone` fields): | Model | Revision | Role | |---|---|---| | `HuggingFaceTB/SmolVLM-500M-Instruct` | `a7da5b986cb5` | VQA + captioning backbone | | `chendelong/RemoteCLIP` | `bf1d8a3ccf2d` | remote-sensing grounding encoder | | `sentence-transformers/all-MiniLM-L6-v2` | `1110a243fdf4` | router embedding | | `antofuller/CROMA` | `0dd28e3d633b` | optical/SAR fusion encoder | **No backbone weights are redistributed** in this release (`release/HF_RELEASE_VERIFICATION.md §4`). The released artifacts are small trained modules *over* these frozen backbones; anyone using them must fetch the backbones too. Pinning by commit prefix (not by a floating tag like `main`) is what makes "the same backbone" reproducible months later. ### 4.6 Device selection Device is selected via `SATQUERY_DEVICE`, and **all placement is `.to(device)`, never `.cuda()`** (`README.md` §Installation; `core/config.py:86-91`): ```python @property def device_preference(self) -> str: override = os.environ.get("SATQUERY_DEVICE") if override: return override return "cuda" if _torch_cuda_available() else "cpu" ``` This is why a CPU-only reviewer can run everything except the training paths. The env override is read **without importing torch** in the deployment context (`docs/DEPLOYMENT.md §3.2`), so a misconfigured device cannot crash the service at import time. --- ## 5. Reproduce the tests ### 5.1 The suites and their expected results | Suite | Command | Expected | Status | |---|---|---|---| | Frontend live-wiring | `python -m pytest tests/unit/test_frontend_live_wiring.py -q` | **106 passed** | `VERIFIED` | | Doc/frontend suite | `pytest` on the 5 doc/frontend files | **183 passed** | `VERIFIED` | | Full unit suite | `python -m pytest tests/unit` | **5–6 environmental/ordering failures**, rest pass; a re-run passes 137 | `MEASURED` | The three rows tell a story a single number would hide: the two **targeted** suites are clean, and the **full** suite has 5–6 failures that are environmental or ordering-related, not regressions. The next subsections reproduce each. ### 5.2 Frontend live-wiring regression suite — 106 passed ```bash python -m pytest tests/unit/test_frontend_live_wiring.py -q ``` — `README.md` §Local development. This suite is the regression net for the frontend↔backend wiring: the query box, the intent panel, the task tags, the run identifiers, and the evidence rendering. It is the suite that would catch a re-introduction of the router defect described in §6.4. **Expected: 106 passed.** Note the historical figure, because a reviewer may see both numbers: `docs/FINAL_DELIVERY_REPORT.md §7` records **94 passed** for this file, while `CURRENT_RELEASE_STATE.md:121` and `README.md` §The test suites record **106 passed** ("re-run this session"). The difference is the suite growing after the delivery report was written — the regression tests added for the router fix took this file from 100 → 106 tests (`DELIVERY_REPORT_2026-09-25.md`, regression-tests note). This document quotes **106**, the re-run-this-session figure, and cites both. (The exact count of tests *collected* in a single full-suite invocation is `UNKNOWN — not established from the available evidence`; the per-suite counts are what the evidence establishes.) ### 5.3 Doc/frontend suite — 183 passed The documentation-and-frontend suite is **five files**, named explicitly in `docs/FINAL_DELIVERY_REPORT.md §7`: - `test_frontend_guide_doc` - `test_frontend_live_wiring` - `test_api_contract_doc` - `test_runbook_doc` - `test_deploy_config` Together they report **183 passed**. These are the checks that the docs' internal links resolve, the frontend assets are present, the API contract document matches the code, the runbook is consistent, and the deploy config is coherent. The same three-row test table is published in this release at `README.md` §The test suites (`README.md:1063-1074`), so a public reader can see the counts without the project-internal report. The underlying record is `docs/FINAL_DELIVERY_REPORT.md §7` (project-internal); the frontend count was re-run this session to **106** (`release/CURRENT_RELEASE_STATE.md:121`). See also [`EVALUATION.md`](EVALUATION.md) §7.2. ### 5.4 Full `tests/unit` — the 5–6 failures, reported honestly Running the entire unit tree trips **5–6** failures. They are reported here rather than hidden, and they are classified by cause exactly as `docs/FINAL_DELIVERY_REPORT.md §7` records them: | # | Failure | Cause | Regression? | |---|---|---|---| | 1–4 | `test_safe_delete_shim` (×4) | the sandbox's bulk-**delete guard** (Windows verbatim-path behaviour) | **No** | | 5 | one ordering flake in the **router route test** | passes in isolation; order/collection-dependent | **No** | | 6 | one **stale adapter test** | asserts `optical_sar` absent when CROMA is *unshipped* — **CROMA is now shipped** | **No** | **Why these are not regressions.** They were isolated by a re-run of the affected files **together**, which passes **137** tests (`docs/FINAL_DELIVERY_REPORT.md §7`; [`EVALUATION.md`](EVALUATION.md) §7.3–7.4). The `test_safe_delete_shim` failures are Windows-specific verbatim-path behaviour that does not occur on the deployment host; the ordering flake disappears when the router route test runs in isolation; the stale adapter test encodes an assumption (CROMA not yet shipped) that the codebase has since outgrown. None of them is a behavioural defect in shipped code. > **Honesty note.** The precise full-suite *collected* count is > `UNKNOWN — not established from the available evidence`. What the evidence establishes is: the two > targeted suites pass (106, 183), the full suite has 5–6 environmental/ordering failures, and a > re-run of the affected files passes 137. ### 5.5 The evidence-engine determinism suite The evidence engine (the component that turns specialist output into a *proof*) has its own suite — `tests/unit/test_evidence_engine.py` (73 tests) — covering **determinism** and **purity**: the same input produces the same evidence, and computing evidence does not mutate inputs. This is what makes the "evidence is independent of the verdict" property in §6.5 hold. ### 5.6 Invocation traps - **Use the venv interpreter.** `.venv/Scripts/python.exe -m pytest …` on Windows. - **The full-suite run trips a bulk-delete guard** in the authoring sandbox (§10.3). Run the targeted suites to avoid it. - **Do not pipe pytest through `grep`** in the authoring sandbox — output is block-buffered and a killed pipeline swallows it (§10.6). Redirect to a file instead. --- ## 6. Reproduce a live run ### 6.1 The deployed stack The live topology is **Cloudflare Pages → Render → outbound tunnel → GitHub Codespace** (`docs/DEPLOYMENT.md §1`; `README.md` §Deployment): | Layer | Role | Host | |---|---|---| | Cloudflare Pages | static frontend | `https://satquery.pages.dev` | | Render | orchestrator / API gateway (`/api/*`, CORS, wake flow) | `https://` | | GitHub Codespace | FastAPI inference host, CPU, port 8000 | `potential-space-trout-r4ppw969w45j2pvvw` | Deployed revisions (VERIFIED): | Component | Revision | |---|---| | Frontend | `2d7ae53b482d` | | Backend / orchestrator | `89d80eaddec5` | | Inference | `5a0936ace491` | > **Trap.** The deployment sources are **private** repositories, separate from this release. The > `deploy/` directory inside the monorepo working copy is **stale and untracked** — it is *not* the > deployed source (`docs/DEPLOYMENT.md §1`). Reproducing a live run means driving the deployed > endpoints, not rebuilding from `deploy/`. ### 6.2 Health and capabilities ```bash curl --noproxy '*' https:///api/health curl --noproxy '*' https:///api/capabilities ``` The `/api/health` payload (recorded live, `docs/DEPLOYMENT.md §2`): ```json {"status":"ok","service":"satquery-orchestrator", "tunnel":{"agent_connected":true,"agent_id":"codespaces-fd1038","pending":0,"completed":97}, "config":{"codespace_name":"potential-space-trout-r4ppw969w45j2pvvw\n","codespace_port":8000, "transport_mode":"auto","tunnel_timeout_s":150.0,"wake_timeout_s":120.0, "upstream_timeout_s":90.0,"device":"cpu","has_github_token":true}} ``` `/api/capabilities` returns **six tasks, all `available: true`**. A live run requires `tunnel.agent_connected: true`; if the Codespace is stopped, the request parks until the tunnel timeout (`docs/DEPLOYMENT.md §5`). > **`--noproxy '*'` is not optional in the authoring sandbox** (§10.1). The sandbox proxy is dead; > without the flag, the request fails before reaching Render. In a normal environment the flag is > harmless. ### 6.3 What was run, and how to re-run it The behavioural validation is **3 passes × 8 cases = 24 live runs**, each pass **8/8**, with **0 mock nodes** and a live trace bar at **94.4444 %** (`.workbuddy-ai/scratch/live_validation/ LIVE_VALIDATION_POSTFIX.md`). The eight cases exercise all six specialist tasks plus the two router-defect queries (B1, B2). | pass | HEAD | harness | result | |---|---|---|---| | 1 | `ff46eba42b18`+`d413d3672311` | v1 (`fill_input`) | 8/8 | | 2 | `2d7ae53b482d` | v2 asserting | 8/8 | | 3 | `2d7ae53b482d` | v2 asserting (pre-discriminator-fix) | 8/8 (recomputed) | To re-run: drive `https://satquery.pages.dev` in a **headed** browser (the headed requirement is explained in §6.4), upload **one asset per case** for single-image tasks and a **pair** for the temporal tasks, submit the query, and read the run identifier, the `mock_nodes` count, the trace fill, and the answer's `[task]` tag. The eight expected cases are tabulated in [`EVALUATION.md`](EVALUATION.md) §6.2. ### 6.4 The Chrome focus trap — why the harness asserts Pass 1's harness drove the query box with `fill_input()`, which types with **real CDP key events**. A re-run attempt failed on case 1 with `run_id=0002`, `mock_nodes=9`, `answer="No answer yet"`, and only a `capabilities` call — the **mock path**. Root cause: > Chrome **drops synthesized key events when the browser window does not hold OS focus**; the harness > had **no assertion**, so it clicked Run with the page's *default* query still in the box. Measured directly: with Chrome backgrounded, `press_key("Z")` left `#qtext.value` unchanged, while `type_text("Q")` (CDP `Input.insertText`, not focus-gated) inserted fine (`LIVE_VALIDATION_POSTFIX.md`, "Why this pass needed a new harness"). This is the single most important trap for anyone reproducing a live run, because it produces a **silent false pass** — the pipeline "works", it just answered a different question. The fix was to **assert the input state before dispatching**, with three pre-dispatch assertions: - `q_ok` — the query box really held the query; - `obs_ok` — `#obsTail == 'ready'`; - `t0_ok` — both frames present where required. The full account, including the check that pass 1 was **not** infected, is in [`EVALUATION.md`](EVALUATION.md) §6.4–6.5. In short: pass 1's intents are query-specific (A1 reads `taskvqa…temporalnone`, not the default's `taskchange…temporalrequired`), their answers embed the query text, and A6's answer proves two files were uploaded — so pass 1's results are clean. ### 6.5 The recompute-verdicts safety property Pass 3 was launched with a harness build that still carried two *discriminator* bugs (the answer `[task]` tag exists only for region tasks; the intent panel is a concatenated string needing a non-greedy match). Its raw output says `SUMMARY 0/8`; the verdicts are recomputed from the recorded evidence by `recompute_verdicts.py`, giving 8/8. This is not a workaround — it is the intended safety property: > The recorded evidence (run id, `mock_nodes`, intent, answer, assertions) is **independent of the > verdict computation**, so a harness bug can never silently turn a real failure into a pass, and > never costs a re-run to correct. — `LIVE_VALIDATION_POSTFIX.md`, Pass 3. Two further harness bugs were found and fixed, both causing **false failures** (not false passes): the answer `[task]` tag exists only for region tasks, and the intent panel needs a non-greedy match. The harness now computes the dispatched task as `answer_tag` when present, else the reading. Both are recorded because a false failure and a false pass are different risks and the project tracks them separately. ### 6.6 Cold start and tunnel caveats - **Cold start.** Render's free tier sleeps and the Codespace may be stopped. The first request can exceed the client timeout while weights are fetched; a retry a few seconds later normally succeeds. Warm the stack before any demonstration and confirm `agent_connected: true` (`README.md` §Deployment caveats). - **Tunnel gaps (B-07, `OPEN`).** The tunnel agent can be briefly absent; a request during a gap may hang or return 504. **This is not fixed in production.** Root cause: in `auto` mode a tunnel timeout falls through to the forwarded-port path, spending the 120 s wake timeout on a 302 — the observed ~249 s failure (`docs/DEPLOYMENT.md §6`). A patch exists and was deliberately **not** deployed. - **Never retry `POST /api/infer` at the gateway** — a retry consumes inference twice (`docs/DEPLOYMENT.md §7`). --- ## 7. Reproduce and verify the released artifacts ### 7.1 The manifest and checksums are generated, not typed `models/manifest.json` and `models/checksums.sha256` are produced by `release/tools/generate_model_manifest.py`, which reads the actual files: > Generated by reading the files. No byte count or hash is typed by hand. Backbones are NOT > redistributed; they are fetched from the Hugging Face Hub, pinned by revision. — `models/manifest.json` (`"note"` field). Verify the local weights: ```bash sha256sum -c models/checksums.sha256 ``` `models/checksums.sha256` (committed): ``` c5ef31277b67aa01a593aec0eac503eeaccc6d674349fda20ca44c9cc6f8e9fa change/head.pt cfae5e43b97ca930f568dc5b8ae4f36b24e9ff717af226159802206ffd63a82a change_vqa/head.pt 785815729a3a39fc34dc41894efaf00d8739365d970a3f830a326e68ae888dab optical_sar/head.pt 93432f7034be91a8ffd9c1a84e3eeec00bed7832c043fe7f83d2be230284c6bb grounding/head.pt 8527c3ed28a293e13293d48601d48e3ceafa137b9acabddaf5de31a58a509b5c router/adapter.pt 07c76a75fa04624880ed7730590f5fdd7b145a8232e3c0af411c3c545a5adf5e vlm/adapter_model.safetensors ``` The manifest records, per artifact: `id`, `task`, `kind`, `path` (in the project), `hf_path` (on the Hub), `backbone`, `architecture`, `source_metric_artifact`, `config_hash`, `bytes`, `sha256`, and `status`. The manifest's own header records `config_hash: 78f1e3700da15aa1` and `artifact_count: 6`, generated `2026-09-25T18:15:38+00:00`. ### 7.2 Independent Hub verification ```bash export HF_TOKEN=... # token with repo.content.read python release/tools/hf_verify.py ``` `release/tools/hf_verify.py` is an **independent** check: it does not trust the upload step. It reads the manifest, **re-downloads each artifact from the Hub over direct HTTPS**, hashes the bytes it received, and compares against the locally-computed sha256. Its docstring is explicit that it uses direct HTTPS *deliberately*: > Note: `hf_hub_download` is deliberately NOT used here — in this environment it returned an empty > file (sha256 e3b0c442…), which would have produced a false FAIL. Direct HTTPS is the honest check. Recorded result (`release/tools/hf_verify_report.txt`): ``` repo : thundercode/SatQuery private : False sha (HEAD) : 55681e0cddb91a4a5655da98a49bc025e537b657 lastModified : 2026-09-25T18:21:52.000Z files on Hub : 22 STATUS hf_path remote bytes local bytes ---------------------------------------------------------------------------------- MATCH change/head.pt 63,231,009 63,231,009 MATCH change_vqa/head.pt 5,822,809 5,822,809 MATCH optical_sar/head.pt 14,427,457 14,427,457 MATCH grounding/head.pt 12,639,041 12,639,041 MATCH router/adapter.pt 211,961 211,961 MATCH vlm/adapter_model.safetensors 34,798,048 34,798,048 artifacts verified : 6 artifacts failed : 0 OK README.md OK MODEL_CARD.md OK models/manifest.json OK models/checksums.sha256 ``` Total released weight payload: **131,130,325 bytes (~125 MiB)** across the six artifacts (`release/HF_RELEASE_VERIFICATION.md §4`). Exit code 0 with `artifacts failed : 0` means the release is intact. ### 7.3 A verification method that was itself wrong — the empty-file false FAIL This is the most instructive reproducibility incident in the release, and it is recorded because a verifier that silently hashes an **empty file** is a verifier that can lie in *both* directions. The **first** verification attempt reported all six artifacts `DIFFER`, with every remote hash equal to: ``` e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ``` — the sha256 of **empty content**. The cause was **not** the upload: `hf_hub_download` returned an empty file in the authoring environment (a download-path problem), so the verifier hashed nothing. It was caught by a **second, independent method** — a direct `curl` download — which produced the correct hash `8527c3ed28a293e13293d48601d48e3ceafa137b9acabddaf5de31a58a509b5c` for `router/adapter.pt`, byte-identical to the local file and confirmed to be a real PyTorch zip (`PK\x03\x04`, containing `adapter/data.pkl`). The verifier was then rewritten to use direct HTTPS with proxies disabled (`ProxyHandler({})`), and now reports 6/6 `MATCH`. `release/HF_RELEASE_VERIFICATION.md §5.1` states the lesson directly: > The failed first attempt is recorded because a verifier that silently hashes an empty file would > have produced a **false failure** — and, with a different bug, could just as easily have produced > a **false pass**. **What a third party should take from this.** Do not use `hf_hub_download` as the *sole* verification path. The honest check is a direct HTTPS download with proxies disabled, hashing the bytes received, and comparing sizes as well as hashes. `hf_verify.py` does all three (`size_ok = remote_bytes == local_bytes == len(content)`; `hash_ok = remote_hash == local_hash`). ### 7.4 Cross-checks against independently-recorded hashes Two of the six hashes can be checked against values recorded **elsewhere** in the project, independent of this release (`release/HF_RELEASE_VERIFICATION.md §6`): | Artifact | Recorded elsewhere | Computed here | Agree | |---|---|---|---| | `change_vqa_head` | `cfae5e43…d63a82a` (`artifacts/change_vqa/run/PROMOTION.json`) | `cfae5e43…d63a82a` | **yes** | | `vlm_lora_adapter` | `07c76a75…a5adf5e` (adapter provenance) | `07c76a75…a5adf5e` | **yes** | This is a stronger form of verification than self-consistency: it means the released bytes match hashes that were recorded by the training/export process, not merely by the release step. ### 7.5 The evidence archive `release/tools/build_archive.py` builds the Phase-7 evidence archive. Per owner decision **G3**, everything under `artifacts/` is included **verbatim** — including duplicates and caches — so the archive is **~4 GB** and requires ZIP64. The layout: ``` SatQuery_AI_Final_Archive_2026-09-25/ README_ARCHIVE.md (what is inside, what was excluded and why) release/ (the curated public release tree, verbatim) artifacts/ (VERBATIM, per owner decision G3) evidence/ live_validation/ (3 validation passes + screenshots) delivery/ (delivery report + handoff) verification/ (state, manifests, verification reports) ``` Weights and caches are stored with `ZIP_STORED` (no recompression) to avoid wasting hours on already-compressed data. The archive **excludes** secret/token files, OS junk, virtualenvs, `node_modules`, the HF hub cache, and temp browser profiles — documented in `README_ARCHIVE.md`, never silent. It is verified by `release/tools/verify_archive.py`, which extracts to a separate temp directory and checks the CRC of every member plus the sha256 of every artifact against the live files. --- ## 8. What "reproduce" means for the two externally-trained artifacts Six trained artifacts are released. **Four were trained inside the project's own pipeline; two were trained outside this repository** on external GPU. This distinction is the heart of the reproducibility contract, so it is stated plainly rather than implied. | Artifact | Where it trains | Guide | |---|---|---| | `router/adapter.pt` | local CPU | `configs/base.yaml` §router | | `grounding/head.pt` | local | `configs/base.yaml` §grounding_training | | `change/head.pt` | local | `configs/base.yaml` §change | | `optical_sar/head.pt` | local, seed sweep | `docs/PHASE14_OPTICAL_SAR_DECISIONS.md` | | `change_vqa/head.pt` | **external GPU (Kaggle)** | `docs/R02_KAGGLE_TRAINING_GUIDE.md` | | `vlm/adapter_model.safetensors` | **external GPU** | `configs/base.yaml` §training | ### 8.1 What the repository reproduces for the external artifacts For the two externally-trained artifacts, this repository reproduces: 1. **The promotion gate** — the checks the returned checkpoint must pass before it is accepted (byte-identity, sha256 match against the recorded digest, zero non-finite tensors). 2. **The evaluation** — the scoring path that turns a checkpoint into the recorded metrics. 3. **The serving wiring** — the code path that loads the artifact and routes to it. It does **not** ship a one-command retrain for those two artifacts. That is stated, not implied. ### 8.2 The change-VQA path (Kaggle) `docs/R02_KAGGLE_TRAINING_GUIDE.md` is the authoritative guide; it states its own scope: > **Audience:** the person running the external GPU job. **Scope:** everything needed to go from > this repository to a returned, reviewable checkpoint. **Not in scope:** deciding whether R-02 is > done — that happens after the artifact comes back. Its status is `IMPLEMENTATION_READY_FOR_EXTERNAL_TRAINING`, and it is explicit that *"training produces an artifact, not a verified capability, and the run record says `TRAINED_UNVERIFIED`."* The reproduction is **byte-scoped**: the upload set is defined by a digest so the external run can be shown to have started from exactly this code. | Item | Value | |---|---| | Upload set | **249 files**, at the root of the archive | | Digest-defining subset | **248 files, 67,225,247 bytes** | | Manifest hash | `e98d3854db5f3edec76c1748c0c1bc3041d006ae5858debee31701539af7d0d6` | | Archive | `satquery-ai.zip` — 250 entries, 60.0 MB compressed | | Inside the archive | `UPLOAD_MANIFEST.txt` — every file with size and SHA256, plus the digest | The digest covers **248 files, not 249**, for a principled reason the guide states: the guide itself lives in `docs/`, so it is part of the upload set — but it also *records* the digest, and a document cannot contain the hash of itself. The digest is therefore defined over the upload set **excluding this one file**; that is the only definition that can be reproduced. For the same reason the byte total for all 249 files is **not** quoted in the guide — it changes whenever the guide is edited; the authoritative value is in `UPLOAD_MANIFEST.txt`. The frozen STANet checkpoint the head depends on is verified by size and SHA256 in the notebook: ``` checkpoint : /artifacts/change/levir_change_v001/head.pt bytes : 63,231,009 sha256 : c5ef31277b67aa01a593aec0eac503eeaccc6d674349fda20ca44c9cc6f8e9fa checkpoint verified against the frozen digest ``` Training knobs are fixed, not tuned-to-pass: `HARD_STOP_SECONDS = 3 * 3600`, `EPOCHS = 40`, `BATCH_SIZE = 256`, `DEVICE = "cuda"`, `SEED = 42` (the trainer's own defaults are `epochs=40, batch_size=128, seed=42, patience=6, time_limit=10800s`). The guide instructs: *"Do not change these to get past an error."* Split integrity is measured, not assumed: ``` measured corpus ground truth (unique scenes / resolved questions): Train 1600 scenes 65967 questions Val 400 scenes 16441 questions Test 968 scenes 39686 questions Test2 968 scenes 31036 questions integrity clean : True scenes/split : {'Train': 1600, 'Val': 400} overlap : {'Train|Val': 0} ``` The two test sets (Test 39686, Test2 31036) are why change-VQA is quoted under **two** test sets (rule 3) — and why the promotion artifact carries both. ### 8.3 The VLM path The VLM artifact is a PEFT LoRA adapter (`r=16, alpha=32, dropout=0.05`) on the text-model projections of `HuggingFaceTB/SmolVLM-500M-Instruct` (`models/manifest.json`). Its acceptance status is **`ACCEPTANCE-REJECTED`** — the metrics are usable (`exact_match 0.963`, `f1 0.96432`) but the adapter was **not promoted**. `USABLE ≠ ACCEPTED` (rule 7). Reproducing this artifact's *training* requires the external GPU environment described in `configs/base.yaml` §training; reproducing its *evaluation* and its *serving wiring* is done by this repository. The rejection is preserved in the record (`artifacts/vlm/phase6_closure.json`), not scrubbed. ### 8.4 The optical-SAR seed sweep The optical-SAR head was trained locally with a **seed sweep**; the selected production artifact is `fusion_head_production_v001`, and its pre-registered metric is the 11.5 target recorded in `docs/PHASE12_115_METRIC_COMPUTED.md` and `artifacts/optical_sar/fusion_head_production_v001/ pre_registered_115_metric.json`. The input dimension is **derived at runtime** (not hardcoded) — `2318 = 3×768 + 12 + 2` (`fusion_head.py:74`, `:311`), independently confirmed in `docs/PHASE14_OPTICAL_SAR_DECISIONS.md §1`. The ruling is **`OPEN`**, and the accuracy (`0.931`) is never quoted without the macro-F1 (`0.434161`) — rare classes are poorly handled (rule 4). ### 8.5 What is *not* one-command There is no script that takes this repository and produces all six artifacts in one invocation. The four locally-trained artifacts have local training paths, and the two external ones require an external GPU environment. This is a deliberate boundary: the release ships **frozen artifacts with provenance**, not a retraining harness. --- ## 9. What is NOT reproducible from this release — exhaustive This section is the honest counterweight to §1. Every item below is something a reader might reasonably expect to reproduce and **cannot** from this release alone. ### 9.1 Not reproducible because the source is not released | Item | Reason | |---|---| | 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`). | | The **live deployment** itself | Requires the private sources plus the Render/Codespace/Cloudflare accounts. | | The **monorepo working copy** | Local only, no remote; 334 dirty entries (`docs/DEPLOYMENT.md §1`). | ### 9.2 Not reproducible because it does not exist or was not run | Item | Reason | |---|---| | **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). | | **Router test-split number** | **The test split was never run.** The only router number is validation, ungated, `n = 86` (rule 5). | | **Captioning benchmark** | `IMPLEMENTED` but the benchmark is `NOT RUN` ([`EVALUATION.md`](EVALUATION.md) §4.8). | | **A composite / "overall accuracy"** | Forbidden by construction: `official_aggregate_weights: null`; `normalize.py` raises on aggregate requests (rule 8). | ### 9.3 Not reproducible because the data is large or external | Item | Reason | |---|---| | **CDVQA / SECOND imagery** | Public but large. The release documents the acquisition + name-verification procedure, not the data. | | **BigEarthNet full corpus** | **Not downloaded** — only a 28k S2 subset was used. | | **LEVIR-CD-256 / VRSBench raw data** | Not redistributed; the splits (7120/1024/2048, and VRSBench `n=16159`) are documented, not shipped. | | **Fusion held-out test (n=4000)** | Documented, not shipped. | | **Feature caches** | Reproducible but included only in the verbatim archive, not in the release. | ### 9.4 Not reproducible because it was external-GPU training | Item | Reason | |---|---| | **change-VQA retrain** | External GPU (Kaggle); the repo reproduces the *promotion gate*, *evaluation*, and *serving wiring*, not a one-command retrain (§8.2). | | **VLM LoRA retrain** | External GPU; and the adapter is `ACCEPTANCE-REJECTED` anyway (§8.3). | ### 9.5 Not reproducible because it is frozen paperwork | Item | Reason | |---|---| | **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. | ### 9.6 Not reproducible *as a bit-identical training run* The released **artifacts** are byte-identical (guaranteed by hash). The **training runs** that produced them are not claimed to be bit-reproducible on arbitrary hardware: floating-point non-determinism, different accelerators, and library-version drift all affect a training run. What is guaranteed is: the *config* is frozen, the *seed* is recorded (`42`), and the *artifact* is hashable. ### 9.7 Explicit non-claims To remove any ambiguity, this release does **not** claim: - that the live system achieves any single accuracy number; - that the router's `0.965116` is a test result (it is validation-only, `n = 86`); - that the VLM adapter is accepted (it is rejected); - that calibration improves ECE (it worsens it, `0.013755 → 0.014929`); - that optical-SAR fusion is production-ready (ruling `OPEN`, macro-F1 `0.434161`); - that change-VQA is settled (ruling `OPEN`, two test sets); - that grounding is solved (`0.2838` canonical IoU — useful, not solved); - that B-07 tunnel gaps are fixed (patch prepared, `NOT deployed`). --- ## 10. Environment traps recorded for reproducibility These are the edges that cost real debugging time in the authoring environment. They are recorded so a third party does not rediscover them — and, more importantly, so that a reviewer can distinguish "this is a documented environment quirk" from "this is a defect in the released code". ### 10.1 The dead sandbox proxy needs `--noproxy '*'` / `ProxyHandler({})` **Symptom.** Outbound HTTP calls fail or hang in the authoring sandbox. **Root cause.** The sandbox proxy is dead; requests are routed to it and never reach the target. **Fix.** Disable proxies for the call: ```bash curl --noproxy '*' https:///api/health ``` ```python opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) ``` `release/tools/hf_verify.py:37-38` does exactly this (`opener_no_proxy()`). In a normal environment the flag is harmless; in the sandbox it is mandatory. ### 10.2 pytest exists only in the repository virtualenv **Symptom.** Invoking the system `pytest` fails or resolves to a different interpreter. **Root cause.** pytest is installed only in `.venv`. **Fix.** Always invoke the venv interpreter explicitly: ```bash .venv/Scripts/python.exe -m pytest tests/unit/test_frontend_live_wiring.py -q ``` ### 10.3 The full-suite pytest trips a bulk-delete guard **Symptom.** Running the whole `tests/unit` tree trips 4× `test_safe_delete_shim` failures. **Root cause.** Windows **verbatim-path** defects in the sandbox's bulk-delete guard. The precise condition under which the shim intermittently triggers on Windows is `UNKNOWN — not established from the available evidence`. **Fix / workaround.** Run the targeted suites (106 and 183 pass cleanly); treat the 4 shim failures as environmental, not regressions (§5.4). ### 10.4 Cloudflare 308-redirects `X.html` → `/X` **Symptom.** A request to `something.html` returns a 308 redirect. **Root cause.** Cloudflare Pages 308-redirects `X.html` → `/X`. **Fix.** Reference the **extensionless** path (`docs/DEPLOYMENT.md §7`). ### 10.5 Chrome drops synthetic CDP key events without OS focus **Symptom.** A browser-driven run silently answers the *default* query; the query box looks untouched; `mock_nodes` is non-zero. **Root cause.** Chrome **drops synthesized key events when the browser window does not hold OS focus**. `press_key`/`fill_input` (real CDP key events) are focus-gated; `Input.insertText` (`type_text`) is not. **Fix.** Use `type_text` (not `fill_input`), and **assert the input state before dispatch** (`q_ok`/`obs_ok`/`t0_ok`). This is the single most dangerous trap because it produces a **silent false pass** (§6.4; [`EVALUATION.md`](EVALUATION.md) §6.4). ### 10.6 Buffering hides liveness (two separate causes) **Symptom 1.** A redirected browser-harness log stays at 0 bytes until the process exits — indistinguishable from a stall. **Root cause 1.** `browser-use` block-buffers stdout even when redirected. **Fix 1.** Force line-buffering and print a per-case marker (`CASE_START `). **Symptom 2.** Piping the harness through `grep` swallows all output if the pipeline is killed. **Root cause 2.** `grep` block-buffers when piped. **Fix 2.** Redirect to a file instead of piping through `grep`. ### 10.7 `hf_hub_download` returned an EMPTY file → a false FAIL **Symptom.** Every artifact hashes to `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` (empty content) and the verification reports all `DIFFER`. **Root cause.** `hf_hub_download` returned an empty file in the authoring environment (a download-path problem), so the verifier hashed nothing. The upload was fine. **Fix.** Use a **direct HTTPS download with proxies disabled**, hash the bytes received, and compare **size as well as hash**. `release/tools/hf_verify.py` is the honest check (§7.3). ### 10.8 `codespace_name` carries a trailing `\n` **Symptom.** `/api/health` reports `"codespace_name":"potential-space-trout-r4ppw969w45j2pvvw\n"`. **Root cause.** B-02; the wake path strips it, so it is **cosmetic**. **State.** `OPEN (cosmetic)`; the fix is not deployed (`docs/DEPLOYMENT.md §2, §6`). ### 10.9 A forwarded Codespace port returns 302 for a private repo **Symptom.** A forwarded Codespace port returns HTTP 302 instead of serving. **Root cause.** Private-repo port forwarding returns 302 — which is *why* the outbound tunnel exists (`docs/DEPLOYMENT.md §7`). **Fix.** Use the tunnel, not the forwarded port. ### 10.10 The tunnel agent must be started by the devcontainer **Symptom.** A restarted Codespace comes up with `agent_connected: false`. **Root cause.** The tunnel agent is started by the devcontainer `postStartCommand`; if it does not run, the agent is absent. **Fix.** Ensure `.devcontainer/` starts the agent on start (`docs/DEPLOYMENT.md §7`). --- ## 11. What is NOT RUN / OPEN / BLOCKED for this topic Per the documentation standard, this section lists, for the topic of reproducibility itself, what is not settled. | Item | Status | Note | |---|---|---| | End-to-end system benchmark | **NOT RUN** | No such benchmark exists; nothing to reproduce. | | Router **test**-split number | **NOT RUN** | Only validation (`n = 86`, ungated) exists. | | Captioning benchmark | **NOT RUN** | Implemented; not benchmarked. | | VLM adapter acceptance | **REJECTED** | Metrics usable; not promoted. `USABLE ≠ ACCEPTED`. | | Optical-SAR fusion ruling | **OPEN** | Accuracy without macro-F1 is never quoted. | | Change-VQA ruling | **OPEN** | Two test sets. | | Grounding acceptance | **OPEN** | Two protocols; modest IoU. | | Calibration improvement | **REJECTED (retained in frozen config)** | ECE worsened `0.013755 → 0.014929`. | | B-07 tunnel gaps | **OPEN** | Patch prepared, **NOT deployed**. | | B-02 trailing `\n` | **OPEN (cosmetic)** | Not deployed. | | Licence | **OPEN** | **No LICENSE file exists**; owner decision. | | Exact full-suite *collected* count | **UNKNOWN** | Per-suite counts are known (106, 183, 137 re-run); the single collected total is not established. | | Exact Windows trigger for the delete-shim flake | **UNKNOWN** | The intermittent condition is not established from the available evidence. | | Bit-identical training on arbitrary hardware | **NOT CLAIMED** | Only artifacts are guaranteed byte-identical, by hash. | --- ## 12. Where the evidence lives | Evidence | Location | |---|---| | The 20-claim metric verifier | `release/tools/verify_readme_metrics.py` | | Its committed output | `release/tools/readme_metrics_report.txt` (`ALL CLAIMS VERIFIED`) | | The Hub verifier | `release/tools/hf_verify.py` | | Its committed output | `release/tools/hf_verify_report.txt` (6/6 `MATCH`) | | Hub release verification write-up | `release/HF_RELEASE_VERIFICATION.md` | | Model manifest (generated) | `models/manifest.json` | | Checksums (generated) | `models/checksums.sha256` | | Manifest generator | `release/tools/generate_model_manifest.py` | | Archive builder / verifier | `release/tools/build_archive.py`, `release/tools/verify_archive.py` | | Config loader + invariants | `core/config.py` (hash `:76-80`; `_validate` `:94`) | | Frozen config registry | `configs/base.yaml` (§evaluation `:273`, §deployment `:282`) | | Dependency manifest + profiles | `requirements.txt` | | Live topology, env vars, traps | `docs/DEPLOYMENT.md` | | 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` | | Verdict recomputation | `recompute_verdicts.py` | | Kaggle training guide (change-VQA) | `docs/R02_KAGGLE_TRAINING_GUIDE.md` | | Optical-SAR decisions | `docs/PHASE14_OPTICAL_SAR_DECISIONS.md` | | Pre-registered 11.5 metric | `docs/PHASE12_115_METRIC_COMPUTED.md` | | Grounding resolution decision | `docs/PHASE7_RESOLUTION_DECISION.md` | | Delivery test results (94/183/5–6/137) | `docs/FINAL_DELIVERY_REPORT.md §7` | | Frontend suite re-run count (106) | `release/CURRENT_RELEASE_STATE.md:121`, `README.md` §The test suites | | Evaluation protocols and honesty rules | [`EVALUATION.md`](EVALUATION.md) | | Benchmarks and their statuses | [`BENCHMARKS.md`](BENCHMARKS.md) | | Model cards and artifact details | [`MODELS.md`](MODELS.md) | | Datasets and splits | [`DATASETS.md`](DATASETS.md) | | Training paths | [`TRAINING.md`](TRAINING.md) | | Known limitations | [`LIMITATIONS.md`](LIMITATIONS.md) |