Record that 2d's worked examples no longer exist

The registry reset deleted Lim Seonho and character_afa7623b, so there is nothing
to merge or split until the rerun mints a new set. What was done instead is the
safety net for that rerun, since reconcile runs inside it: a merge retires the
losing row rather than deleting it, and records which assignments moved.

Half-closes caveats/audit-open.md#destructive-reconcile. The unmerge path and the
split stay unwritten on purpose, with the revisit trigger named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 19:27:13 +04:00
parent ca4661763c
commit d63a337f15
3 changed files with 99 additions and 9 deletions
+63
View File
@@ -396,3 +396,66 @@ 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.
## 2026-08-12, fourth session, continued — identity 2c, extras versus cast
Asked: "fix extra-vs-cast?"
Traced the three places that read `vision["characters"]` raw before changing anything. That corrected an
inference made earlier in the session. `build_scene` already drops an unassigned detection from
`characters` and `present` (`worker_scene.py:63`), so extras never reached the cast list at all.
The leak was their ACTIONS. `actions` was built from every detection, and that list is what the script
prompt renders and what the correctness verifier uses as evidence. So a background extra's "standing at the
window" arrived as a fact about the panel with no character attached, and the verifier confirmed it,
because the action really was in the blob. That is a second mechanism behind the invented-narration
complaints in item 3, independent of the model inventing anything.
Gated two consumers on `has_face is False`, matching the enrollment gate's semantics exactly:
`worker_scene`'s `actions`/`action`, and `service._beat`, which picks the director's "who" from the first
three detections and falls back to an action when a detection has no name.
Left `service._present_characters` ungated on purpose. It builds the dialogue stage's candidate speakers
and the set-of-mark boxes. An extra picked as speaker already resolves to unknown, not to a wrong name, so
the failure is contained. And the gate's cost lands hardest there, since a character drawn from behind has
no face box and gating would delete a real speaker from the only list that can attribute their line.
`decisions/identity-bbox.md#extras-gate-consumers`.
Checks: `worker_scene self-check ok` with three cases (cast action survives, faceless dropped, missing key
survives), 115 passed on homesrv. Deployed: image rebuilt, `_beat` verified inside the container, scene
worker restarted, `./check_stale.sh` clean. Committed `ca46617` and `8b27aec`.
Still not run on a GPU. Three changes now ride the same cycle: `has_face`, the NONE mint, and this.
## 2026-08-12, fourth session, continued — identity 2d, merge and split
Asked: "2d? merge and split?"
Checked the registry before planning anything, and 2d as written is stale. The registry reset earlier today
deleted both worked examples. There is no `Lim Seonho` to merge into, `character_afa7623b` does not exist,
and the current registry is 8 rows with one named character (`Seonho`). Nothing to merge or split until the
rerun mints a new set.
So the useful work was the safety net for that rerun, since `reconcile` runs inside it. The caveat's cost
line was the reason: one bad merge was unrecoverable without rebaselining the whole manga, and the cycle
about to run includes a merge pass over embeddings nobody has seen yet.
`merge_characters` no longer deletes the loser. It sets `merged_into = keeper`, so the row keeps its
embedding, description and gender, and it stamps every repointed assignment with
`method = merged_from:<loser_id>` in `identity_assignment_sources`. That reuses a free-form column on a
table that already existed rather than adding a merge log. `source` is left alone, so a reviewer's `manual`
assignment keeps its veto in `assign_identity` after being repointed.
Roster readers filter `merged_into IS NULL`, including the name-dedup in `create_character`, which would
otherwise fold new crops back into a character reconcile had retired. Lookup by id does not filter, because
an assignment or a narration reference may still point at a merged id.
Two existing assertions asserted the old destructive behaviour (`test_db.py:235`, `test_merge_refs.py:37`)
and were rewritten, not deleted: the invariant changed on purpose.
Checks: 116 passed on homesrv, up from 115. Additive `ALTER TABLE` through the existing `init_db` migration
block, verified on the live database (`merged_into` present, 0 rows merged). Committed `00096cc`.
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.