Skip to content

fix(validation): stop untranslated prose hiding behind reference context - #160

Merged
JunjoSick merged 1 commit into
mainfrom
port/source-copy-revalidation
Oct 21, 2026
Merged

JunjoSick merged 1 commit into
mainfrom
port/source-copy-revalidation

Conversation

@JunjoSick

@JunjoSick JunjoSick commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Was stacked on #159, which is now merged (83eedb7f); retargeted to main. One commit.

Summary

Ports the October 8–9 source-copy fix (ffc565e1 on the local, never-pushed remediation/audit-2026-08 branch) onto current main. It's re-implemented rather than cherry-picked, because main's validator and findings model changed after Aug 25 (CJK near-copies, non-linguistic blocks, structured findings with warning/error severity, warning-only batch acceptance in e4efaac2).

The defect on main. source_copy_validation_error returned early, before any check ran, when:

  • the section title contained a reference word at a word boundary: "Notes", "Endnotes", "Bibliography", "References", "Works Cited", "Index". That includes "Author's Note", "Translator's Note", "Notes from Underground" and "Subject Index";
  • the block started with p. <n>.;
  • the block contained in English: anywhere.

So whole untranslated paragraphs in those places passed validation. Cache hits were never checked at all, and resume trusted earlier accepted output.

Changes

  1. Validator (bookforge-llm/src/validation.rs): the three exemptions now apply only to blocks under MIN_SOURCE_CHARS (120). Longer text always goes through the equality and near-copy checks. Short reference lines keep exactly their current behavior, so this adds no noise on short citations.
  2. One rule for fresh, cached and saved output (batch/rendering.rs): new blocking_source_copy_error = the copy check and main's existing intentionally_unchanged_block grading. Titles, headings and short proper-noun lines stay warnings, as batch acceptance treats them, so cache and resume don't start rejecting what batches already accept.
  3. Cache (translate/cache.rs): a hit with a blocking copy becomes an ordinary miss; the source job is untouched.
  4. Resume (resume.rs, bookforge-store/src/db/translations.rs): after preflight and before context rehydration, saved provider output with a blocking copy moves to needs_review through new JobStore::mark_stored_translation_for_review. It's an immediate transaction guarded by the expected text and by human_corrected = 0. Text, block rows, attempts, usage and provenance are untouched, and the finding is appended (the engine-findings writer would delete the segment's existing findings). Flagged segments aren't retranslated automatically.
  5. Human corrections and double-check, a real bug on main: rebuild_block_translations lets in-memory translations override stored blocks, and resume's double-check edits those in-memory translations, accepted segments included. A reviewer's correction stayed frozen in SQLite but could be replaced in the rebuilt EPUB. Human-accepted segments are now kept out of the pass entirely, and with nothing to review no double-check provider is constructed.

The trade-off to review

A verbatim citation of 120+ characters (a long bibliography entry kept unchanged) is now flagged and needs explicit acceptance with bookforge correct. Shorter citations are unaffected. The October design chose this deliberately, after seeing a handful of such references on a real book, over guessing which text is a citation from author/year/punctuation patterns. A structural citation classifier is still open work.

Validation

  • Validator matrix: long copies and near-copies are flagged under every reference heading (Notes, Endnotes, Bibliography, References, Works Cited, Index, Author's Note, Notes to Chapter 1, none) × prefix (none, p. 12., in English:). Short citations stay exempt in reference sections. Correct translations of the old exemption test strings (an endnote, a bilingual gloss) are not flagged.
  • Store: a stale expected text is refused; translations/blocks/usage rows are byte-identical before and after; reclassification happens once; existing findings are kept; human acceptance wins.
  • Cache/revalidation: copied prose misses the cache without touching the source job; translated hits are reused with no new usage; a short reference citation, an unchanged heading, mock runs and same-language runs stay reusable; resume flags once and keeps evidence; an accepted citation survives resume and is not shared across jobs; results restored from the cache are revalidated too.
  • Resume end to end: a legacy copy saved as deepseek is flagged with blocks/provider/usage/attempts unchanged, and the EPUB is still written. After bookforge correct acceptance, a resume with double-check Full pointed at a closed local port with an unset API key completes without ever building that provider, and leaves the accepted row identical. Mutation-checked: with the human-correction filter disabled, this test fails (double-check failed: … environment variable … is not set), offline and in under a second. (main silently skips unknown double-check providers, so an unsupported provider name would not have tested anything.)
  • Gate on Windows (GNU) with CI's compiler, rustc 1.99.1, and RUSTFLAGS="-D warnings": clippy (all targets/features), fmt, git diff --check, release-security script, no-default-features check, dashboard JS tests and the 1.88 MSRV check all pass. Workspace tests: 1,435 passed, 1 failed, 9 ignored; examples: 13 passed.
  • The one failure is the known flaky batch::tests::persistent_decode_error_bisects_an_oversized_batch_after_retry (request counts [2, 2], or a hang; documented in fix(ci): unbreak CI after Rust 1.99 and clear October RustSec advisories #159). This port can't reach it: the test's inputs are two short words, and the change only affects text of 120+ characters in reference contexts. Single-test reruns on this build went 12 ok / 2 hang / 1 fail of 15, against 26 / 1 / 3 of 30 on main, with the same failure modes. The 54 validation and batch-rendering tests pass.
  • Not run locally: the browser smoke step, Linux and Windows MSVC (CI covers them), and no real-book replay. That needs a fresh paid job, because saved jobs from older prompt versions refuse to resume.

🤖 Generated with Claude Code

@JunjoSick
JunjoSick force-pushed the port/source-copy-revalidation branch from 03593b5 to cee9333 Compare October 20, 2026 23:17
@JunjoSick
JunjoSick changed the base branch from fix/fetch-update-deprecation to main October 20, 2026 23:37
@JunjoSick
JunjoSick marked this pull request as ready for review October 20, 2026 23:38
Port of the October source-copy fix (local remediation/audit-2026-08,
ffc565e1), re-implemented on main's validator and findings model.

source_copy_validation_error exempted whole sections titled like Notes,
Bibliography or Index, any block starting with a page reference, and any
text containing an "in English:" gloss, so long untranslated paragraphs
passed validation. The exemptions now apply only below MIN_SOURCE_CHARS;
longer text always gets the equality and near-copy checks.

blocking_source_copy_error combines that check with batch severity
grading, so cache reuse and resume revalidation hold stored output to
the rule fresh batch output passes: titles, headings and short
proper-noun lines stay warnings.

- cache: a hit with a blocking copy becomes an ordinary miss
- resume: saved provider copies move to needs_review through
  JobStore::mark_stored_translation_for_review before context
  rehydration; text, usage and provenance are untouched and the finding
  is appended rather than replacing existing ones
- resume double-check: human-accepted segments are excluded. The EPUB
  is rebuilt from in-memory translations, so an automated rewrite could
  replace a correction in the output while the store kept it frozen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JunjoSick
JunjoSick force-pushed the port/source-copy-revalidation branch from cee9333 to b410e30 Compare October 20, 2026 23:45
@JunjoSick
JunjoSick merged commit a6e6635 into main Oct 21, 2026
15 checks passed
@JunjoSick
JunjoSick deleted the port/source-copy-revalidation branch October 21, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant