pub struct ShipEvidence {
pub phase: PhaseId,
pub shipped: bool,
pub workflow_finished_seen: bool,
pub finished_reason: Option<String>,
pub stage: Option<Stage>,
pub state_present: bool,
pub feature_branch_exists: bool,
pub merged_into_develop: bool,
pub has_remote: bool,
}Expand description
DevFlow’s own structural record of whether a phase has shipped.
Every field degrades to its safest value rather than erroring —
collect returns a value, never a Result — because an oracle that
can fail is an oracle a reviewer will learn to skip.
Fields§
§phase: PhaseIdThe phase this evidence was collected for.
shipped: boolThe strict shipped predicate: whether the terminal-only
workflow_shipped event has been emitted for this phase.
This is the load-bearing field of the whole module — read this
comment before touching it. An earlier revision of the plan that
produced this module defined the shipped predicate as “whether
workflow_finished has been emitted”, asserted three separate times
that it was the only site emitting that event, and was proven wrong
by a cross-AI review before landing: workflow_finished is emitted at
TWO sites. The first is real Ship finalization
(pipeline_gate::finish_workflow_with_gate_timeout), guarded by the
entire hooks_after_ship batch succeeding. The second is
transition’s devflow start --until <stage> clean-stop branch
(crates/devflow-cli/src/pipeline_gate.rs, the
state.stop_until == Some(from) arm near the top of transition),
which emits workflow_finished with {"reason": "stopped_at", …}
and returns BEFORE any checkout hook, before state.stage = to,
before the "transition" event, and before launch_stage — nothing
resembling a Ship has run. Had workflow_finished stayed the
predicate, a phase halted after one stage would read as shipped: a
false green inside the very oracle built to eliminate false greens.
The fix is structural, not a payload convention: a distinct,
terminal-only workflow_shipped event, emitted at exactly one site,
strictly after the hooks_after_ship batch’s success loop breaks and
strictly before the (unchanged) workflow_finished emission there.
shipped reads ONLY this event — it must never fall back to
filtering workflow_finished on reason != "stopped_at", because
that is a payload-discipline convention a future third emitter could
silently violate (the real finalization payload is literally Null
today, so “absence of a reason key” is exactly the fingerprint a
careless new emitter would also have).
Git ancestry is also deliberately not the predicate: merged_into_develop
is shape-sensitive (a squash merge does not preserve the ancestry
is_merged_into_develop checks), and it goes false for every
successfully shipped phase once BranchCleanup — the hook that runs
immediately after Merge in the very same hooks_after_ship batch —
deletes the feature branch the ancestry check depends on. Git facts are
reported below as corroboration only and never gate shipped.
Phases that finalized before this event existed have no
workflow_shipped line in their event log, so this reports false
for them. That fail-closed direction is deliberate: an oracle that
under-claims is safe, one that over-claims is the defect class this
module exists to remove.
workflow_finished_seen: boolCorroboration only: whether the older workflow_finished event has
ever been emitted for this phase. Never consulted by shipped.
finished_reason: Option<String>The reason field from the last workflow_finished event, if any
event exists and it carried one. A value of "stopped_at" is what
distinguishes a --until halt from a real finalization — surfaced
here so the ambiguity is legible in the oracle’s own output instead
of hidden inside this module’s implementation.
stage: Option<Stage>The phase’s current stage, read from its persisted state file, or
None when no state file exists (state is cleared once a phase
finalizes — see finish_workflow_with_gate_timeout’s
workflow::clear_state call).
state_present: boolWhether a state file exists at all for this phase.
feature_branch_exists: boolWhether the phase’s feature/phase-NN branch currently exists.
merged_into_develop: boolWhether that branch (if it exists) is an ancestor of develop.
Corroboration only — see shipped’s doc comment for why this is not
the predicate.
has_remote: boolWhether the repository has at least one configured remote.
Trait Implementations§
Source§impl Clone for ShipEvidence
impl Clone for ShipEvidence
Source§fn clone(&self) -> ShipEvidence
fn clone(&self) -> ShipEvidence
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ShipEvidence
impl Debug for ShipEvidence
impl Eq for ShipEvidence
Source§impl PartialEq for ShipEvidence
impl PartialEq for ShipEvidence
Source§impl Serialize for ShipEvidence
impl Serialize for ShipEvidence
impl StructuralPartialEq for ShipEvidence
Auto Trait Implementations§
impl Freeze for ShipEvidence
impl RefUnwindSafe for ShipEvidence
impl Send for ShipEvidence
impl Sync for ShipEvidence
impl Unpin for ShipEvidence
impl UnsafeUnpin for ShipEvidence
impl UnwindSafe for ShipEvidence
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.