R6A-R4-R2 quote translation ledger identity type safety

#41
Files changed (1) hide show
  1. core/generation_runner.py +56 -7
core/generation_runner.py CHANGED
@@ -608,6 +608,41 @@ class GenerationRunner:
608
  and str(item.get("quote") or "").strip()
609
  }
610
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  @classmethod
612
  def _prepare_qualitative_translation_source(cls, source, quote_sources, quote_ledger):
613
  """Use one task-level target rendering for every frozen qualitative quote.
@@ -953,20 +988,34 @@ class GenerationRunner:
953
  english_commit_text=english_commit_text,
954
  )
955
  updated_sections = dict(translation_sections or {})
956
- quote_ledger_record = dict(updated_sections.get("__quote_ledger__") or {})
 
 
 
 
 
 
 
 
957
  current_quote_hashes = {
958
  tag: hashlib.sha256(re.sub(r"\s+", " ", quote).strip().encode("utf-8")).hexdigest()
959
  for tag, quote in getattr(self, "_qualitative_quote_sources", {}).items()
960
  }
961
  current_request_hash = str(getattr(self, "request_hash", "") or "")
962
- stored_quote_hashes = quote_ledger_record.get("source_quote_hashes")
963
- ledger_identity_ok = (
964
- quote_ledger_record.get("target_lang") in {None, "", target_lang}
965
- and quote_ledger_record.get("request_hash") in {None, "", current_request_hash}
966
- and (stored_quote_hashes in {None, ""} or stored_quote_hashes == current_quote_hashes)
 
 
 
967
  )
 
968
  quote_ledger = (
969
- dict(quote_ledger_record.get("translations") or {}) if ledger_identity_ok else {}
 
 
970
  )
971
  translation_source, pending_quote_tags, reused_quote_tags = self._prepare_qualitative_translation_source(
972
  source, getattr(self, "_qualitative_quote_sources", {}), quote_ledger
 
608
  and str(item.get("quote") or "").strip()
609
  }
610
 
611
+ @staticmethod
612
+ def _qualitative_quote_ledger_identity_ok(
613
+ record, *, target_lang: str, request_hash: str, source_quote_hashes: dict,
614
+ task_id: str = "", translation_contract_version: str = TRANSLATION_CONTRACT_VERSION,
615
+ ) -> bool:
616
+ """Type-safe identity gate for task-level target-language Quote reuse.
617
+
618
+ Missing scalar identity fields remain backward-compatible with pre-R4
619
+ checkpoints. Present fields must be strings and match exactly. Quote
620
+ hashes must be a mapping and match the current frozen English evidence.
621
+ execution_epoch is deliberately not part of logical identity so a newer
622
+ epoch can reuse the same QA-passed target rendering for the same task.
623
+ """
624
+ if not isinstance(record, dict) or not isinstance(source_quote_hashes, dict):
625
+ return False
626
+
627
+ def scalar_matches(stored, current: str) -> bool:
628
+ if stored is None or stored == "":
629
+ return True
630
+ return isinstance(stored, str) and stored == str(current or "")
631
+
632
+ stored_hashes = record.get("source_quote_hashes")
633
+ if stored_hashes is None or stored_hashes == "":
634
+ hashes_match = True
635
+ else:
636
+ hashes_match = isinstance(stored_hashes, dict) and stored_hashes == source_quote_hashes
637
+
638
+ return (
639
+ scalar_matches(record.get("task_id"), task_id)
640
+ and scalar_matches(record.get("target_lang"), target_lang)
641
+ and scalar_matches(record.get("request_hash"), request_hash)
642
+ and scalar_matches(record.get("translation_contract_version"), translation_contract_version)
643
+ and hashes_match
644
+ )
645
+
646
  @classmethod
647
  def _prepare_qualitative_translation_source(cls, source, quote_sources, quote_ledger):
648
  """Use one task-level target rendering for every frozen qualitative quote.
 
988
  english_commit_text=english_commit_text,
989
  )
990
  updated_sections = dict(translation_sections or {})
991
+ raw_quote_ledger_record = updated_sections.get("__quote_ledger__")
992
+ if raw_quote_ledger_record is not None and not isinstance(raw_quote_ledger_record, dict):
993
+ logging.warning(
994
+ "Qualitative quote translation ledger ignored malformed record type=%s",
995
+ type(raw_quote_ledger_record).__name__,
996
+ )
997
+ quote_ledger_record = (
998
+ dict(raw_quote_ledger_record) if isinstance(raw_quote_ledger_record, dict) else {}
999
+ )
1000
  current_quote_hashes = {
1001
  tag: hashlib.sha256(re.sub(r"\s+", " ", quote).strip().encode("utf-8")).hexdigest()
1002
  for tag, quote in getattr(self, "_qualitative_quote_sources", {}).items()
1003
  }
1004
  current_request_hash = str(getattr(self, "request_hash", "") or "")
1005
+ current_task_id = str(getattr(getattr(self, "task_dir", None), "name", "") or "")
1006
+ ledger_identity_ok = self._qualitative_quote_ledger_identity_ok(
1007
+ quote_ledger_record,
1008
+ task_id=current_task_id,
1009
+ target_lang=target_lang,
1010
+ request_hash=current_request_hash,
1011
+ translation_contract_version=TRANSLATION_CONTRACT_VERSION,
1012
+ source_quote_hashes=current_quote_hashes,
1013
  )
1014
+ stored_translations = quote_ledger_record.get("translations")
1015
  quote_ledger = (
1016
+ dict(stored_translations)
1017
+ if ledger_identity_ok and isinstance(stored_translations, dict)
1018
+ else {}
1019
  )
1020
  translation_source, pending_quote_tags, reused_quote_tags = self._prepare_qualitative_translation_source(
1021
  source, getattr(self, "_qualitative_quote_sources", {}), quote_ledger