Record two GPU cycles: the gate works, coverage is the open question
No worker code changed. This is the evidence from the 2026-08-12 16:39 and 17:38 runs, and where each finding now lives. The fourth session's four identity changes all work on real panels. Panel 7's two wrong bindings are gone. The lead going unassigned there is correct and was measured, not assumed: face_detect finds one face on the whole panel at conf 0.599, nothing else above 0.056 even at a 0.04 threshold, and the crop shows him drawn from behind. Two decisions, both closed: a roster name is a guess so it never reaches detection, and merged_into is exactly one hop deep. Two caveats, both open: detection can order a bbox backwards (1 in 117), and identity coverage has fallen on every run since the gate landed (70 -> 61 -> 50). Coverage is the thing to settle next, and not by reading the number. identity_labels already holds 145 rows of ground truth. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+127
@@ -459,3 +459,130 @@ block, verified on the live database (`merged_into` present, 0 rows merged). Com
|
||||
Deliberately not built: the unmerge path and the split. No wrong merge has been observed since the crops
|
||||
were fixed, so the consumer of these records waits for one. The forward case is partly covered by 2b, since
|
||||
a resolver NONE now mints instead of folding a stranger into the nearest match.
|
||||
|
||||
## 2026-08-12, fifth session — the GPU cycle all four changes were waiting for
|
||||
|
||||
Asked: "go ahead" on the rerun.
|
||||
|
||||
First correction: the vision/identity/reconcile timestamps in `/job/status` are UTC and the git log is
|
||||
local (UTC+4), so the run that looked like a rerun was the pre-change baseline. `_mark_has_face` landed at
|
||||
19:07 local, the run finished 13:17 local. The handoff was right that nothing had run.
|
||||
|
||||
Reset the registry for `ef105a86` (8 characters, 77 assignments, 101 sources, 142 S3 objects), cleared
|
||||
`vision` and everything downstream (116 vision results), restarted the identity worker for its in-process
|
||||
known-character cache, then ran vision, identity and reconcile. 6 minutes wall: vision 3m48s, identity
|
||||
1m25s, reconcile 48s. All three `completed`, 116/116/16.
|
||||
|
||||
### What the four changes did
|
||||
|
||||
| change | verdict |
|
||||
| --- | --- |
|
||||
| `has_face` stamp | works, present on all 110 detections, 67 true / 43 gated (39%) |
|
||||
| extras gate | works, panel 7's two wrong bindings are gone |
|
||||
| NONE mints | works, 16 characters minted where 8 existed |
|
||||
| non-destructive merge | works, 7 rows carry `merged_into`, 9 assignments stamped `merged_from:` |
|
||||
|
||||
Coverage went 70% -> 61% (77 -> 67 assignments over the same 110 detections). That is the gate's price and
|
||||
it is mostly correct, see below.
|
||||
|
||||
### Panel 7, the worked example, before and after
|
||||
|
||||
```
|
||||
person_1 Seonho 1.00 -> -- none -- has_face=False
|
||||
person_2 character_f7a4fd 0.00 -> character_519d2b 0.00 has_face=True
|
||||
person_4 character_d72710 0.94 -> -- none -- WRONG binding removed
|
||||
person_5 Seonho 1.00 -> -- none -- WRONG binding removed
|
||||
```
|
||||
|
||||
The two wrong bindings the gate existed to kill are dead. It also took `person_1`, and that is correct:
|
||||
ran `face_detect.detect_faces` on p006.png directly and it finds exactly one face on the whole panel, conf
|
||||
0.599, inside `person_2`. Swept the threshold to 0.04 and nothing else appears above 0.056. Looked at the
|
||||
crop: `person_1` is the lead drawn from behind at his desk, back of the head and headphones, no face in
|
||||
frame. The detector is right and the gate is right. The cost is that a back-turned character cannot be
|
||||
identified from that panel at all.
|
||||
|
||||
Also worth recording: `person_1`'s new box frames the lead correctly. The old note that it framed an empty
|
||||
window mullion was written against the pre-fix boxes.
|
||||
|
||||
### The new defect: a roster hint named the wrong man
|
||||
|
||||
The registry holds three real people and one wrong name.
|
||||
|
||||
- `character_92129ac7` "Lim Seonho", 22 assignments. Verified against the art: the p010 introduction panel
|
||||
captioned `LIM SEONHO (29)`, yellow plaid shirt and headphones, which is the roster's description for
|
||||
"Seonho". This is the lead and the name is right.
|
||||
- `character_556aef60`, 25 assignments, unnamed. Verified: the woman with short black hair and pearls, the
|
||||
roster's second character, whom the roster itself calls "Unknown".
|
||||
- `character_dbadfff7` "Seonho", 15 assignments. Verified: a different man, glasses, dark clothing, in
|
||||
profile. He carries the lead's roster name.
|
||||
|
||||
p020 assigns `Seonho` to `person_1` and `Lim Seonho` to `person_3`, so the pipeline holds them as two
|
||||
people in one panel. inference: the roster hint injected at `service.py:882` puts a name in front of
|
||||
detection, and detection attached it to the wrong face before the caption panel could mint the real one.
|
||||
This is not the alias-merge case and merging the two rows would be wrong. It needs name binding to require
|
||||
evidence, the way `name_claims` already does for captions and address.
|
||||
|
||||
### Two smaller things the run exposed
|
||||
|
||||
- **Merge chains.** `character_e1ab7776 -> character_521c301f -> character_556aef60`. `merged_into` points
|
||||
at a row that is itself merged, so a single-hop resolve lands on a retired character. Roster readers are
|
||||
fine because they filter `merged_into IS NULL`. Anything that follows one hop is not.
|
||||
- **Orphan `name_claims`.** All 5 rows point at `character_afa7623b`, which the reset deleted. The reset
|
||||
clears characters and assignments but not claims.
|
||||
|
||||
Not run: dialogue and everything downstream. The job sits at `dialogue waiting`.
|
||||
|
||||
### Same session — the three fixes the run's evidence asked for
|
||||
|
||||
All three are orchestrator-side, so no worker changed and `check_stale.sh` is not in play.
|
||||
|
||||
- **Roster hints no longer seed detection.** Deleted the two lines at `service.py:882` that appended
|
||||
`_roster_char_hints` to `known_characters`. `build_detect_prompt` drops any hint without a name, so a
|
||||
nameless hint would have contributed nothing anyway. Names now reach detection only from registry rows,
|
||||
which are embedding-backed and were themselves named from an in-panel caption or address. The roster
|
||||
still feeds `roster_cast` in `run_stage_dialogue`, where names are matched against speech.
|
||||
- **`merge_characters` resolves the keeper to its chain root** before merging, so `merged_into` stays one
|
||||
hop deep. Bounded 64-step walk, the cap only exists so a cycle cannot hang reconcile.
|
||||
- **`reset_registry` deletes `name_claims`** for the manga's panels and reports the count.
|
||||
|
||||
Checks: 117 passed on homesrv, up from 116. The new chain test was confirmed to fail with the walk
|
||||
disabled, then the walk was restored and the suite re-run. Deployed by rebuilding the image; verified
|
||||
inside the running container by parsing the deployed source, not by grepping it, because the first check
|
||||
matched the word `_roster_char_hints` inside the comment that explains its removal.
|
||||
|
||||
Also copied `audit_registry.py` into the repo before the rebuild. It had only ever been `docker cp`'d, so
|
||||
every rebuild dropped it. `Dockerfile` has `COPY . .`, so it is baked now and the trap is gone.
|
||||
|
||||
Open risk on the next run: without the roster hint the lead is unnamed until the p010 caption, so panels 1
|
||||
to 9 may mint him as an anonymous character that reconcile then has to merge. Watch whether "Lim Seonho"
|
||||
comes back as one row or two.
|
||||
|
||||
### Same session — run B, 17:38-17:44
|
||||
|
||||
Reset (16 characters, 67 assignments, 67 sources, **5 name_claims**, 156 S3 objects), cleared `vision`,
|
||||
restarted the identity worker, ran the three stages. The `name_claims: 5` line is the orphan fix confirmed
|
||||
on live data.
|
||||
|
||||
117 detections, 59 assignments, 50% coverage, 20 characters, 72 with `has_face` (45 gated).
|
||||
|
||||
The registry split is fixed. "Lim Seonho" is one row with 25 assignments, "Seonho" fell from 15 to 1. So
|
||||
dropping the roster hint did not split the lead across the panels before his caption, which was the risk
|
||||
recorded before the run.
|
||||
|
||||
Two findings, both now filed:
|
||||
|
||||
- detection still names from in-panel text and can attach the name to the wrong body (p011, p026), which
|
||||
is `decisions/identity-bbox.md#roster-does-not-name`'s "not covered" paragraph
|
||||
- one degenerate bbox, `caveats/audit-open.md#degenerate-bbox`
|
||||
|
||||
And one fix of my own that was incomplete: the merge chain came back as
|
||||
`477c1894 -> a92d9df4 -> 4fb94c15` with the keeper walk deployed. The walk only sees the pair being merged
|
||||
now. Retiring a row that is already somebody's keeper needed the second half, a repoint of the loser's
|
||||
dependents. Both halves and both tests are in `decisions/identity-bbox.md#merge-chains-flatten`.
|
||||
|
||||
Checks: 118 passed, was 116. Each new merge test was run with its fix disabled and confirmed to fail
|
||||
first. Deployed by rebuild and verified by parsing the container's source.
|
||||
|
||||
Coverage is now 70 -> 61 -> 50 across three runs and is the open question, recorded as
|
||||
`caveats/audit-open.md#coverage-trend`. `identity_labels` already holds 145 rows of ground truth, so the
|
||||
next move is to score with `eval_identity.py` rather than to keep reading the coverage number.
|
||||
|
||||
Reference in New Issue
Block a user