Cut the dead attic workers, and file what the audit left standing

attic/worker_ocr.py and attic/worker_parse.py are 224 lines imported by nothing
and named in no doc. The OCR stage was removed when narration moved to the
director beat. The two design notes in attic/ stay, they are history.

worker_vision._panel_size had one reference and it was the definition.

The audit's larger finding is filed rather than fixed: call_gemma4,
_extract_json and _strip_thought exist in both worker_vision and worker_script
and have already diverged. That matters because the JSON repair pass can
fabricate dialogue, so a fix would land in one copy and not the other. It is
caveats/audit-open.md#gemma-helpers-duplicated with its revisit trigger.

HANDOFF.md carries the rest: _wrap2 against textwrap, the duplicated ONNX
preprocessing, and worker_layers pointing at a legacy/ directory that was never
tracked in git.

Checked: ruff clean, worker_vision and worker_render self-checks pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 23:06:34 +04:00
parent bec9411af3
commit 0d281016e7
6 changed files with 88 additions and 320 deletions
+71 -86
View File
@@ -1,121 +1,106 @@
# HANDOFF, 2026-08-12/13 (seventh session)
# HANDOFF, 2026-08-13 (eighth session)
Live state is in `NEXT.md`. This file is only what this session did. The previous handoff is in
`JOURNAL.md`.
## Asked
"go" on item 1 of the previous plan, then a fourth GPU cycle. Then "commit and let's figure out how to
deal with the 2b1b". Then a long architecture message, "mind turning the architecture in docs?", plus a
question about panel merging before cropping. Then "well, I'd like the merge to be wired actually. and
then 3 of your fixes."
Push the repo to Gitea and open a PR to `master`. Then, from the PR review: fix the s3 URI constants,
run ruff and fix what it finds, push, run a ponytail audit, fix, push, merge. Then audit.
## Result
Item 1 written, run on a GPU, committed. Naming diagnosed from a real dialogue run and three fixes written
against it. The crop merge wired. `ARCHITECTURE.md` written. Nothing has run on a GPU since 20:25 UTC.
No GPU work. Nothing ran on a pipeline stage. PR #1 is open at
`https://gitea.kvmx.ru/kami/manga-recap-pipeline/pulls/1`, `master` <- `restore-runtime`.
## Committed and proven on a GPU
`master` held only the reconstruction commit `ff6a512`. All 27 commits of real work sat unpushed on
`restore-runtime`.
`a386e9d` here, `2daaa84` in the orchestrator. **The resolver gallery is the live cast, not cosine's top-k**
(`decisions/identity-bbox.md#cast-is-the-gallery`). `tracklets.cast_gallery` builds it from
`get_known_characters`, gender-compatible, named first, `GALLERY_CAP = 10`, re-read per tracklet. The
`if s.get("candidates")` guard is gone, so a crop with an empty cosine top-k now reaches the resolver.
`worker_vision.REF_BUDGET = 12` spreads reference images, `max(1, min(3, 12 // n))` apiece.
## Landed
Fourth cycle, 19:44-19:52 UTC: vision 116/116 3m55s, identity 116/116 2m44s, reconcile 20/20 44s. Ran with
the span cap and `GENERIC` tokenizer, which had never touched a GPU either.
| commit | what |
| --- | --- |
| `12d4ed8` | the `ARCHITECTURE.md` rewrite, which was finished but uncommitted |
| `be63b22` | deleted a 0-byte file named `II, d[i+4:i+12]))\n"; done`, added by `8113bdf` |
| `bec9411` | s3 URI templates into `transport.py`, plus `ruff.toml` and the lint fixes |
| metric | 18:07 run | 19:44 run |
| --- | --- | --- |
| detections | 119 | 119 |
| assignments | 68 | 60 |
| coverage | 57% | 50% |
| tracklets / crops | 12 / 64 | 33 / 72 |
| lead's assignments | 36 | 16 |
| characters after reconcile | 18 | 14 |
| minted / cleared | -- | 10 / 12 |
### S3 URIs
Coverage fell because gemma clears 12 crops instead of naming them wrongly. Checked by eye and confirmed by
the user. The lead's 16 are 14 him plus 2 art-in-art. `character_2b1b12a1` holds 13, all correct.
`character_f0d4e901` holds 9, of which 2 are `2b1b12a1`.
Eight templates now live in `transport.py`: `PANEL_URI`, `PAGE_PANEL_URI`, `AUDIO_URI`,
`AUDIO_FLAT_URI`, `LAYER_URI`, `CLIP_URI`, `CHAPTER_URI`, `CHAR_PNG_URI`, `CHAR_NPY_URI`. Five workers
formatted their own before.
## Measured, then fixed
`transport.ids_from_uri` replaces three separate copies of the same parse in `worker_tts`,
`worker_layers` and `worker_render`. It raises on a uri too short to carry the ids instead of returning
a wrong pair. `worker_render._mc_from_uri` is gone, its 6 call sites repointed.
Dialogue ran 116/116 in 5m57s at 20:19 UTC to test the naming path. Six claims, three defects:
### Lint
```
p040 character_2b1b12a1 "Choi Haeseon" caption 1.00 -> NOT promoted
p010 character_b1dd5659 "Lim Seonho" caption 1.00 -> conflict flag
p047 character_b1dd5659 "Seonho" address 0.90 -> conflict flag
p011 character_f0d4e901 "Seonho" address 1.00 -> PROMOTED (wrong body)
p026 character_f0d4e901 "Seonho" address 1.00 -> PROMOTED (wrong body)
p110 character_028d4a49 "Haeseon" address 1.00 -> already named
```
`ruff check .` exits 0. Ruff's defaults found 115. Fixed: implicit `Optional` in 8 signatures, an
unparenthesized implicit concatenation inside the ASS filter list, 5 `subprocess.run` calls now saying
`check=False` out loud, 1 unused import, 1 duplicate exception handler, 1 non-executable shebang,
4 `dict()` calls and 2 `startswith` chains.
## Written, tested, NOT run on a GPU
12 rules are off in `ruff.toml`, each with its reason. The ASYNC ones matter: ffmpeg and ffprobe run
synchronously inside `async def` endpoints, so a busy worker cannot answer `/health`. That is already
`caveats/audit-open.md#blocking-event-loop`, tracked as [#199], and it needs a per-handler refactor
rather than a lint fix.
Orchestrator, committed `2927927`, `db.add_name_claim` + `decisions/identity-naming.md`:
`CLAUDE.md` said "There is no lint or build step". It now names `ruff check .` and the rule that every
output URI is a template in `transport.py`.
- `alias_groups`: one name's word set inside another's is the same name, longer wins
- `STRONG_EVIDENCE` gains `caption` at confidence >= 0.9
- a name held by another live character refuses to promote, files `name-already-taken`
## Audit findings
Here, committed `97cb483`:
Applied:
- `merge_faceless_captions` wired into both crop endpoints. It existed and was never called.
- `ARCHITECTURE.md`, the target shape from the user's design, each section carrying what exists today.
- `delete:` `attic/worker_ocr.py` and `attic/worker_parse.py`, 224 lines, imported by nothing and named
in no doc. `attic/char-recognition.md` and `attic/plan-workpc.md` kept, they are design history.
- `delete:` `worker_vision._panel_size`, 8 lines, one reference and it is the definition.
## Not started
Found and NOT applied, in order of size:
- **The head/body split is not fixed.** The wired merge only folds a fragment with text and no face. A body
fragment has no dialogue, so `_merge_plan` leaves it solo. No evidence gathered on how often it happens.
- The three cheap items from `ARCHITECTURE.md`: a `plane` field per detection, same-panel co-presence as a
weak cannot-link, `offscreen` as a fourth `speaker_ref` kind.
- `caveats/audit-open.md#gallery-cap-drops-the-unnamed`: the cap fired at `p097` (16 -> 10) and `p109`
(11 -> 10) and dropped exactly the freshly minted anonymous rows.
- `shrink:` `call_gemma4`, `_extract_json` and `_strip_thought` each exist twice, in `worker_vision.py`
and `worker_script.py`, and **have already diverged**. `worker_vision.call_gemma4` is 21 lines and
takes a content list, `worker_script`'s is 8 and takes a prompt string plus a system prompt. The two
`_extract_json` bodies carry the same comment about `raw_decode` but different error text. This is
the live risk: `caveats/audit-open.md#repair-fabricates` says the JSON repair pass can fabricate
dialogue, and a fix would land in one copy. A shared `gemma.py` costs one new file and removes about
25 duplicated lines. Not done because it touches the two largest workers and nothing has run on a GPU
since.
- `stdlib:` `worker_render._wrap2`, 16 lines of greedy word-wrap with a 2-line cap, is close to
`textwrap.wrap(text, width, max_lines=2, placeholder="…")`. Not identical: `_wrap2` does
`rstrip(".,")` before the ellipsis, and textwrap reserves width for the placeholder, so the break
points differ. It renders burned-in subtitles, so the diff is user-visible. Verify against the render
self-check before swapping.
- `shrink:` `_letterbox` and `_load` are duplicated between `bubble_detect.py` and `face_detect.py`,
two ONNX detectors with the same preprocessing. About 16 lines.
- `delete:` `worker_layers.py:14` points `LAYERED_WORKFLOW` at `legacy/qwen_layered_workflow.json`.
`legacy/` was **never tracked in git** and is absent from disk, so the module's self-check has never
been able to pass here. This is the other half of `caveats/audit-open.md#layers-writes-nothing`.
## Checks
```bash
.venv/bin/python worker_vision.py # ok, including the reference-budget asserts
.venv/bin/python worker_crop.py # ok
./check_stale.sh # exit 0 before the cycle
/usr/bin/ssh kami@192.168.1.104 "cd /home/kami/docker-apps/manga-infra/orchestrator && python3 tracklets.py && python3 test_name_binding.py && python3 -m pytest -q --ignore=test_api.py" # 121 passed
ruff check . # All checks passed
.venv/bin/python worker_render.py # ok, ffmpeg ran, about 4 minutes
```
Each new assert was confirmed to fail with its fix disabled.
Self-checks pass: `transport`, `collage`, `bubble_detect`, `test_vision_parse`, `worker_crop`,
`worker_scene`, `worker_script`, `worker_identity`, `worker_tts`, `session_manager`, `worker_vision`,
`worker_render`, `face_detect`.
`worker_layers` fails, and did before this branch, on the missing `legacy/` file above.
## Next command
Re-crop and run the fifth cycle. **Clear the panels prefix first** or the merge will not take effect:
`crop_webtoon` skips an upload when the key exists.
The fifth GPU cycle is still the next pipeline work. It is blocked only on the GPU being free. The
exact sequence is in `JOURNAL.md` under the seventh session, and `NEXT.md` item 1 holds the
expectations. **Clear the panels prefix first** or the wired caption merge
will not take effect.
```bash
cd /home/kami/Programs/n8n-worker && ./check_stale.sh # restart crop, it is stale after the wiring
/usr/bin/ssh kami@192.168.1.104 "cd /home/kami/docker-apps/manga-infra/orchestrator && docker compose up -d --build orchestrator"
J=778297bc-e7ce-439d-91b5-8a027060d17f; M=ef105a86-4b7e-4ac4-b45c-b7d83b8f5b5e; C=7c944dd4-e972-42c7-ba60-9f6939548e80
/usr/bin/ssh kami@192.168.1.104 "mc rm --recursive --force homesrv/panels/$M/$C/panels/"
/usr/bin/ssh kami@192.168.1.104 "curl -s -X POST http://127.0.0.1:9090/characters/reset -H 'Content-Type: application/json' -d '{\"manga_id\":\"$M\",\"confirm\":true}'"
# honour restart_identity_worker: true
/usr/bin/ssh kami@192.168.1.104 "curl -s -X POST http://127.0.0.1:9090/stage/clear -H 'Content-Type: application/json' -d '{\"job_id\":\"$J\",\"stage\":\"crop\"}'"
for S in crop vision identity reconcile dialogue; do /usr/bin/ssh kami@192.168.1.104 "curl -s --max-time 7200 -X POST http://127.0.0.1:9090/stage/run -H 'Content-Type: application/json' -d '{\"job_id\":\"$J\",\"stage\":\"$S\"}'"; done
```
Before that, or instead of it while the GPU is busy, `ARCHITECTURE.md` step 0 is the measurement spine:
`eval/chapter-truth.json` plus purity and fragmentation printed by `audit_registry.py`.
Expect: fewer than 116 panels, `2b1b12a1` named `Choi Haeseon`, no `conflicting-name-claims` on the lead,
and a `name-already-taken` flag where the female `Seonho` used to be.
## Traps confirmed or found
- **`crop_webtoon` skips an upload when the key exists.** Right for a resume, silently wrong after any
slicing change. Clear `s3://panels/<manga>/<chapter>/panels/` before re-cropping.
- Restart a worker with `tmux send-keys -t manga-workers:<window> C-c`, then re-send the launch line from
`start_workers.sh` with its `MIOPEN_ENV` prefix. A `pgrep` kill matches the calling shell.
- `POST /characters/reset` returns `restart_identity_worker: true`. Honour it.
- `docker compose up --build orchestrator` drops any `docker cp`'d script, `audit_registry.py` included.
- A heredoc piped into `docker exec` over `/usr/bin/ssh` produces no output. `scp` the file, `docker cp`
it, then run it.
- `panel_order` and the panel filename differ by one: `panel_order` 10 is `p009.png`.
- The contact-sheet script that found the identity errors is `sheet.py` in the session scratchpad, not
committed. It reads `assigns.json` dumped from `identity_assignments` and pulls crops from
`manga/<manga_id>/characters/_crops/`.
One ordering trap, unresolved: the truth file cannot be keyed on `panel_id` or `character_id`. The
fifth cycle re-crops and calls `/characters/reset`, which destroys both. Key it on page-space geometry,
or write it after the fifth cycle rather than before.