File size: 33,365 Bytes
4b98524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
"""Matching a capture against a farm's enrolled animals, and refusing to name one.

Directive Β§6.4, and the counterpart to `app/counting.py`: that module turns boxes
into a claim, this one turns a nearest neighbour into a candidate. Both exist
because the hard part is not the model β€” it is being honest about the cases the
model cannot carry.

**The measurement this runner ships against is the open-set one, not the
closed-set one.** Run `8db9e0bd1b30`
(`experiments/cattle_identity/metrics.json`) enrolled 169 animals from the
muzzle268 database and measured, on 789 probes of animals that *were* enrolled:
top-1 0.9772, top-3 0.9937, MRR 0.9853, against a chance rate of 0.005917. That
is the number worth quoting and it is not the number that decides the product.

On 416 probes of animals nobody enrolled, **the unthresholded false-accept rate
is 1.000**. Every unenrolled animal comes back as somebody, because every query
has a nearest neighbour and nothing about a nearest neighbour knows the right
answer was absent. The two similarity distributions overlap badly β€” enrolled
probes median 0.9707, unenrolled median 0.9042 with an unenrolled *maximum* of
0.9776 β€” so the cutoff that admits no impostor at all sits at 0.98 and accepts
**24.08%** of the correct matches.

`MEASURED_POLICY` is that operating point, and choosing it is the whole design:

- Three quarters of the time an enrolled animal is photographed, this runner
  claims `no_confident_match` **and still returns the ranked candidates**, so a
  person sees the names and picks one. That is a worse headline and the same
  information.
- The alternative β€” no threshold, always name the top candidate β€” is the one
  that reads better and puts a neighbour's cow in a farm's records 100% of the
  time an unenrolled animal is photographed.

`Β§6.4`'s confirm step is therefore load-bearing rather than decorative.

**What carries that requirement to a device, precisely.** This paragraph used to
say `IdentityResult.to_json` states `requires_confirmation: True` on every path.
It does β€” and this runner never calls it. The served result is
`schemas.InferenceResult`, which has no such field, so **the flag reaches
nobody** and citing it here described a guarantee that was not on the wire.

What actually crosses is the capability row. `Requirement.HUMAN_CONFIRMATION` is
on the registry entry, travels verbatim to the API, and is published as
`requirements: ["guided_capture", "human_confirmation"]` by `GET /capabilities`,
where a database constraint forces Β§6.4's four buttons to travel beside it. That
is a real mechanism and it is a **different endpoint from the result**: a client
that renders a result without having read `/capabilities` has nothing in the
payload telling it to ask. Closing that gap means a field on `InferenceResult`,
which is a contract change for every capability and is not made here.

What this runner does guarantee on every path is narrower and worth stating
exactly: no result carries an interpretation, an `observation_confidence`, or a
non-`None` confidence on any observation; `identity_candidate` is emitted only
when the measured policy accepted; and `_result` raises on
`identity_without_confirmation`. A result from here cannot *look* settled. It
relies on the client having read the capability to know it must ask.

**What is not measured here.** The database is US beef breeds β€” Angus, Angus x
Hereford, Continental x British crosses. No Nigerian and no zebu animal has been
through this, and a White Fulani is white all over, so the coat-pattern signal
the published re-identification literature leans on is absent for the herds this
product is for. The registry carries `UNVALIDATED_GEOGRAPHY`, which is what stops
`may_be_promoted_to_production` mechanically.

## The cold-gallery cost, which is a real limit and not a solved problem

Measured on 2026-08-23 over this service's own HTTP path by
`scripts/verify_identity_wiring.py`, on an Apple M-series laptop, single process,
enrolling five muzzle photographs per animal:

| Register | Enrolment photographs | Cold request | Per photograph | Warm median |
|---|---|---|---|---|
| 20 animals | 100 | **11.13 s** | 0.11 s | **0.059 s** |
| 60 animals | 300 | **60.35 s** | 0.20 s | **0.07 s** |

`_VECTOR_CACHE` is what separates the cold column from the warm one, and it only
helps the warm one. **A farm's first identification pays for embedding its whole
register.**

**Both per-photograph rates are recorded because they disagree**, and the honest
reading is that this is a loaded shared laptop rather than a controlled
measurement β€” the 0.20 s run had a test suite beside it. Extrapolate on the
slower one; a latency figure from a shared machine is not a latency figure, which
is a rule this repository already writes down in
`experiments/cattle_respiratory/README.md`. At 0.20 s a 169-animal register is
about **169 seconds** cold.

Neither rate is the 59 ms in `backbones.py`. That is the embed call alone on a
frame somebody already decoded; this path also reads the blob and decodes a JPEG.
An earlier version of this paragraph extrapolated on 59 ms and so put a
169-animal farm at "about a minute" β€” roughly three times better than measured,
in a paragraph whose entire purpose is to be honest about a limit.

**Even the small register does not fit the inline job model.** `app/main.py` runs
jobs synchronously, and this service declined to wire an eighteen-second
capability inline at all; eleven seconds for twenty animals is the same order and
a hundred and sixty-nine seconds is not close. So the honest statement is not
"wired for the farm sizes Animap has" β€” it is that **every** cold request is slow
and only the warm path is fast, so a deployment has to keep the cache warm rather
than treat the cold case as an edge.

The fix is not a bigger cache. It is for the API to store each enrolment vector
beside its `MediaAsset` and send vectors instead of media ids, which moves the
cost to enrolment time where a person is already waiting. That is a change to the
API's schema and is not made here.

**Where these numbers come from, since they cite no run record.** They are a
session measurement, not a benchmark, and the distinction matters in a repository
whose whole discipline is that a figure names a run id. The script is
`scripts/verify_identity_wiring.py`; it is reproducible and it has not been
through the experiment harness, so nothing here may be quoted as a measured
accuracy. What it does establish is that the wiring reproduces the benchmark
rather than merely citing it: over 122 held-out probes against a 60-animal
gallery, top-1 similarity ran min 0.8336, median 0.9764, max 0.9930 against the
run record's min 0.8336, median 0.9707, max 0.9930, and rank-1 was right 121
times out of 122. The accept rate was 41% against the benchmark's 24.08%, which
is the direction a 60-animal gallery moves it against 169.
"""

