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:
+63
@@ -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.
|
||||
|
||||
@@ -45,10 +45,27 @@ One number sets the agenda:
|
||||
deliberate NONE and a hallucinated index both unassigned every crop of the tracklet. A NONE now
|
||||
mints an anonymous character from the crop, using the embedding `/identity/resolve` ships beside
|
||||
it as `emb_uri`. Deployed: image rebuilt, `resolve_outcome` verified inside the container.
|
||||
c. **Separate extra from cast.** Four of the six detections on panel 7 are background extras or
|
||||
nothing at all, and all six reach identity as equal candidates.
|
||||
d. Only then merge `Seonho` into `Lim Seonho` and split `character_afa7623b`, which still needs the
|
||||
reversible-merge design (`caveats/audit-open.md#destructive-reconcile`), not a patch.
|
||||
c. ~~Separate extra from cast.~~ **Done 2026-08-12, not yet run on a GPU**
|
||||
(`decisions/identity-bbox.md#face-gates-enrollment`,
|
||||
`decisions/identity-bbox.md#extras-gate-consumers`). `has_face` stops a faceless detection
|
||||
enrolling, and two more consumers now skip it: `worker_scene`'s `actions`, which is the script
|
||||
prompt's content and the verifier's evidence, and `service._beat`, the director's "who".
|
||||
`_present_characters` stays ungated on purpose, reasoned out in the decision.
|
||||
The remaining gap is that vision still emits extras into the blob, which is deliberate so the
|
||||
audit can see what was gated.
|
||||
d. **The worked examples are gone.** The registry reset deleted `Lim Seonho` and
|
||||
`character_afa7623b`. The current registry is 8 rows, one named (`Seonho`), 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 no
|
||||
longer deletes the losing row: it sets `merged_into`, and stamps every repointed assignment with
|
||||
`method = merged_from:<loser_id>`. A wrong merge now costs a hand-written SQL walk, not a full
|
||||
rebaseline (`caveats/audit-open.md#destructive-reconcile`).
|
||||
|
||||
Deliberately not built: the unmerge path and the split. No wrong merge has been observed since the
|
||||
crops were fixed, so the consumer of those records waits for one. The forward case is partly covered
|
||||
by 2b, because a resolver NONE now mints rather than folding a stranger into the nearest match.
|
||||
Splitting a character that is ALREADY over-merged still needs a re-embed pass over its detections.
|
||||
|
||||
**Cast profiles already exist. Do not rebuild them.** The user asked whether the main cast could get a
|
||||
profile built from reference frames and reused. `characters` already carries `ref_image_uris` and
|
||||
|
||||
+15
-5
@@ -5,12 +5,22 @@ fixed findings live in `decisions/audit-phase1.md`. Line numbers are from the au
|
||||
|
||||
## Reconcile deletes the losing character irreversibly {#destructive-reconcile}
|
||||
|
||||
Reconciliation deletes the losing character row (`db.py:506`). Clearing the reconcile stage does not
|
||||
undo it, and name claims attached to the merged-away character are not repointed.
|
||||
Reconciliation used to delete the losing character row. Clearing the reconcile stage did not undo it, and
|
||||
nothing recorded which detections had been the loser's.
|
||||
|
||||
Costs: one bad merge is unrecoverable without rebuilding the identity stage for the whole manga.
|
||||
Revisit when: identity work resumes, or a reviewer reports a wrong merge on a real chapter.
|
||||
Workaround: none. Clear identity and rerun, which loses the good merges too.
|
||||
**Half-closed 2026-08-12.** The loss is no longer unrecoverable. `merge_characters` marks the loser
|
||||
`merged_into = keeper` instead of deleting it, so its embedding, description and gender survive, and every
|
||||
repointed assignment is stamped `method = merged_from:<loser_id>` in `identity_assignment_sources`. Those
|
||||
two records are enough to walk a merge backwards. Roster readers filter `merged_into IS NULL`. Lookup by
|
||||
id does not, so an assignment still pointing at a merged id resolves.
|
||||
|
||||
What is still missing is the mechanism that consumes them: there is no unmerge, and no split. Undoing a
|
||||
merge today means a manual SQL walk of the two records above.
|
||||
|
||||
Costs: a wrong merge needs hand-written SQL to undo, not a rebaseline.
|
||||
Revisit when: a reviewer reports a wrong merge, or the review gates from [#136] get a place to hang
|
||||
name/merge/split actions. No wrong merge has been seen since the crops were fixed, so building the unmerge
|
||||
before either trigger would be speculative.
|
||||
|
||||
## Clearing a stage does not undo what it wrote {#dishonest-clearing}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user