huskies: merge 945

This commit is contained in:
dave
2026-05-13 06:05:01 +00:00
parent 3a8894ea8f
commit 9ce5a8df0c
53 changed files with 497 additions and 654 deletions
+2 -3
View File
@@ -53,9 +53,8 @@ pub use types::{
};
pub use write::{
bump_retry_count, migrate_legacy_stage_strings, migrate_names_from_slugs,
migrate_story_ids_to_numeric, name_from_story_id, set_agent, set_blocked, set_depends_on,
set_epic, set_frozen, set_item_type, set_mergemaster_attempted, set_name, set_qa_mode,
set_retry_count, set_review_hold, write_item,
migrate_story_ids_to_numeric, name_from_story_id, set_agent, set_depends_on, set_epic,
set_item_type, set_name, set_qa_mode, set_resume_to, set_retry_count, write_item,
};
#[cfg(test)]
-1
View File
@@ -552,7 +552,6 @@ mod tests {
None,
None,
None,
None,
);
assert!(
read_item(story_id).is_none(),
+24 -44
View File
@@ -21,7 +21,6 @@ pub struct CrdtItemDump {
pub name: Option<String>,
pub agent: Option<String>,
pub retry_count: Option<i64>,
pub blocked: Option<bool>,
pub depends_on: Option<Vec<u32>>,
pub claimed_by: Option<String>,
pub claimed_at: Option<f64>,
@@ -135,10 +134,6 @@ pub fn dump_crdt_state(story_id_filter: Option<&str>) -> CrdtStateDump {
JsonValue::Number(n) => Some(n as i64),
_ => None,
};
let blocked = match item_crdt.blocked.view() {
JsonValue::Bool(b) => Some(b),
_ => None,
};
let depends_on = match item_crdt.depends_on.view() {
JsonValue::String(s) if !s.is_empty() => serde_json::from_str::<Vec<u32>>(&s).ok(),
_ => None,
@@ -161,7 +156,6 @@ pub fn dump_crdt_state(story_id_filter: Option<&str>) -> CrdtStateDump {
name,
agent,
retry_count,
blocked,
depends_on,
claimed_by,
claimed_at,
@@ -321,10 +315,6 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
JsonValue::Number(n) => Some(n as i64),
_ => None,
};
let blocked = match item.blocked.view() {
JsonValue::Bool(b) => Some(b),
_ => None,
};
let depends_on = match item.depends_on.view() {
JsonValue::String(s) if !s.is_empty() => serde_json::from_str::<Vec<u32>>(&s).ok(),
_ => None,
@@ -348,16 +338,6 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
_ => None,
};
let mergemaster_attempted = match item.mergemaster_attempted.view() {
JsonValue::Bool(b) => Some(b),
_ => None,
};
let review_hold = match item.review_hold.view() {
JsonValue::Bool(b) => Some(b),
_ => None,
};
let item_type = match item.item_type.view() {
JsonValue::String(s) if !s.is_empty() => Some(s),
_ => None,
@@ -368,12 +348,12 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
_ => None,
};
let frozen = match item.frozen.view() {
JsonValue::Bool(b) => Some(b),
let resume_to = match item.resume_to.view() {
JsonValue::String(s) if !s.is_empty() => Some(s),
_ => None,
};
let stage = project_stage_for_view(&stage_str, &story_id, merged_at, blocked)?;
let stage = project_stage_for_view(&stage_str, &story_id, merged_at, resume_to.as_deref())?;
Some(PipelineItemView {
story_id,
@@ -381,17 +361,13 @@ pub(super) fn extract_item_view(item: &PipelineItemCrdt) -> Option<PipelineItemV
name,
agent,
retry_count,
blocked,
depends_on,
claimed_by,
claimed_at,
merged_at,
qa_mode,
mergemaster_attempted,
review_hold,
item_type,
epic,
frozen,
})
}
@@ -412,7 +388,7 @@ fn project_stage_for_view(
stage_str: &str,
story_id: &str,
merged_at: Option<f64>,
blocked: Option<bool>,
resume_to: Option<&str>,
) -> Option<crate::pipeline_state::Stage> {
use crate::pipeline_state::{ArchiveReason, BranchName, GitSha, Stage};
use chrono::{DateTime, Utc};
@@ -436,6 +412,12 @@ fn project_stage_for_view(
other => other,
};
// Story 945: resume target for `Frozen` / `ReviewHold` variants is stored
// in the sibling `resume_to` register. Fall back to `Coding` when the
// register is empty or holds an unrecognised value.
let resume_target =
|| -> Box<Stage> { Box::new(resume_to.and_then(Stage::from_dir).unwrap_or(Stage::Coding)) };
match clean {
"upcoming" => Some(Stage::Upcoming),
"backlog" => Some(Stage::Backlog),
@@ -451,6 +433,16 @@ fn project_stage_for_view(
"merge_failure" => Some(Stage::MergeFailure {
reason: String::new(),
}),
"merge_failure_final" => Some(Stage::MergeFailureFinal {
reason: String::new(),
}),
"frozen" => Some(Stage::Frozen {
resume_to: resume_target(),
}),
"review_hold" => Some(Stage::ReviewHold {
resume_to: resume_target(),
reason: String::new(),
}),
"done" => {
let merged_at = merged_at
.map(|ts| {
@@ -462,19 +454,10 @@ fn project_stage_for_view(
merge_commit: GitSha("legacy".to_string()),
})
}
"archived" => {
let reason = if blocked.unwrap_or(false) {
ArchiveReason::Blocked {
reason: "migrated from legacy blocked field".to_string(),
}
} else {
ArchiveReason::Completed
};
Some(Stage::Archived {
archived_at: Utc::now(),
reason,
})
}
"archived" => Some(Stage::Archived {
archived_at: Utc::now(),
reason: ArchiveReason::Completed,
}),
_ => None,
}
}
@@ -589,7 +572,6 @@ mod tests {
assert_eq!(view.name.as_deref(), Some("View Test"));
assert_eq!(view.agent.as_deref(), Some("coder-1"));
assert_eq!(view.retry_count, Some(2));
assert_eq!(view.blocked, Some(true));
assert_eq!(view.depends_on, Some(vec![10, 20]));
}
@@ -647,7 +629,6 @@ mod tests {
None,
None,
None,
None,
);
// The story is live on this node.
@@ -718,7 +699,6 @@ mod tests {
None,
None,
None,
None,
);
assert!(
read_item(story_id).is_none(),
-2
View File
@@ -119,7 +119,6 @@ async fn subscribe_receives_stage_transition_events() {
None,
None,
None,
None,
);
let evt: CrdtEvent = rx.try_recv().expect("expected CrdtEvent on insert");
@@ -138,7 +137,6 @@ async fn subscribe_receives_stage_transition_events() {
None,
None,
None,
None,
);
let evt: CrdtEvent = rx.try_recv().expect("expected CrdtEvent on stage change");
+12 -56
View File
@@ -49,6 +49,13 @@ pub struct PipelineDoc {
}
/// CRDT sub-document representing a single pipeline work item with LWW fields for stage, agent, etc.
///
/// Story 945: the `blocked`, `review_hold`, `frozen`, and
/// `mergemaster_attempted` boolean flags have been deleted. Each of the
/// states those flags encoded is represented as a `Stage` variant, so the
/// stage register is the single source of truth. A sibling `resume_to`
/// register stores the `Stage::Frozen { resume_to }` / `Stage::ReviewHold
/// { resume_to, .. }` resume target as a clean wire-form stage name.
#[add_crdt_fields]
#[derive(Clone, CrdtNode, Debug)]
pub struct PipelineItemCrdt {
@@ -57,7 +64,6 @@ pub struct PipelineItemCrdt {
pub name: LwwRegisterCrdt<String>,
pub agent: LwwRegisterCrdt<String>,
pub retry_count: LwwRegisterCrdt<f64>,
pub blocked: LwwRegisterCrdt<bool>,
pub depends_on: LwwRegisterCrdt<String>,
/// Node ID (hex-encoded Ed25519 pubkey) of the node that claimed this item.
/// Used for distributed work claiming — the LWW register resolves conflicts
@@ -74,18 +80,6 @@ pub struct PipelineItemCrdt {
/// QA mode override for this item: `"server"`, `"agent"`, or `"human"`.
/// Empty string means "use the project default".
pub qa_mode: LwwRegisterCrdt<String>,
/// Set to `true` when the auto-assigner has already spawned a mergemaster
/// session for a content-conflict failure. Prevents repeated auto-spawns
/// across restarts. Written as `false` (not removed) when cleared.
pub mergemaster_attempted: LwwRegisterCrdt<bool>,
/// Set to `true` when a story is held for human review at a pipeline stage
/// boundary (e.g. spikes after QA passes; human-QA items after gates pass).
/// The auto-assigner skips items with this flag so a human can inspect the
/// state before the pipeline continues. Written as `false` (not removed)
/// when explicitly cleared (e.g. by `tool_approve_qa`). Sub-story 932 of
/// the 929 CRDT-only migration; replaces the legacy `review_hold: true`
/// YAML front-matter field.
pub review_hold: LwwRegisterCrdt<bool>,
/// Item type: `"story"`, `"bug"`, `"spike"`, `"refactor"`, or `"epic"`.
/// Empty string means "infer from story_id slug or default to story".
/// Sub-story 933; replaces the legacy `type:` YAML front-matter field.
@@ -94,12 +88,11 @@ pub struct PipelineItemCrdt {
/// member of any epic. Sub-story 933; replaces the legacy `epic:` YAML
/// front-matter field that linked member work items to their epic.
pub epic: LwwRegisterCrdt<String>,
/// Set to `true` when a story is frozen. Frozen stories stay at their
/// current `Stage` but are skipped by the auto-assigner until explicitly
/// unfrozen. Orthogonal to `Stage` (story 934, stage 4); replaces the
/// pre-934 `Stage::Frozen { resume_to }` variant whose resume payload was
/// just "the stage you were in when you froze".
pub frozen: LwwRegisterCrdt<bool>,
/// Story 945: resume-target stage for `Stage::Frozen` and
/// `Stage::ReviewHold` variants. Stored as a clean wire-form stage
/// name (e.g. `"coding"`, `"qa"`). Empty string means "no resume target
/// stored" (defaults to `Coding` on read).
pub resume_to: LwwRegisterCrdt<String>,
}
/// CRDT node that holds a single peer's presence entry.
@@ -140,7 +133,6 @@ pub struct WorkItem {
pub(super) name: Option<String>,
pub(super) agent: Option<String>,
pub(super) retry_count: Option<i64>,
pub(super) blocked: Option<bool>,
pub(super) depends_on: Option<Vec<u32>>,
/// Node ID of the node that claimed this item (hex-encoded Ed25519 pubkey).
pub(super) claimed_by: Option<String>,
@@ -150,16 +142,10 @@ pub struct WorkItem {
pub(super) merged_at: Option<f64>,
/// QA mode override: `"server"`, `"agent"`, or `"human"`.
pub(super) qa_mode: Option<String>,
/// Whether the auto-assigner has already attempted a mergemaster spawn.
pub(super) mergemaster_attempted: Option<bool>,
/// Whether the item is held for human review (sub-story 932).
pub(super) review_hold: Option<bool>,
/// Item type (sub-story 933): `"story"`, `"bug"`, `"spike"`, `"refactor"`, `"epic"`.
pub(super) item_type: Option<String>,
/// Epic ID this item belongs to, or `None` (sub-story 933).
pub(super) epic: Option<String>,
/// Whether the item is frozen (story 934, stage 4). Orthogonal to `Stage`.
pub(super) frozen: Option<bool>,
}
impl WorkItem {
@@ -183,11 +169,6 @@ impl WorkItem {
self.agent.as_deref()
}
/// Whether the item is blocked. Returns `false` when the register is unset.
pub fn blocked(&self) -> bool {
self.blocked.unwrap_or(false)
}
/// Retry counter. Returns `0` when the register is unset.
pub fn retry_count(&self) -> u32 {
self.retry_count.unwrap_or(0).max(0) as u32
@@ -218,16 +199,6 @@ impl WorkItem {
self.qa_mode.as_deref()
}
/// Whether a mergemaster spawn has already been attempted. Returns `false` when unset.
pub fn mergemaster_attempted(&self) -> bool {
self.mergemaster_attempted.unwrap_or(false)
}
/// Whether the item is held for human review. Returns `false` when unset.
pub fn review_hold(&self) -> bool {
self.review_hold.unwrap_or(false)
}
/// Item type (`"story"`, `"bug"`, `"spike"`, `"refactor"`, `"epic"`), or
/// `None` when the register is unset.
pub fn item_type(&self) -> Option<&str> {
@@ -239,13 +210,6 @@ impl WorkItem {
self.epic.as_deref()
}
/// Whether the item is frozen (story 934, stage 4). Returns `false` when
/// the register is unset. Orthogonal to [`Self::stage`]: a frozen story
/// stays at its current stage but is skipped by the auto-assigner.
pub fn frozen(&self) -> bool {
self.frozen.unwrap_or(false)
}
/// Construct a `WorkItem` for use in tests outside `crdt_state::*`.
///
/// Within `crdt_state` use a struct literal directly (fields are `pub(super)`).
@@ -258,17 +222,13 @@ impl WorkItem {
name: Option<String>,
agent: Option<String>,
retry_count: Option<i64>,
blocked: Option<bool>,
depends_on: Option<Vec<u32>>,
claimed_by: Option<String>,
claimed_at: Option<f64>,
merged_at: Option<f64>,
qa_mode: Option<String>,
mergemaster_attempted: Option<bool>,
review_hold: Option<bool>,
item_type: Option<String>,
epic: Option<String>,
frozen: Option<bool>,
) -> Self {
Self {
story_id: story_id.into(),
@@ -276,17 +236,13 @@ impl WorkItem {
name,
agent,
retry_count,
blocked,
depends_on,
claimed_by,
claimed_at,
merged_at,
qa_mode,
mergemaster_attempted,
review_hold,
item_type,
epic,
frozen,
}
}
}
+10 -87
View File
@@ -82,15 +82,15 @@ pub fn set_epic(story_id: &str, epic_id: Option<&str>) -> bool {
true
}
/// Set the `review_hold` CRDT flag for a pipeline item (sub-story 932).
/// Set the `resume_to` CRDT register for a pipeline item (story 945).
///
/// `true` marks the item as held for human review at a pipeline-stage boundary;
/// the auto-assigner skips items with this flag. `false` clears the hold and
/// is written explicitly (the register is not removed) so the cleared state
/// survives CRDT replay correctly.
/// This sibling register stores the wire-form stage name (`"coding"`, `"qa"`,
/// etc.) that a `Stage::Frozen` or `Stage::ReviewHold` variant should resume
/// to. Empty string means "no resume target stored" (defaults to `Coding`
/// on read).
///
/// Returns `true` if the item was found and the op was applied, `false` otherwise.
pub fn set_review_hold(story_id: &str, value: bool) -> bool {
pub fn set_resume_to(story_id: &str, stage: &Stage) -> bool {
let Some(state_mutex) = get_crdt() else {
return false;
};
@@ -100,54 +100,8 @@ pub fn set_review_hold(story_id: &str, value: bool) -> bool {
let Some(&idx) = state.index.get(story_id) else {
return false;
};
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].review_hold.set(value));
true
}
/// Set the `frozen` CRDT flag for a pipeline item (story 934, stage 4).
///
/// `true` freezes the story at its current `Stage` — the auto-assigner skips
/// it but the stage register is untouched. `false` unfreezes; the story
/// remains at its current stage and resumes auto-assignment. Both writes
/// are explicit (not removals) so the cleared state survives CRDT replay.
///
/// Returns `true` if the item was found and the op was applied, `false` otherwise.
pub fn set_frozen(story_id: &str, value: bool) -> bool {
let Some(state_mutex) = get_crdt() else {
return false;
};
let Ok(mut state) = state_mutex.lock() else {
return false;
};
let Some(&idx) = state.index.get(story_id) else {
return false;
};
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].frozen.set(value));
true
}
/// Set the `mergemaster_attempted` CRDT flag for a pipeline item.
///
/// Passing `true` records that a mergemaster session has been spawned for this
/// item, preventing repeated auto-spawns across restarts.
/// Passing `false` explicitly writes `false` (does not remove the register) so
/// the cleared state is distinguishable from an unset register and survives
/// CRDT replay correctly.
///
/// Returns `true` if the item was found and the op was applied, `false` otherwise.
pub fn set_mergemaster_attempted(story_id: &str, value: bool) -> bool {
let Some(state_mutex) = get_crdt() else {
return false;
};
let Ok(mut state) = state_mutex.lock() else {
return false;
};
let Some(&idx) = state.index.get(story_id) else {
return false;
};
apply_and_persist(&mut state, |s| {
s.crdt.doc.items[idx].mergemaster_attempted.set(value)
});
let value = stage_dir_name(stage).to_string();
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].resume_to.set(value));
true
}
@@ -244,7 +198,6 @@ pub fn write_item(
name: Option<&str>,
agent: Option<&str>,
retry_count: Option<i64>,
blocked: Option<bool>,
depends_on: Option<&str>,
claimed_by: Option<&str>,
claimed_at: Option<f64>,
@@ -292,9 +245,6 @@ pub fn write_item(
s.crdt.doc.items[idx].retry_count.set(rc as f64)
});
}
if let Some(b) = blocked {
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].blocked.set(b));
}
if let Some(d) = depends_on {
apply_and_persist(&mut state, |s| {
s.crdt.doc.items[idx].depends_on.set(d.to_string())
@@ -335,17 +285,14 @@ pub fn write_item(
"name": name.unwrap_or(""),
"agent": agent.unwrap_or(""),
"retry_count": retry_count.unwrap_or(0) as f64,
"blocked": blocked.unwrap_or(false),
"depends_on": depends_on.unwrap_or(""),
"claimed_by": claimed_by.unwrap_or(""),
"claimed_at": claimed_at.unwrap_or(0.0),
"merged_at": merged_at.unwrap_or(0.0),
"qa_mode": "",
"mergemaster_attempted": false,
"review_hold": false,
"item_type": "",
"epic": "",
"frozen": false,
"resume_to": "",
})
.into();
@@ -366,17 +313,14 @@ pub fn write_item(
item.name.advance_seq(floor);
item.agent.advance_seq(floor);
item.retry_count.advance_seq(floor);
item.blocked.advance_seq(floor);
item.depends_on.advance_seq(floor);
item.claimed_by.advance_seq(floor);
item.claimed_at.advance_seq(floor);
item.merged_at.advance_seq(floor);
item.qa_mode.advance_seq(floor);
item.mergemaster_attempted.advance_seq(floor);
item.review_hold.advance_seq(floor);
item.item_type.advance_seq(floor);
item.epic.advance_seq(floor);
item.frozen.advance_seq(floor);
item.resume_to.advance_seq(floor);
}
// Broadcast a CrdtEvent for the new item.
@@ -404,7 +348,6 @@ pub fn write_item_str(
name: Option<&str>,
agent: Option<&str>,
retry_count: Option<i64>,
blocked: Option<bool>,
depends_on: Option<&str>,
claimed_by: Option<&str>,
claimed_at: Option<f64>,
@@ -436,7 +379,6 @@ pub fn write_item_str(
name,
agent,
retry_count,
blocked,
depends_on,
claimed_by,
claimed_at,
@@ -463,25 +405,6 @@ pub fn set_retry_count(story_id: &str, count: i64) {
}
}
/// Set the `blocked` register on a story to the given value.
///
/// Pure metadata operation — the item's stage is not changed.
/// Use this alongside a state-machine transition out of `Blocked` /
/// `MergeFailure` to keep the legacy `blocked` register in sync with the
/// typed stage post-865 (where YAML side-effects no longer clear the
/// register on their own).
pub fn set_blocked(story_id: &str, blocked: bool) {
let Some(state_mutex) = get_crdt() else {
return;
};
let Ok(mut state) = state_mutex.lock() else {
return;
};
if let Some(&idx) = state.index.get(story_id) {
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].blocked.set(blocked));
}
}
/// Increment `retry_count` by 1 and return the new value.
///
/// Pure metadata operation — the item's stage is not changed.
+34 -22
View File
@@ -212,8 +212,8 @@ fn legacy_stage_to_clean(s: &str) -> Option<&'static str> {
/// vocabulary (`"coding"`, `"merge"`, etc.).
///
/// Items that were at `"7_frozen"` additionally get the new `frozen` flag set
/// — the stage variant `Frozen` was dropped in story 934 stage 4 in favour of
/// an orthogonal CRDT register.
/// Story 945: the `Frozen` variant returns as `Stage::Frozen { resume_to }`,
/// replacing the orthogonal CRDT register that briefly existed in 934 stage 4.
///
/// One-time startup migration: items that have transitioned at least once
/// since story 934 stage 1 (which made writes emit clean form) are no-ops.
@@ -222,8 +222,10 @@ pub fn migrate_legacy_stage_strings() {
return;
};
// First pass: collect (index, clean_stage, set_frozen) for items that
// still carry legacy stage strings.
// First pass: collect (index, clean_stage, was_frozen) for items that
// still carry legacy stage strings. `was_frozen` triggers writing
// `resume_to = "backlog"` so the post-945 typed projection reads back as
// `Stage::Frozen { resume_to: Stage::Backlog }`.
let migrations: Vec<(usize, &'static str, bool)> = {
let Ok(state) = state_mutex.lock() else {
return;
@@ -239,7 +241,11 @@ pub fn migrate_legacy_stage_strings() {
};
let clean = legacy_stage_to_clean(&current)?;
let was_frozen = current == "7_frozen";
Some((idx, clean, was_frozen))
// For legacy frozen items, store the post-945 stage as
// "frozen" rather than "backlog" so the typed projection
// produces `Stage::Frozen` again.
let stage_out = if was_frozen { "frozen" } else { clean };
Some((idx, stage_out, was_frozen))
})
.collect()
};
@@ -258,12 +264,14 @@ pub fn migrate_legacy_stage_strings() {
s.crdt.doc.items[idx].stage.set(clean.to_string())
});
if was_frozen {
apply_and_persist(&mut state, |s| s.crdt.doc.items[idx].frozen.set(true));
apply_and_persist(&mut state, |s| {
s.crdt.doc.items[idx].resume_to.set("backlog".to_string())
});
}
}
slog!(
"[crdt] Migrated {count} legacy stage strings to clean wire form \
({frozen_count} of which were '7_frozen' backlog + frozen=true)"
({frozen_count} of which were '7_frozen' frozen + resume_to=backlog)"
);
}
@@ -292,7 +300,6 @@ mod stage_migration_tests {
None,
None,
None,
None,
);
// Then overwrite the stage register with the raw legacy string,
// bypassing `db::normalise_stage_str` / `write_item_str`'s mapping.
@@ -353,27 +360,33 @@ mod stage_migration_tests {
}
#[test]
fn migrate_collapses_7_frozen_to_backlog_and_sets_frozen_flag() {
fn migrate_promotes_7_frozen_to_typed_frozen_variant() {
init_for_test();
let story_id = "9510_legacy_frozen";
seed_with_raw_stage(story_id, "7_frozen");
// Sanity: before migration, the frozen flag is false.
let before = read_item(story_id).expect("seeded item exists");
assert!(!before.frozen(), "frozen flag should start false");
// Sanity: before migration, the projection's legacy fallback maps
// raw `"7_frozen"` → `Stage::Backlog` (frozen state is lost without the
// migration's resume_to write).
let before = read_item(story_id).expect("legacy 7_frozen should still project");
assert!(
matches!(before.stage(), crate::pipeline_state::Stage::Backlog),
"raw 7_frozen should fall back to Backlog before migration; got {:?}",
before.stage()
);
migrate_legacy_stage_strings();
let after = read_item(story_id).expect("item must still exist after migration");
assert!(
matches!(after.stage(), crate::pipeline_state::Stage::Backlog),
"7_frozen should collapse to Backlog: got {:?}",
after.stage()
);
assert!(
after.frozen(),
"frozen flag should be set after 7_frozen migration"
);
match after.stage() {
crate::pipeline_state::Stage::Frozen { resume_to } => {
assert!(
matches!(**resume_to, crate::pipeline_state::Stage::Backlog),
"resume_to should default to Backlog for migrated 7_frozen items"
);
}
other => panic!("7_frozen should migrate to Stage::Frozen; got {other:?}"),
}
}
#[test]
@@ -390,7 +403,6 @@ mod stage_migration_tests {
None,
None,
None,
None,
);
seed_with_raw_stage("9521_needs_migration", "2_current");
+2 -2
View File
@@ -10,8 +10,8 @@ mod migrations;
mod tests;
pub use item::{
bump_retry_count, set_agent, set_blocked, set_depends_on, set_epic, set_frozen, set_item_type,
set_mergemaster_attempted, set_name, set_qa_mode, set_retry_count, set_review_hold, write_item,
bump_retry_count, set_agent, set_depends_on, set_epic, set_item_type, set_name, set_qa_mode,
set_resume_to, set_retry_count, write_item,
};
#[cfg(test)]
+1 -82
View File
@@ -100,7 +100,6 @@ fn migrate_story_ids_to_numeric_rewrites_slug_ids() {
None,
None,
None,
None,
);
let result = migrate_story_ids_to_numeric();
@@ -133,7 +132,6 @@ fn migrate_story_ids_to_numeric_is_idempotent() {
None,
None,
None,
None,
);
// First call — nothing to migrate.
@@ -163,20 +161,8 @@ fn migrate_story_ids_to_numeric_skips_conflict() {
None,
None,
None,
None,
);
write_item_str(
"44",
"2_current",
None,
None,
None,
None,
None,
None,
None,
None,
);
write_item_str("44", "2_current", None, None, None, None, None, None, None);
let result = migrate_story_ids_to_numeric();
// The slug entry must NOT be migrated because "44" is already occupied.
@@ -210,7 +196,6 @@ fn migrate_story_ids_to_numeric_preserves_stage_and_name() {
None,
None,
None,
None,
);
migrate_story_ids_to_numeric();
@@ -236,7 +221,6 @@ fn migrate_names_from_slugs_fills_empty_names() {
None,
None,
None,
None,
);
// Before migration the name should be empty.
@@ -271,7 +255,6 @@ fn migrate_names_from_slugs_leaves_existing_names_unchanged() {
None,
None,
None,
None,
);
migrate_names_from_slugs();
@@ -309,7 +292,6 @@ fn set_depends_on_round_trip_and_clear() {
None,
None,
None,
None,
);
// Set depends_on to [837] and verify CRDT register holds the list.
@@ -349,62 +331,6 @@ fn set_depends_on_returns_false_for_unknown_story() {
);
}
// ── set_mergemaster_attempted regression tests ───────────────────────────
#[test]
fn set_mergemaster_attempted_true_then_false_flips_register() {
init_for_test();
write_item_str(
"873_story_mergemaster_flip",
"4_merge",
None,
None,
None,
None,
None,
None,
None,
None,
);
// Set true — register must read back as true.
let ok = set_mergemaster_attempted("873_story_mergemaster_flip", true);
assert!(
ok,
"set_mergemaster_attempted should return true for known item"
);
let view = read_item("873_story_mergemaster_flip").unwrap();
assert_eq!(
view.mergemaster_attempted,
Some(true),
"CRDT register should hold true after setting true"
);
// Set false — register must flip back to false (not unset).
let ok = set_mergemaster_attempted("873_story_mergemaster_flip", false);
assert!(
ok,
"set_mergemaster_attempted(false) should return true for known item"
);
let view = read_item("873_story_mergemaster_flip").unwrap();
assert_eq!(
view.mergemaster_attempted,
Some(false),
"CRDT register should hold false after explicit clear"
);
}
#[test]
fn set_mergemaster_attempted_returns_false_for_unknown_story() {
init_for_test();
let ok = set_mergemaster_attempted("nonexistent_story_mm", true);
assert!(
!ok,
"set_mergemaster_attempted should return false for unknown story_id"
);
}
// ── set_agent tests ──────────────────────────────────────────────────────
#[test]
@@ -421,7 +347,6 @@ fn set_agent_some_writes_name() {
None,
None,
None,
None,
);
let found = set_agent("871_story_set_agent_write", Some("coder-1"));
@@ -449,7 +374,6 @@ fn set_agent_none_clears_register() {
None,
None,
None,
None,
);
// Confirm agent is set.
@@ -495,7 +419,6 @@ fn set_qa_mode_round_trip_server_then_human() {
None,
None,
None,
None,
);
// Set qa=server via typed path and assert CRDT register reflects it.
@@ -551,7 +474,6 @@ fn bump_retry_count_increments_by_one() {
None,
None,
None,
None,
);
let v1 = bump_retry_count("9001_story_bump_test");
@@ -581,7 +503,6 @@ fn set_retry_count_resets_to_zero() {
None,
None,
None,
None,
);
set_retry_count("9002_story_set_test", 0);
@@ -765,7 +686,6 @@ async fn tombstone_survives_concurrent_writes() {
None,
None,
None,
None,
);
assert!(
read_item(story_id).is_some(),
@@ -787,7 +707,6 @@ async fn tombstone_survives_concurrent_writes() {
None,
None,
None,
None,
);
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
}