from __future__ import annotations

import threading
from collections import OrderedDict
from uuid import UUID, uuid4

import numpy as np
from PIL import Image

from app.adapters.embedding import DINOV3_SPEC, OnnxEmbeddingAdapter
from app.adapters.embedding.identity import (
    ENROLMENT_VIEWS,
    PRIMARY_VIEW,
    Embedding,
    IdentityIndex,
    IndexMismatch,
    OpenSetPolicy,
)
from app.capabilities import FORBIDDEN_CLAIMS, Capability
from app.media import MediaRef, MediaStore
from app.providers import ModelArtefact
from app.quality import assess
from app.schemas import (
    EnrolledAnimal,
    InferenceLocation,
    InferenceRequest,
    InferenceResult,
    Observation,
    QualityCheck,
)

#: The operating point measured in run `8db9e0bd1b30`, and the only policy this
#: runner ships.
#:
#: `accept_similarity` 0.98 is the lowest cutoff in the published sweep whose
#: false-accept rate is 0.0. `accept_margin` 0.0 is the measured companion and it
#: never rejects anything β€” a margin can only be non-negative β€” which is
#: deliberate rather than an oversight: `open_set_margin_sweep` in the same run
#: shows the margin rule buys nothing here. At margin 0.07 it holds the
#: false-accept rate to 0.0072 while accepting 23.45% of correct matches, which
#: is *worse on both axes* than the similarity rule alone. The rule stays wired
#: because a future backbone may separate the two distributions differently, and
#: it is set where the evidence puts it.
#:
#: **Both numbers cite the run that produced them and `OpenSetPolicy` will not
#: accept them otherwise.** Its `__post_init__` raises `UnmeasuredThreshold` on a
#: threshold with no run id, which is what stops a cutoff being tuned by whoever
#: is looking at a demo that morning.
MEASURED_POLICY = OpenSetPolicy.measured(
    accept_similarity=0.98,
    accept_margin=0.0,
    run_id="8db9e0bd1b30",
    measured_on="muzzle268-169enrolled",
)

