From d63a337f158ea7e85a2e2f0718b6d054a89cede1 Mon Sep 17 00:00:00 2001 From: kami Date: Wed, 12 Aug 2026 19:27:13 +0400 Subject: [PATCH] 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 --- JOURNAL.md | 63 +++++++++++++++++++++++++++++++++++++++++++ NEXT.md | 25 ++++++++++++++--- caveats/audit-open.md | 20 ++++++++++---- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/JOURNAL.md b/JOURNAL.md index f30bdf7..d4247cb 100644 --- a/JOURNAL.md +++ b/JOURNAL.md @@ -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:` 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. diff --git a/NEXT.md b/NEXT.md index be20615..5318855 100644 --- a/NEXT.md +++ b/NEXT.md @@ -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:`. 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 diff --git a/caveats/audit-open.md b/caveats/audit-open.md index cebd189..f0f4f92 100644 --- a/caveats/audit-open.md +++ b/caveats/audit-open.md @@ -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:` 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}