Skip to main content

ShipEvidence

Struct ShipEvidence 

Source
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: PhaseId

The phase this evidence was collected for.

§shipped: bool

The 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: bool

Corroboration 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: bool

Whether a state file exists at all for this phase.

§feature_branch_exists: bool

Whether the phase’s feature/phase-NN branch currently exists.

§merged_into_develop: bool

Whether 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: bool

Whether the repository has at least one configured remote.

Trait Implementations§

Source§

impl Clone for ShipEvidence

Source§

fn clone(&self) -> ShipEvidence

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ShipEvidence

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for ShipEvidence

Source§

impl PartialEq for ShipEvidence

Source§

fn eq(&self, other: &ShipEvidence) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for ShipEvidence

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ShipEvidence

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more