Spaces:
Running on Zero
Running on Zero
| # Model artefacts | |
| **Three capabilities have a model behind them.** `cattle_detection` and | |
| `poultry_count` run a checksummed YOLOX-m ONNX graph (ADR 0017, ADR 0018), and | |
| `cattle_identity` has a frozen DINOv2-small embedding backbone (ADR 0020) β a | |
| backbone, not an identity model, and the capability stays `coming_soon` because | |
| nothing has tested it on the same animal twice. The rest have nothing, and the | |
| service says so rather than inventing a result. | |
| ``` | |
| models/ | |
| _artefacts/ | |
| yolox_m-0.1.1rc0.onnx the shipped detector, shared by two capabilities | |
| dinov3_vits16-onnx-1.onnx frozen embeddings, locally exported | |
| cattle_detection/ | |
| model_card.json | |
| poultry_count/ | |
| model_card.json | |
| cattle_identity/ | |
| model_card.json | |
| alternates/ | |
| yolox_onnx/ Apache-2.0, kept working, never registered | |
| model_card.json | |
| yolox_s-0.1.1rc0.onnx | |
| dinov2_embedding/ Apache-2.0 fallback; measurably worse, kept working | |
| model_card.json | |
| dinov2_small-onnx-1.onnx | |
| megadescriptor/ CC-BY-NC-4.0. Installed to be measured, never served | |
| model_card.json | |
| megadescriptor_l_384-onnx-1.onnx | |
| miewid/ no licence at all. Same: measured, never served | |
| model_card.json | |
| miewid_msv3-onnx-1.onnx | |
| ``` | |
| **Two of those alternates are weights Animap may not serve.** They are on disk | |
| because a licence stopped being a reason to leave a model unmeasured β directive | |
| Β§40.2 asked for MegaDescriptor against DINOv3 and the honest answer needed the | |
| benchmark, not an argument about why it could not be run. Nothing about the terms | |
| changed: `app/adapters/licences.py` still reports both as non-servable, | |
| `registry.refused()` still names them, and they load only under | |
| `ANIMAP_LICENCE_POLICY=record`, which logs every load at ERROR and keeps it. | |
| Under the default `enforce` policy they are refused and their adapters report | |
| unavailable. | |
| An alternate keeps its artefact **inside its own directory**. `load_card` refuses | |
| a path that climbs out of the card's grandparent, which is what stops a card | |
| being a way to load whatever is on the box. | |
| Weights are not in git. Cards are β a governance record nobody can see in a diff | |
| is not one. | |
| ## Installing them | |
| ```bash | |
| .venv/bin/python scripts/install_models.py # fetch what the cards name | |
| .venv/bin/python scripts/install_models.py --check # verify, never fetch | |
| ``` | |
| **Some artefacts have no URL to fetch.** An ONNX export of a published | |
| checkpoint is not a file the publisher hosts, so its card carries `produced_by` | |
| naming the script that makes it, and `install_models.py` refuses to download | |
| rather than writing a model-card web page into a `.onnx` file: | |
| ```bash | |
| .venv/bin/pip install -r requirements-export.txt # build-time only, never shipped | |
| .venv/bin/python scripts/export_embedding.py | |
| ``` | |
| The export is checked against torch before it is written and is | |
| byte-reproducible, so the sha256 on a card pins the exact bytes that were | |
| validated. **It is reproducible from the same exporter code, not from the | |
| weights alone** β renaming a wrapper attribute changes the graph's node names and | |
| therefore the checksum, without changing a single weight. | |
| The script reads the committed cards, downloads what each one names, and refuses | |
| anything whose sha256 does not match. It has no list of its own, so there is | |
| nothing to drift out of date. **The service never calls it.** A model that is not | |
| on disk leaves its capability unavailable. | |
| ## Permissive licences only | |
| **A card naming AGPL-3.0 is refused at load, not warned about.** `discover()` | |
| skips it and the capability stays unavailable. | |
| **And a card is not trusted about its own licence.** `app/adapters/licences.py` | |
| holds what each *runtime* really loads under, read from a primary source and | |
| dated, and `discover()` refuses a card whose declared licence disagrees with it. | |
| That closes the exploit ADR 0017 records β `"license": "Apache-2.0"` written over | |
| a path to copyleft weights β without depending on somebody having hard-coded the | |
| right runtime name. It also catches the version of the trap that arrives from | |
| upstream: `BVRA/MegaDescriptor-L-384/config.json` declares `"license": "mit"` | |
| inside the checkpoint config while the repository is CC-BY-NC-4.0. Ultralytics YOLO was the shipped | |
| detector until ADR 0017 and is now not installed at all; read | |
| [`docs/adr/0017-ultralytics-licence.md`](../../../docs/adr/0017-ultralytics-licence.md) | |
| before reaching for it again. | |
| Four mechanisms hold that position, because it is the failure that is cheap now | |
| and expensive later: `requirements.txt` omits the package, `.dockerignore` | |
| excludes `*.pt`, the `Dockerfile` fails the build if one arrives anyway, and | |
| `providers.discover()` refuses the card. `GET /health` reports | |
| `artefact_licenses`, so a deployment that picked one up says so from outside. | |
| ## Adding one | |
| Create a directory named for the capability, containing a `model_card.json`. | |
| Every field below is required; a card missing any of them is refused at load, and | |
| the capability stays unavailable: | |
| ```json | |
| { | |
| "model_id": "animap-cattle-weight", | |
| "version": "0.1.0", | |
| "capability_key": "cattle_weight", | |
| "runtime": "ultralytics", | |
| "artefact": "../_artefacts/model.pt", | |
| "source": "https://β¦ where it came from, exactly", | |
| "license": "the licence, and it must permit this use", | |
| "sha256": "checksum of the artefact file", | |
| "validation_notes": "What was tested, on what data, with what result.", | |
| "geography_validated": ["NG"] | |
| } | |
| ``` | |
| ### Why each field is enforced | |
| **`source` and `license`** β a model of unknown origin cannot be shipped, and a | |
| licence that forbids commercial use is a legal problem discovered too late. | |
| **`sha256`** β verified on every load. The artefact on disk must be the one that | |
| was validated, not one swapped in afterwards. | |
| **`validation_notes`** β must be non-empty, and whitespace does not count. This | |
| is the field that says somebody checked the model works. A card can otherwise be | |
| filled in purely to get past the loader. | |
| **`geography_validated`** β a body-condition model trained on European dairy | |
| cattle is not validated for White Fulani in Kaduna. Recording where it was tested | |
| is what stops that being assumed. The sentinel `global-coco` means "validated | |
| nowhere, only pretrained", and `Capability.may_be_enabled` refuses to promote a | |
| capability past `beta` while it is present. | |
| **`runtime`** β not required by governance, because governance is about | |
| provenance. But a card without it can be inspected and not run: `detectors.build` | |
| refuses rather than defaulting, since a default would let a typo change which | |
| licensed model produced a farmer's result. | |
| ### Where the artefact may live | |
| The `artefact` path may point sideways β several capabilities share one detector, | |
| and storing the file twice would mean two things to keep in step. It may not | |
| point out of the models tree; `load_card` refuses that. A card under | |
| `alternates/` is two levels down and outside the discovery glob, so an alternate | |
| can never silently become the model that answered a request. | |
| ## What is not allowed | |
| - **No runtime downloads.** The service never fetches a model. An artefact | |
| arrives through a reviewed change, or it does not arrive. | |
| - **No binaries in git.** `.gitignore` excludes `*.tflite`, `*.onnx`, `*.pt` and | |
| `*.pth`, and everything in this directory except the READMEs and the cards. | |
| - **No placeholder adapters.** A capability with a validated artefact but no | |
| implemented adapter returns `501`, not a plausible-looking result. An adapter | |
| that returns something believable is precisely the failure ADR 0005 exists to | |
| prevent. | |
| ## Promoting a capability | |
| The path from nothing to trusted, per ADR 0006: | |
| 1. Register the capability β already done for all 21. | |
| 2. Install a validated artefact here. | |
| 3. Implement its adapter and check the output against this contract. | |
| 4. Move `coming_soon` β `beta`. The app shows a Beta badge; results are stored | |
| with their confidence, and nothing acts on them automatically. | |
| 5. Collect corrections. `corrections.selected_interpretation_id` captures when | |
| the model's second-ranked candidate was the right one, which is the highest | |
| value training signal the schema produces. | |
| 6. Promote to `enabled` when the corrections say it earns it. | |
| Step 6 is a decision about evidence, not a configuration change β and today it is | |
| also blocked mechanically, because both beta capabilities carry the | |
| `global-coco` sentinel. | |