S0-R1-C4 Unified Phase-1 Validation Barrier + Retry Budget Ownership

#77
Files changed (1) hide show
  1. core/generation_runner.py +41 -51
core/generation_runner.py CHANGED
@@ -3087,46 +3087,18 @@ The previous attempt for this same manuscript unit was rejected by validation. R
3087
  # Model prose has no authority over quote presence/count.
3088
  en_raw = self._strip_model_controlled_qualitative_quotes(en_raw, data_metadata)
3089
 
3090
- # S0-R1 phase 1: feedback-only critic + pure semantic validators.
3091
- pre_validation_rows = self._section_validation_bundle(
3092
- mode, agent, quant_engine, prefix + en_raw, data_metadata,
3093
- section_id=section_id, section_name=display_name,
3094
- evidence_contract=evidence_packet,
3095
- include_critic=not deterministic_appendix,
3096
- )
3097
- if pre_validation_rows:
3098
- if section_attempt < max_section_attempts:
3099
- attempt_feedback = self._section_retry_feedback(pre_validation_rows)
3100
- logging.warning(
3101
- "Section validation retry | unit=%s attempt=%s count=%s types=%s",
3102
- display_name, section_attempt, len(pre_validation_rows),
3103
- sorted({str(row.get("type") or "") for row in pre_validation_rows if isinstance(row, dict)}),
3104
- )
3105
- self._restore_section_citation_state(
3106
- agent, citation_plan, display_name, frozen_citation_state
3107
- )
3108
- continue
3109
- self._restore_section_citation_state(
3110
- agent, citation_plan, display_name, frozen_citation_state
3111
- )
3112
- state.update({
3113
- "citation_plan": citation_plan,
3114
- "agent_state": agent.export_state(),
3115
- "quality_diagnostics": quality_diagnostics,
3116
- })
3117
- self._checkpoint_section_retry_exhausted(
3118
- state, section_id=section_id, section_name=display_name,
3119
- violations=pre_validation_rows, pct=pct, attempts=max_section_attempts,
3120
- )
3121
- raise RuntimeError(
3122
- f"section_content_invalid_after_retry:{display_name}:{pre_validation_rows[:3]}"
3123
- )
3124
-
3125
- # S0-R1-C2: the authoritative path never attaches a citation merely
3126
- # to satisfy section-level coverage. The writer-owned claim/evidence
3127
- # plan is audited as written; any validated coverage shortfall flows
3128
- # to the existing claim-anchored recovery path below. This rule is
3129
- # shared by ASQ and AMJ and keeps the sealed English citation binding
3130
  # authoritative for ZH/RU/KK translation.
3131
  self._progress(pct, f"citation_audit:{current_index}:{total_steps}")
3132
  used_before = set(agent.used_citations)
@@ -3134,20 +3106,33 @@ The previous attempt for this same manuscript unit was rejected by validation. R
3134
  prefix + en_raw, section_name=display_name, section_id=section_id,
3135
  quarantine=is_quarantined
3136
  )
3137
- # Capture immediately: citation recovery may later replace the
3138
- # agent's last-violation buffer. These violations belong to the
3139
- # writer attempt and require the one bounded same-Section retry.
3140
  citation_semantic_rows = copy.deepcopy(
3141
  list(getattr(agent, "_last_citation_semantic_violations", []) or [])
3142
  )
3143
- if citation_semantic_rows:
 
 
 
 
 
 
 
 
 
 
 
3144
  if section_attempt < max_section_attempts:
3145
- retry_excluded_tags.update(self._citation_violation_tags(citation_semantic_rows))
3146
- attempt_feedback = self._section_retry_feedback(citation_semantic_rows)
3147
  logging.warning(
3148
- "Citation semantic Section retry | unit=%s attempt=%s count=%s types=%s",
3149
- display_name, section_attempt, len(citation_semantic_rows),
3150
- sorted({str(row.get("type") or "") for row in citation_semantic_rows if isinstance(row, dict)}),
 
 
 
3151
  )
3152
  self._restore_section_citation_state(
3153
  agent, citation_plan, display_name, frozen_citation_state
@@ -3163,10 +3148,15 @@ The previous attempt for this same manuscript unit was rejected by validation. R
3163
  })