#: Said on every run, whatever the result, in the shape `CountingProfile`
#: established. Β§6.4's product statement, cut to what a person reads.
STANDING_WARNING = (
    "A suggestion, not a record. Animap proposes an animal and you confirm it β€” "
    "an unconfirmed match is never written to this animal's history."
)

#: Said whenever candidates are returned at all, because the open-set result is
#: not something a reader can infer from a ranked list.
UNENROLLED_WARNING = (
    "An animal that has never been enrolled will still produce a nearest match. "
    "Measured on 416 photographs of unenrolled cattle, every one of them came "
    "back as somebody. If this animal is new, register it rather than picking "
    "the closest name."
)

#: How many candidates a result carries. Β§6.4 shows a ranked list rather than one
#: answer, and `evidence_correction.selected_interpretation` is the reason: a
#: farmer picking the second name records *"rank 2 was right"*, which its own
#: docstring calls the highest-value training signal in the table. Three, because
#: `closed_set_top3_accuracy` is 0.9937 and a fourth carries no measurement.
TOP_K = 3

#: Enrolment vectors held between requests, keyed by media id and artefact.
#:
#: **Without this the design does not fit the inline job model.** The gallery
#: travels on the request so the service holds no farm state
#: (`schemas.EnrolledAnimal` says why), and the cost of that is re-embedding
#: every enrolled photograph on every identification. At the **measured 0.20 s**
#: per enrolment photograph β€” end to end, including decode, not the 59 ms embed
#: call `backbones.py` reports on a frame somebody already decoded β€” a farm with
#: a hundred animals and the five-shot enrolment the accuracy was measured on is
#: 500 photographs, or **about a hundred seconds a request**. That is far past
#: the inline ceiling `app/main.py` describes and far worse than the
#: eighteen-second capability this service declined to wire inline.
#:
#: **A media id is immutable, which is what makes caching it correct rather than
#: merely fast.** `MediaAsset` rows are write-once and the blob behind one is
#: never rewritten, so the same id is the same bytes forever and its vector
#: cannot go stale. The artefact digest is in the key because a re-export with
#: different pooling produces a vector of the same width and a different meaning
#: β€” the case `IdentityIndex` pins its backbone id to catch β€” so vectors from two
#: artefacts must never collide here.
#:
#: **The farm id is in the key, and it was not for one commit.** The argument for
#: leaving it out was that nothing can be read from the cache without already
#: holding the media id. A watchdog showed that is not quite the property that
#: matters: `AzureBlobMediaStore` scopes an unpathed lookup to `farm/{farm_id}/`,
#: so *without* a cache a request naming another farm's media id fails to resolve
#: β€” and *with* one it would hit and score. What leaked was a similarity against
#: a foreign photograph rather than the photograph or the name, and it needed the
#: service token plus a known foreign UUID, so it was narrow. It was also free to
#: close, and a cache must not be the reason a farm boundary that the media store
#: enforces stops being enforced.
_VECTOR_CACHE: OrderedDict[tuple[str, str, str], np.ndarray] = OrderedDict()

#: Entries kept. 768 float32 values is 3 KB, so this is about 12 MB β€” small
#: against the container's 4 GiB, and enough for a few hundred animals at five
#: shots each. Least-recently-used is evicted, so the farms being worked today
#: stay warm.
_CACHE_LIMIT = 4096

#: `submit()` runs in FastAPI's threadpool, so two captures can be scored at
#: once and an `OrderedDict` is not safe under that on its own.
_CACHE_LOCK = threading.Lock()


def cache_clear() -> None:
    """Empty the vector cache. For tests, and for a deployment that swaps an
    artefact without a restart."""
    with _CACHE_LOCK:
        _VECTOR_CACHE.clear()


def _cached_vector(key: tuple[str, str, str]) -> np.ndarray | None:
    with _CACHE_LOCK:
        vector = _VECTOR_CACHE.get(key)
        if vector is not None:
            _VECTOR_CACHE.move_to_end(key)
        return vector


