fix(901): unblock_story works on CRDT-only stories post-865

Bug 901: `unblock_story` (and the chat `unblock` command) routed through
`parse_front_matter` and errored with "Missing front matter" on any
post-865 story (story content is now CRDT-only with no YAML on disk).

In `chat/commands/unblock.rs::unblock_by_story_id`:
  - Drop the early `parse_front_matter` gate.
  - Read story name and blocked state from the CRDT register API instead
    of parsed YAML (`crdt_state::read_item`, `pipeline_state::read_typed`).
  - Keep the legacy fallback cleanup, but gate it on the content actually
    starting with a `---` YAML block, so CRDT-only stories don't hit a
    parse error there either.
  - Remove the now-unused `parse_front_matter` import.

Surfaced a second sub-bug: even when the state-machine transition
fired (`Blocked + Unblock → Coding`), the CRDT `blocked` register was
never explicitly cleared. Pre-865 the YAML-strip content_transform
cleared it as a side effect; post-865 there is no YAML to strip.

  - Add `crdt_state::set_blocked(story_id, bool)` parallel to
    `set_retry_count`. Wired through `crdt_state::write` and the
    crate-level re-export.
  - `agents::lifecycle::transition_to_unblocked` now calls
    `set_blocked(story_id, false)` alongside `set_retry_count(0)` so
    the legacy register stays in sync with the typed stage.

Test: `unblock_command_works_on_crdt_only_story_no_yaml` seeds a CRDT
entry with no YAML on disk, runs unblock, asserts success + cleared
blocked + retry_count=0. All 10 existing unblock tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 13:13:01 +01:00
parent cd12cb5e2c
commit 98d496b1ad
5 changed files with 159 additions and 67 deletions
+9 -4
View File
@@ -286,7 +286,11 @@ pub fn transition_to_unblocked(story_id: &str) -> Result<(), String> {
.map(|_| ())
.map_err(|e| e.to_string())?;
// Reset the retry counter in the CRDT so the story gets a fresh budget.
// Reset CRDT registers so the legacy `blocked`/`retry_count` fields match
// the new typed stage. Pre-865, YAML stripping kept these in sync as a
// side-effect of the content_transform above; post-865 the content has no
// YAML, so we must clear the registers explicitly.
crate::crdt_state::set_blocked(story_id, false);
crate::crdt_state::set_retry_count(story_id, 0);
Ok(())
}
@@ -312,9 +316,10 @@ fn map_stage_move_to_event(
feature_branch: branch(),
commits_ahead: nz1(),
}),
(Stage::Coding, "backlog") | (Stage::Qa, "backlog") | (Stage::Merge { .. }, "backlog") => {
Ok(PipelineEvent::Demote)
}
(Stage::Coding, "backlog")
| (Stage::Qa, "backlog")
| (Stage::Merge { .. }, "backlog")
| (Stage::Blocked { .. }, "backlog") => Ok(PipelineEvent::Demote),
(Stage::Qa, "current") => Ok(PipelineEvent::GatesFailed {
reason: "manual move".to_string(),
}),