3164
  self._checkpoint_section_retry_exhausted(
3165
  state, section_id=section_id, section_name=display_name,
3166
- violations=citation_semantic_rows, pct=pct, attempts=max_section_attempts,
 
 
 
 
 
3167
  )
3168
  raise RuntimeError(
3169
- f"section_citation_invalid_after_retry:{display_name}:{citation_semantic_rows[:3]}"
3170
  )
3171
 
3172
  used_after = set(agent.used_citations)
 
3087
  # Model prose has no authority over quote presence/count.
3088
  en_raw = self._strip_model_controlled_qualitative_quotes(en_raw, data_metadata)
3089
 
3090
+ # S0-R1-C4 phase-1 barrier: all writer-owned semantic validators
3091
+ # inspect the same attempt before the single same-Section retry budget
3092
+ # can be consumed. Citation semantics therefore run before the one
3093
+ # unified validation decision instead of being short-circuited by a
3094
+ # validator that happens to execute earlier.
3095
+
3096
+ # S0-R1-C2/C4: citation semantics are part of the same phase-1
3097
+ # decision. The authoritative path never attaches a citation merely
3098
+ # to satisfy section-level coverage; it audits the writer-owned
3099
+ # claim/evidence binding exactly as written. Any validated coverage
3100
+ # shortfall still flows to the existing claim-anchored recovery path.
3101
+ # This rule is shared by ASQ and AMJ and keeps sealed English
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3102
  # authoritative for ZH/RU/KK translation.
3103
  self._progress(pct, f"citation_audit:{current_index}:{total_steps}")
3104
  used_before = set(agent.used_citations)
 
3106
  prefix + en_raw, section_name=display_name, section_id=section_id,
3107
  quarantine=is_quarantined
3108
  )
3109
+ # Capture immediately: later recovery may replace the agent's last-
3110
+ # violation buffer. Only citation-owned rejected tags may enter the
3111
+ # retry exclusion set.
3112
  citation_semantic_rows = copy.deepcopy(
3113
  list(getattr(agent, "_last_citation_semantic_violations", []) or [])
3114
  )
3115
+ phase1_validation_rows = self._section_validation_bundle(
3116
+ mode, agent, quant_engine, prefix + en_raw, data_metadata,
3117
+ section_id=section_id, section_name=display_name,
3118
+ evidence_contract=evidence_packet,
3119
+ include_critic=not deterministic_appendix,
3120
+ citation_violations=citation_semantic_rows,
3121
+ )
3122
+ non_citation_count = max(
3123
+ 0, len(phase1_validation_rows) - len(citation_semantic_rows)
3124
+ )
3125
+ if phase1_validation_rows:
3126
+ retry_excluded_tags.update(self._citation_violation_tags(citation_semantic_rows))
3127
  if section_attempt < max_section_attempts:
3128
+ attempt_feedback = self._section_retry_feedback(phase1_validation_rows)
 
3129
  logging.warning(
3130
+ "Unified Phase-1 Section retry | unit=%s attempt=%s count=%s types=%s",
3131
+ display_name, section_attempt, len(phase1_validation_rows),
3132
+ sorted({
3133
+ str(row.get("type") or "")
3134
+ for row in phase1_validation_rows if isinstance(row, dict)
3135
+ }),
3136
  )
3137
  self._restore_section_citation_state(
3138
  agent, citation_plan, display_name, frozen_citation_state
 
3148
  })
3149
  self._checkpoint_section_retry_exhausted(
3150
  state, section_id=section_id, section_name=display_name,
3151
+ violations=phase1_validation_rows, pct=pct, attempts=max_section_attempts,
3152
+ )
3153
+ terminal_kind = (
3154
+ "section_citation_invalid_after_retry"
3155
+ if citation_semantic_rows and non_citation_count == 0
3156
+ else "section_content_invalid_after_retry"
3157
  )
3158
  raise RuntimeError(
3159
+ f"{terminal_kind}:{display_name}:{phase1_validation_rows[:3]}"
3160
  )
3161
 
3162
  used_after = set(agent.used_citations)