Ship each crop's embedding so a resolver NONE can mint
Two identity fixes, neither yet run on a GPU. has_face, the extras gate: fixing the bbox coordinate space made extras worse, because crops finally landed on their subjects and a background extra bound to the lead at 1.00. /vision stamps has_face per character using face_detect and _pair_faces_to_present, so containment and its margin match the speaker path. Identity skips has_face is False before it crops or embeds. Fails open on a missing or raising detector, and gates on `is False` so an older vision blob behaves as before. The NONE mint: /identity/resolve now writes each crop's embedding to the crop's key with a .npy suffix and returns emb_uri. The orchestrator cannot compute an embedding of its own (siglip and gemma cannot both be resident), which is why it was clearing a deliberate NONE instead of minting an anonymous character. Also: audit_registry.py, and the tmux respawn-window trap, which leaves a bare shell rather than re-running the window command. decisions/identity-bbox.md#face-gates-enrollment decisions/identity-bbox.md#none-mints-an-anonymous-character Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+90
@@ -306,3 +306,93 @@ subject, including `person_5`, who is Seonho in the foreground with headphones a
|
||||
|
||||
Not done: `vision` and `identity` have not re-run, so every box, embedding and `ref_image_uris` in the
|
||||
registry is still from the wrong space. That rerun is GPU work and was not started.
|
||||
|
||||
## 2026-08-12, third session — the rerun, and the run that did not count
|
||||
|
||||
Asked: move on from the chapter rebuild. Scope chosen mid-session: reset the registry, rerun vision,
|
||||
identity and reconcile only, and inspect before spending the ~80 minutes downstream.
|
||||
|
||||
**The registry had to be wiped first, and nothing could do it.** `clear_stage_data` maps `identity` to
|
||||
`identity_assignments` only and spares `characters` on purpose (`db.py:790`), because the registry is
|
||||
cross-run. So a rerun would have matched fresh crops against 19 stale grid-space embeddings and kept the
|
||||
duplicate `Lim Seonho` / `Seonho` rows that raise `ambiguous-speaker`. Added `db.reset_registry` and
|
||||
`POST /characters/reset` (`confirm=true` required), which also clears `identity_assignment_sources` —
|
||||
untouched by any stage clear, and a leftover `manual` row there makes `assign_identity` refuse the next
|
||||
model assignment (`db.py:663`). Covered by `test_db.py:TestResetRegistry`; 115 orchestrator tests pass.
|
||||
The orchestrator image bakes its source, so it needed `docker compose up -d --build orchestrator`.
|
||||
|
||||
**The first full cycle was void: the worker was serving pre-fix code.** vision + identity + reconcile
|
||||
completed 116/116/20 and reproduced the defect exactly — 46 of 110 boxes past the 900px panel width,
|
||||
coordinates clamping at 1000. The vision worker started 12:00:09, `worker_vision.py` changed 12:11:35,
|
||||
and the commit carrying `_bbox_to_pixels` landed 12:16:22. Python had already bound the old module.
|
||||
Nothing in the stage output said so (`decisions/identity-bbox.md#stale-worker-invalidates`). Added
|
||||
`check_stale.sh`, which compares every worker's process start against its module mtime and exits
|
||||
non-zero; mutation-tested by touching `worker_tts.py`.
|
||||
|
||||
**The rerun against restarted workers.** 8 minutes for all three stages.
|
||||
|
||||
```
|
||||
before stale run after
|
||||
x2 past panel width 47/113 46/110 0/110
|
||||
coords exactly on 1000 21 27 1
|
||||
max y2 1000 1000 2307
|
||||
characters 19 8 8
|
||||
coverage 26/113 = 23% 77/110 = 70% 77/110 = 70%
|
||||
top character's share 96% Haeseon 47% 47%
|
||||
```
|
||||
|
||||
`Choi Haeseon`, which had absorbed 25 of 26 assignments, no longer exists in the registry. On panel 7
|
||||
Seonho is bound for the first time, and the unnamed colleague took an anonymous id instead of being
|
||||
called `Choi Haeseon` at 0.9.
|
||||
|
||||
**Fixing the boxes made the extras problem worse.** With crops finally landing on their subjects, a
|
||||
background extra bound to `Seonho` at confidence 1.00, putting an extra into the lead's reference set.
|
||||
`/vision` now stamps `has_face` per character via `face_detect` + `_pair_faces_to_present`, and identity
|
||||
skips `has_face is False` before it crops or embeds
|
||||
(`decisions/identity-bbox.md#face-gates-enrollment`). Fails open on a missing or raising detector.
|
||||
Self-checked in both workers. **Not yet proven on a GPU run** — that is the next command.
|
||||
|
||||
Deleted along the way and not recoverable: the rebuilt `chapter.mp4` and all 49 clips, by the cascade
|
||||
from `/stage/clear vision`. The user chose not to keep a copy.
|
||||
|
||||
## 2026-08-12, fourth session — identity 2b, the resolver NONE branch
|
||||
|
||||
Asked: "how much will `has_face` help with character and identity problems?", then "fix 2b first".
|
||||
|
||||
Answered the first honestly: `has_face` reaches 2 of 6 detections on panel 7 and nothing else. It does not
|
||||
touch naming or merging, and it cannot touch the chibi at 1:35, because an anime face detector detects a
|
||||
chibi face. Also flagged its real cost: the model is face-only by design, so back-turned cast lose
|
||||
enrollment along with the extras, and coverage is where that shows up first.
|
||||
|
||||
Measured before writing anything, read-only, no GPU:
|
||||
|
||||
```
|
||||
registry: 8 characters, 1 named -> ['Seonho']
|
||||
detections: 110 assignments: 77 = 70% coverage
|
||||
spread: Seonho 36, character_565c88 24, character_759e23 9, character_f7a4fd 3,
|
||||
character_25f682 3, character_d72710 1, character_823aba 1
|
||||
```
|
||||
|
||||
That killed the assumed cause. Anonymous ids already recur, so the identity worker's own
|
||||
pending-promote path gives stable anonymous identities. The defect was elsewhere.
|
||||
|
||||
`/vision/resolve` can answer "none of these" and always could: `worker_vision.py:1071` returns
|
||||
`state="new"` for `choice: 0` and `state="unresolved"` for an out-of-range index. `service.py` read only
|
||||
`character_id` and unassigned every crop of the tracklet for either. The stale `ponytail:` comment above
|
||||
that block named the real blocker and was right: minting needs an `embedding_uri` the orchestrator cannot
|
||||
compute, since siglip and gemma cannot both be resident.
|
||||
|
||||
Fixed by carrying the embedding, not by adding a GPU pass. `/identity/resolve` writes each crop's
|
||||
embedding to the crop's key with a `.npy` suffix and returns `emb_uri`. `tracklets.resolve_outcome` holds
|
||||
the three-way decision as a pure function. `service.py` mints via the existing `create_character` and
|
||||
falls into the existing assign loop. `decisions/identity-bbox.md#none-mints-an-anonymous-character`.
|
||||
|
||||
Checks: `worker_identity self-check ok`, `tracklets self-check ok`, 115 passed on homesrv.
|
||||
Deployed: image rebuilt, `resolve_outcome` verified inside the running container, `audit_registry.py`
|
||||
re-copied after the recreate. Vision and identity restarted, `./check_stale.sh` exits 0.
|
||||
|
||||
New trap: `tmux respawn-window -k` does not re-run the window command. It leaves a bare shell and the
|
||||
worker down. Both workers were dead for two minutes before `/health` caught it.
|
||||
|
||||
Not run: the GPU cycle. `has_face` and the NONE mint are both unproven on real panels and now land in the
|
||||
same run.
|
||||
|
||||
Reference in New Issue
Block a user