feat(932): add review_hold CRDT register + migrate callers off yaml_legacy

review_hold is now a typed bool register on PipelineItemCrdt alongside
blocked / mergemaster_attempted. Exposed via the typed setter
`crdt_state::set_review_hold(story_id, value)` and the
`WorkItem::review_hold()` accessor. Replaces the legacy
`review_hold: true` YAML front-matter field.

Migrated callers:
- http/mcp/qa_tools.rs::tool_approve_qa  — clear via set_review_hold(false)
- agents/lifecycle.rs::reject_story_from_qa  — clear via set_review_hold(false)
- agents/pool/pipeline/advance/helpers.rs::write_review_hold_to_store
  — set via set_review_hold(true), no more content rewrite
- agents/pool/auto_assign/reconcile.rs (two callsites) — set via
  set_review_hold(true) instead of FS YAML write
- agents/pool/auto_assign/story_checks.rs::has_review_hold — reads the
  typed register instead of conflating with Stage::Frozen (real bug fix:
  the legacy implementation returned `stage.is_frozen()`, which made
  the auto-assigner treat *every* held-for-review item as frozen even
  when it wasn't actually parked at the freeze stage).

Dead yaml_legacy helpers removed:
- write_review_hold(path), write_review_hold_in_content(content)
- clear_front_matter_field(path) — last caller was the qa_tools wrap

The yaml_residue marker doc now only mentions 933; the 932 line is gone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 19:49:36 +01:00
parent f9f16d6a14
commit aadbb1b2af
12 changed files with 122 additions and 130 deletions
-35
View File
@@ -9,8 +9,6 @@
use crate::io::story_metadata::QaMode;
use serde::Deserialize;
use std::fs;
use std::path::Path;
/// Identity wrapper that flags a yaml_legacy callsite blocked on adding a
/// CRDT register (story 929 residue). Every wrap is a grep-findable marker —
@@ -22,8 +20,6 @@ use std::path::Path;
/// entirely (929 stage 10).
///
/// Filed sub-stories enumerate each gap:
/// - 932: `review_hold` flag (write-side in qa_tools, read-side in
/// auto_assign).
/// - 933: epic mechanism — `item_type` and `epic` link fields.
pub fn yaml_residue<T>(v: T) -> T {
v
@@ -200,37 +196,6 @@ pub(crate) fn write_merge_failure_in_content(contents: &str, reason: &str) -> St
set_front_matter_field(contents, "merge_failure", &format!("\"{escaped}\""))
}
/// Write `review_hold: true` to story content.
pub(crate) fn write_review_hold_in_content(contents: &str) -> String {
set_front_matter_field(contents, "review_hold", "true")
}
/// Remove a key from the YAML front matter of a story file on disk.
///
/// Legacy filesystem-backed wrapper around
/// [`clear_front_matter_field_in_content`] for the small number of callers
/// that still read story files directly.
pub(crate) fn clear_front_matter_field(path: &Path, key: &str) -> Result<(), String> {
let contents =
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
let updated = clear_front_matter_field_in_content(&contents, key);
if updated != contents {
fs::write(path, &updated).map_err(|e| format!("Failed to write story file: {e}"))?;
}
Ok(())
}
/// Write `review_hold: true` to the YAML front matter of a story file on disk.
///
/// Legacy filesystem-backed wrapper around [`write_review_hold_in_content`].
pub(crate) fn write_review_hold(path: &Path) -> Result<(), String> {
let contents =
fs::read_to_string(path).map_err(|e| format!("Failed to read story file: {e}"))?;
let updated = write_review_hold_in_content(&contents);
fs::write(path, &updated).map_err(|e| format!("Failed to write story file: {e}"))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;