huskies: merge 982

This commit is contained in:
dave
2026-05-13 15:30:03 +00:00
parent e6d051d016
commit 91fbad568a
15 changed files with 357 additions and 117 deletions
+22 -19
View File
@@ -11,8 +11,8 @@ use std::path::Path;
use std::process::Command;
use crate::pipeline_state::{
ApplyError, ArchiveReason, BranchName, GitSha, PipelineEvent, Stage, TransitionFired,
apply_transition, stage_label,
ApplyError, ArchiveReason, BranchName, GitSha, MergeFailureKind, PipelineEvent, Stage,
TransitionFired, apply_transition, stage_label,
};
use crate::slog;
@@ -246,10 +246,12 @@ pub fn transition_to_blocked(story_id: &str, reason: &str) -> Result<(), String>
/// Transition a story from `Stage::Merge` (or `Stage::MergeFailure`) to
/// `Stage::MergeFailure` via the state machine.
///
/// Builds a `PipelineEvent::MergeFailed { reason }`, validates the transition,
/// writes the resulting `Stage::MergeFailure` to the CRDT, and persists the
/// reason to the typed `MergeJob.error` CRDT register so it survives server
/// restarts (story 929: the legacy YAML write of `merge_failure: "..."` is gone).
/// Builds a `PipelineEvent::MergeFailed { kind }`, validates the transition,
/// writes the resulting `Stage::MergeFailure` to the CRDT, and persists two
/// display-only copies for status tools:
/// - `ContentKey::GateOutput`: the kind's gate-output string so the CRDT
/// projection layer can reconstruct the kind after a server restart.
/// - `MergeJob.error`: human-readable description for status renderers.
///
/// When the story is already in `MergeFailure`, this is a silent self-loop: the
/// returned `TransitionFired::before` will be `Stage::MergeFailure`. Callers
@@ -258,26 +260,27 @@ pub fn transition_to_blocked(story_id: &str, reason: &str) -> Result<(), String>
/// Returns `Err` on `TransitionError` — callers must NOT fall back to direct register writes.
pub fn transition_to_merge_failure(
story_id: &str,
reason: &str,
kind: MergeFailureKind,
) -> Result<TransitionFired, String> {
let fired = apply_transition(
story_id,
PipelineEvent::MergeFailed {
reason: reason.to_string(),
},
None,
)
.map_err(|e| e.to_string())?;
let display = kind.display_reason();
let gate_output = kind.to_gate_output();
// Persist the failure reason on the MergeJob CRDT entry so display tools
// (status_tools, chat status renderer, pipeline.rs::load_pipeline_state)
// can surface it without re-parsing YAML.
let fired = apply_transition(story_id, PipelineEvent::MergeFailed { kind }, None)
.map_err(|e| e.to_string())?;
// Persist gate-output string so the CRDT projection can reconstruct the
// MergeFailureKind on server restart (display-only; scheduling uses the
// typed kind from the Stage variant).
crate::db::write_content(crate::db::ContentKey::GateOutput(story_id), &gate_output);
// Persist human-readable description on the MergeJob CRDT entry so display
// tools (status renderer, pipeline state view) can surface it.
crate::crdt_state::write_merge_job(
story_id,
"failed",
chrono::Utc::now().timestamp() as f64,
None,
Some(reason),
Some(&display),
);
Ok(fired)