refactor(929): drop redundant YAML re-parse in db::ops::move_item_stage

Every stage transition was reading the content body's YAML front matter to
derive name/agent/blocked/depends_on, then writing those values straight
back into the CRDT registers — but the CRDT was already the source of
truth for all of these fields. The reparse was at best a no-op and at
worst could regress the CRDT to stale YAML values during transitions on
items whose YAML was out of date.

Now move_item_stage:
- writes the new stage to the CRDT with None for every other field, so
  write_item leaves existing registers untouched.
- reads name/agent/blocked/depends_on back from the CRDT view when
  mirroring the row into the SQLite shadow table (still needed because
  the shadow stores a denormalised snapshot for read-side queries).

The yaml_legacy::parse_front_matter import is gone from db/ops.rs; the
only path still using it on the production side is ItemMeta::from_yaml,
which is a caller convenience (mostly used in test fixtures).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 20:15:08 +01:00
parent 4888f051c3
commit 6c62e0fa31
+20 -25
View File
@@ -132,24 +132,10 @@ pub fn move_item_stage(
_ => None,
};
let (name, agent, _ignored_retry_count, blocked, depends_on) = content
.as_deref()
.or(current_content.as_deref())
.and_then(|c| parse_front_matter(c).ok())
.map(|meta| {
(
meta.name,
meta.agent,
meta.retry_count.map(|r| r as i64),
meta.blocked,
meta.depends_on
.as_ref()
.and_then(|d| serde_json::to_string(d).ok()),
)
})
.unwrap_or((None, None, None, None, None));
// CRDT stage transition.
// Story 929: metadata (name/agent/blocked/depends_on) is owned by the
// CRDT typed registers — no need to re-derive it from the content body's
// YAML front matter on every stage transition. Pass `None` for those
// fields so write_item leaves the existing registers untouched.
let merged_at_ts = if crate::pipeline_state::Stage::from_dir(new_stage)
.is_some_and(|s| matches!(s, crate::pipeline_state::Stage::Done { .. }))
{
@@ -160,11 +146,11 @@ pub fn move_item_stage(
crate::crdt_state::write_item(
story_id,
new_stage,
name.as_deref(),
agent.as_deref(),
None,
blocked,
depends_on.as_deref(),
None,
None,
None,
None,
None,
None,
merged_at_ts,
@@ -176,15 +162,24 @@ pub fn move_item_stage(
// transitions.
crate::crdt_state::set_retry_count(story_id, 0);
// Shadow table — always reset retry_count to 0 on stage transition.
let retry_count: Option<i64> = Some(0);
// Shadow table — read current metadata from the CRDT so the SQLite
// mirror stays in sync. Always reset retry_count to 0 on stage transition.
if let Some(db) = PIPELINE_DB.get() {
let view = crate::crdt_state::read_item(story_id);
let name = view.as_ref().and_then(|v| v.name().map(str::to_string));
let agent = view.as_ref().and_then(|v| v.agent().map(str::to_string));
let blocked = view.as_ref().map(|v| v.blocked());
let depends_on = view
.as_ref()
.map(|v| v.depends_on())
.filter(|d| !d.is_empty())
.and_then(|d| serde_json::to_string(d).ok());
let msg = PipelineWriteMsg {
story_id: story_id.to_string(),
stage: new_stage.to_string(),
name,
agent,
retry_count,
retry_count: Some(0),
blocked,
depends_on,
content,