def _remember(key: tuple[str, str, str], vector: np.ndarray) -> None:
    with _CACHE_LOCK:
        _VECTOR_CACHE[key] = vector
        _VECTOR_CACHE.move_to_end(key)
        while len(_VECTOR_CACHE) > _CACHE_LIMIT:
            _VECTOR_CACHE.popitem(last=False)


#: Muzzle photographs per animal in the protocol the accuracy was measured under.
#:
#: Run `8db9e0bd1b30` enrolled each animal from five, all under the `muzzle` view
#: name, and `IdentityIndex.candidates` scores an animal by its **best** view β€”
#: so the five are five chances to match rather than five samples averaged
#: together. An animal enrolled from one photograph is being asked a harder
#: question than the benchmark asked, and measured that way on twelve animals,
#: **0 of 23 held-out queries cleared the 0.98 cutoff**.
#:
#: Used only to report a thin enrolment, never to refuse one.
MEASURED_ENROLMENT_SHOTS = 5


class IdentityRunner:
    """Runs `cattle_identity` against one capture and one farm's register."""

    #: The view a single capture is compared against. Β§6.4 leads with the muzzle
    #: and the re-identification measurement is entirely muzzle-to-muzzle, so
    #: comparing a muzzle print against a side-body vector would contribute
    #: nothing but a chance of a spurious high score. Restricting the pool is
    #: what keeps the runtime behaviour inside the measured one.
    #:
    #: **Nothing checks that the capture actually is a muzzle, and a watchdog was
    #: right to call that a gap.** The registry declares `muzzle_not_visible` in
    #: `reject_if`, but `app/quality.py` implements resolution, illumination and
    #: motion blur and nothing that looks for an anatomical part β€” so the
    #: condition is declared and never evaluated. A caller that sends the five
    #: enrolment views in the order the registry lists them sends `front_face`
    #: first, and it would be scored against a gallery of muzzles.
    #:
    #: What that costs is bounded in the safe direction: a front-face photograph
    #: is *less* similar to every enrolled muzzle, so it drives the top score down
    #: and towards `no_confident_match`. It degrades to a refusal rather than to a
    #: wrong name. But it is a refusal for a reason nobody is told, and the fix is
    #: a real one β€” either the capture flow states which view it took, or
    #: something evaluates `muzzle_not_visible`. Neither exists today.
    query_view = PRIMARY_VIEW

    def run(
        self,
        *,
        request: InferenceRequest,
        capability: Capability,
        artefact: ModelArtefact,
        store: MediaStore,
        request_id: UUID | None = None,
    ) -> InferenceResult:
        request_id = request_id or uuid4()
        warnings: list[str] = [STANDING_WARNING]

        if len(request.media_ids) > capability.frames_required:
            warnings.append(
                f"{len(request.media_ids)} frames were supplied; this capability "
                f"reads {capability.frames_required}."
            )

        image = store.open_image(self._ref(request, request.media_ids[0]))
        verdict = assess(image)
        checks = list(verdict.checks)

        if verdict.blocked:
            failure = verdict.first_failure
            warnings.append(
                failure.detail if failure and failure.detail
                else "The capture was not usable."
            )
            return self._result(
                request=request, capability=capability, artefact=artefact,
                request_id=request_id, observations=[], checks=checks,
                warnings=warnings, recapture=True,
            )

        adapter = OnnxEmbeddingAdapter(
            artefact, DINOV3_SPEC,
            input_size=artefact.input_size or 224,
            mean=artefact.image_mean or (0.485, 0.456, 0.406),
            std=artefact.image_std or (0.229, 0.224, 0.225),
            dimensions=artefact.embedding_dimensions or 768,
        ).load()

        index = IdentityIndex(
            farm_id=str(request.farm_id),
            backbone_id=DINOV3_SPEC.adapter_id,
            dimensions=adapter.dimensions,
            artefact_sha256=artefact.sha256,
        )
        names: dict[str, str] = {}
        enrolled_views, skipped = self._enrol(
            index, adapter, artefact, request, store, names,
        )
        warnings.extend(skipped)

        query = self._embed(adapter, artefact, image)
        result = index.match(
            query,
            policy=MEASURED_POLICY,
            top_k=TOP_K,
            # Compared against muzzles only. See `query_view`.
            restrict_to_views=(self.query_view,),
            names=names,
        )

        checks.append(self._enrolment_check(index, enrolled_views))

        observations: list[Observation] = [
            # Emitted on every path, refusals included, for the reason
            # `counting.py` emits its grid: a threshold that has to be re-derived
            # later is re-derived from stored results or not at all.
            Observation(
                type="enrolled_animals",
                value=float(result.enrolled_animals),
                unit="animals",
                confidence=None,
            ),
            Observation(
                type="open_set_verified",
                value=1.0 if result.open_set_verified else 0.0,
                confidence=None,
            ),
        ]

        top = result.top
        if top is not None:
            observations.append(Observation(
                # **Uncalibrated, and named so.** Nothing mapped cosine
                # similarity onto a probability that the animal is Kofi, so this
                # is a diagnostic and the release declares no sentence for it β€”
                # exactly as `counting_grid` carries none. Β§37's whole complaint
                # is an uncalibrated model score reaching a person as a promise.
                type="top_candidate_similarity",
                value=round(top.similarity, 4),
                confidence=None,
            ))

        accepted = result.claim == "identity_candidate"
        if not accepted:
            observations.append(Observation(
                type="no_confident_match", value=None, confidence=None,
            ))

        # **The ranked list is emitted on both paths, under different names.**
        # `IdentityResult` returns candidates whether or not the policy accepted
        # the top one, and dropping them on the refusal path would throw away the
        # thing Β§6.4 is shaped around: `evidence_correction.selected_
        # interpretation` records *"rank 2 was right"*, and its own docstring
        # calls that the highest-value training signal in the table. At the
        # measured operating point three quarters of correct matches are refused,
        # so the refusal path is where most of that signal lives β€” emitting
        # nothing there would collect it almost nowhere.
        #
        # The type differs because the claim differs. `identity_candidate` is
        # Β§6.4's *"This looks like Kofi"*; `closest_candidate` is *"not a
        # confident match, and these are the nearest on your register"*. One
        # observation type carrying both would leave the device deciding which
        # sentence to show from a field that does not say, and the release
        # declares a separate sentence for each.
        #
        # ## Exactly one settled name, ever
        #
        # An accepted run used to publish `identity_candidate` for **all three**
        # ranked candidates, so `deploy/semantics` rendered *"This looks like
        # Cow 0100"*, *"This looks like Cow 0200"* and *"This looks like Cow
        # 0300"* β€” three settled names for one animal, on one `captured_at`,
        # which no ordering on the device can pick between. The policy accepted
        # **the top candidate**; it said nothing whatever about ranks 2 and 3.
        #
        # So rank 1 is the proposal and the rest are what Β§6.4's screen calls
        # *"Also considered"* β€” the same list, under the type that does not
        # assert. Picking one of them is `choose_another_animal`, which is a
        # correction and the signal the ledger is built to keep.
        for position, candidate in enumerate(result.candidates):
            settled = accepted and position == 0
            observations.append(Observation(
                type="identity_candidate" if settled else "closest_candidate",
                # The animal's own name. `identity_candidate` is one of three
                # claims in the whole registry that carries free text, and
                # `app/capabilities.py` says why: a registry cannot enumerate a
                # farm's animals. The value comes from the farm's own register
                # via the request, never from this service.
                value=candidate.display_name,
                # **`confidence` stays None on both paths.**
                # `Candidate.confidence` is documented as never set by the
                # matcher, and promoting a similarity into it here would defeat
                # that in one line.
                confidence=None,
            ))

        if result.candidates:
            warnings.append(UNENROLLED_WARNING)

        warnings.extend(result.warnings)

        if index.unverified_queries:
            # Should be zero on this path: every vector is built as an
            # `Embedding` below. Reported rather than asserted, because the count
            # existing at all is the gap between what the index checks and what
            # it would like to.
            warnings.append(
                f"{index.unverified_queries} vectors were compared without "
                f"provenance. Every vector this service builds carries it, so "
                f"this is a defect rather than a capture problem."
            )

        return self._result(
            request=request, capability=capability, artefact=artefact,
            request_id=request_id, observations=observations, checks=checks,
            warnings=warnings,
            # A refusal here is not a bad photograph. The animal may simply not
            # be enrolled, and asking for a recapture would send a worker back to
            # a pen to re-photograph a cow the system has never seen.
            recapture=verdict.degraded,
        )

    def _ref(self, request: InferenceRequest, media_id: UUID,
             object_path: str | None = None) -> MediaRef:
        return MediaRef(
            media_id=media_id,
            farm_id=request.farm_id,
            captured_at=request.captured_at,
            object_path=object_path or request.path_for(media_id),
        )

    def _embed(
        self, adapter: OnnxEmbeddingAdapter, artefact: ModelArtefact,
        image: Image.Image,
    ) -> Embedding:
        """A vector that can prove where it came from.

        Always an `Embedding`, never a bare array. `IdentityIndex._accept` can
        only check the *width* of an array, and a watchdog scored a foreign
        vector against a `dinov3-vits16` index at similarity 1.0 through exactly
        that gap.
        """
        return Embedding(
            vector=adapter.embed(image),
            backbone_id=DINOV3_SPEC.adapter_id,
            artefact_sha256=artefact.sha256,
        )

    def _enrol(
        self,
        index: IdentityIndex,
        adapter: OnnxEmbeddingAdapter,
        artefact: ModelArtefact,
        request: InferenceRequest,
        store: MediaStore,
        names: dict[str, str],
    ) -> tuple[int, list[str]]:
        """Build this farm's gallery from the request. Returns views enrolled.

        A gallery entry that cannot be read is **skipped with a warning rather
        than failing the job**: one unreadable enrolment photograph out of two
        hundred should not stop a farmer identifying an animal, and the animals
        that did load are still a gallery. The count that reaches the result is
        the count that actually enrolled, so a farm cannot be told it was
        compared against more animals than it was.
        """
        enrolled_views = 0
        skipped: list[str] = []

        for entry in request.enrolled:
            usable = self._views_for(entry, skipped)
            if not usable:
                continue
            who = entry.display_name or entry.animal_id
            stored = 0

            for view, media_ids in usable.items():
                for position, media_id in enumerate(media_ids):
                    key = (str(request.farm_id), str(media_id), artefact.sha256)
                    cached = _cached_vector(key)
                    if cached is not None:
                        vector = Embedding(
                            vector=cached,
                            backbone_id=DINOV3_SPEC.adapter_id,
                            artefact_sha256=artefact.sha256,
                        )
                    else:
                        try:
                            image = store.open_image(self._ref(
                                request, media_id,
                                entry.path_for_view(view, position),
                            ))
                        except Exception as exc:  # MediaError, or any store error
                            skipped.append(
                                f"An enrolled {view} photograph of {who} could "
                                f"not be read ({exc}), so it was left out of "
                                f"the comparison."
                            )
                            continue
                        vector = self._embed(adapter, artefact, image)
                        _remember(key, vector.vector)
                    try:
                        # **One call per photograph, under the one view name.**
                        # `enrol` takes a mapping of view to vector, so a single
                        # call can only hold one shot per view β€” and the measured
                        # protocol is five muzzles per animal, scored by maximum.
                        # Calling it per photograph is how those five reach the
                        # index, and it is what run 8db9e0bd1b30 did.
                        index.enrol(
                            entry.animal_id, {view: vector},
                            media_ids={view: str(media_id)},
                        )
                    except (IndexMismatch, ValueError) as exc:
                        skipped.append(
                            f"A {view} photograph of {who} was left out of the "
                            f"comparison: {exc}"
                        )
                        continue
                    stored += 1

            if not stored:
                continue
            names[entry.animal_id] = who
            enrolled_views += stored

        return enrolled_views, skipped

    def _views_for(
        self, entry: EnrolledAnimal, skipped: list[str]
    ) -> dict[str, list[UUID]]:
        """The views of one animal this runner will compare against.

        Unknown view names are dropped here with a warning rather than left for
        `enrol` to raise on, because one typo in one animal's record should cost
        that animal's view and not the whole request.
        """
        usable: dict[str, list[UUID]] = {}
        for view, media_ids in entry.views.items():
            if view not in ENROLMENT_VIEWS:
                skipped.append(
                    f"{entry.display_name or entry.animal_id} has a view called "
                    f"{view!r}, which is not one of the five Β§6.4 enrols "
                    f"({', '.join(ENROLMENT_VIEWS)}). It was ignored."
                )
                continue
            if media_ids:
                usable[view] = list(media_ids)
        return usable

    def _enrolment_check(
        self, index: IdentityIndex, enrolled_views: int
    ) -> QualityCheck:
        """Whether this farm had anything to compare the capture against.

        A quality check rather than a warning, because the answer is about the
        *farm's register* rather than about the photograph, and a capture flow
        that can distinguish the two can say *"register this animal"* instead of
        *"take it again"*.
        """
        muzzles = sum(1 for v in index.views if v.view == self.query_view)
        animals = len(index.animal_ids)
        if muzzles:
            detail = (
                f"{animals} animals enrolled, {enrolled_views} views, "
                f"{muzzles} of them muzzles."
            )
            thin = [
                a for a in index.animal_ids
                if sum(1 for v in index.views
                       if v.animal_id == a and v.view == self.query_view)
                < MEASURED_ENROLMENT_SHOTS
            ]
            if thin:
                # **Reported, never a refusal.** A thin enrolment still matches,
                # and one good muzzle photograph is worth more than a refused
                # enrolment β€” `IdentityIndex.enrol` takes that position and this
                # agrees with it. What it is not is the protocol the accuracy was
                # measured under, and a farm whose animals never quite match
                # deserves to know the reason is its register rather than its
                # camera.
                detail += (
                    f" {len(thin)} of them carry fewer than "
                    f"{MEASURED_ENROLMENT_SHOTS} muzzle photographs, which is "
                    f"the enrolment the accuracy was measured on."
                )
            return QualityCheck(check="gallery", passed=True, detail=detail)
        return QualityCheck(
            check="gallery",
            passed=False,
            detail=(
                f"No enrolled {self.query_view} to compare against. "
                f"{len(index.animal_ids)} animals were sent and none carries "
                f"the view this capability matches on."
            ),
        )

    def _result(
        self, *, request, capability, artefact, request_id, observations,
        checks, warnings, recapture,
    ) -> InferenceResult:
        forbidden = [o.type for o in observations if o.type in FORBIDDEN_CLAIMS]
        if forbidden:
            raise ValueError(
                f"{capability.key} tried to emit a forbidden claim: {forbidden}"
            )
        banned = set(capability.acquisition.forbidden_claims)
        offending = [o.type for o in observations if o.type in banned]
        if offending:
            # `identity_without_confirmation` is the one thing Β§6.4 forbids, and
            # it is checked here as well as in the registry because this runner
            # is the only thing that could emit it.
            raise ValueError(
                f"{capability.key} tried to emit {offending}, which its own "
                f"registry entry forbids by name."
            )

        return InferenceResult(
            request_id=request_id,
            capability_key=capability.key,
            model_id=artefact.model_id,
            model_version=artefact.version,
            inference_location=InferenceLocation.REMOTE,
            subject_type=request.subject_type,
            subject_id=request.subject_id,
            observations=observations,
            # **No interpretation, and none is possible.** A name is a fact about
            # the register, not a judgement about the animal, and there is
            # nothing for a vet to review. `observation_confidence` is likewise
            # None: the three-level label would be read as a confidence in the
            # name, and nothing calibrated one.
            interpretations=[],
            observation_confidence=None,
            interpretation_confidence=None,
            quality_checks=checks,
            warnings=warnings,
            recommended_recapture=recapture,
        )


#: Capabilities with an implemented adapter, in the shape `app/counting.py`
#: publishes. `app/main.py` merges the two: a capability with a validated
#: artefact and no entry in either returns 501 rather than a plausible result.
RUNNERS: dict[str, IdentityRunner] = {
    "cattle_identity": IdentityRunner(),
}