Skip to content

Commit 5c40d4e

Browse files
bellmanbellman
authored andcommitted
omx(team): auto-checkpoint worker-3 [4]
1 parent 5625ba5 commit 5c40d4e

3 files changed

Lines changed: 304 additions & 9 deletions

File tree

rust/crates/runtime/src/green_contract.rs

Lines changed: 253 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,33 @@ impl std::fmt::Display for GreenLevel {
3030
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3131
pub struct GreenContract {
3232
pub required_level: GreenLevel,
33+
pub require_test_command_provenance: bool,
34+
pub require_base_branch_freshness: bool,
35+
pub require_recovery_attempt_context: bool,
36+
pub block_known_flakes: bool,
3337
}
3438

3539
impl GreenContract {
3640
#[must_use]
3741
pub fn new(required_level: GreenLevel) -> Self {
38-
Self { required_level }
42+
Self {
43+
required_level,
44+
require_test_command_provenance: false,
45+
require_base_branch_freshness: false,
46+
require_recovery_attempt_context: false,
47+
block_known_flakes: false,
48+
}
49+
}
50+
51+
#[must_use]
52+
pub fn merge_ready(required_level: GreenLevel) -> Self {
53+
Self {
54+
required_level,
55+
require_test_command_provenance: true,
56+
require_base_branch_freshness: true,
57+
require_recovery_attempt_context: true,
58+
block_known_flakes: true,
59+
}
3960
}
4061

4162
#[must_use]
@@ -52,12 +73,164 @@ impl GreenContract {
5273
}
5374
}
5475

76+
#[must_use]
77+
pub fn evaluate_evidence(&self, evidence: &GreenEvidence) -> GreenEvidenceOutcome {
78+
let mut missing = Vec::new();
79+
let mut blocking_flakes = Vec::new();
80+
81+
if evidence.observed_level < self.required_level {
82+
missing.push(GreenContractRequirement::RequiredLevel);
83+
}
84+
85+
if self.require_test_command_provenance && !evidence.has_passing_test_command() {
86+
missing.push(GreenContractRequirement::TestCommandProvenance);
87+
}
88+
89+
if self.require_base_branch_freshness && !evidence.base_branch_fresh {
90+
missing.push(GreenContractRequirement::BaseBranchFreshness);
91+
}
92+
93+
if self.require_recovery_attempt_context && !evidence.recovery_attempt_context_recorded {
94+
missing.push(GreenContractRequirement::RecoveryAttemptContext);
95+
}
96+
97+
if self.block_known_flakes {
98+
blocking_flakes = evidence
99+
.known_flakes
100+
.iter()
101+
.filter(|flake| flake.blocks_green)
102+
.cloned()
103+
.collect();
104+
}
105+
106+
if missing.is_empty() && blocking_flakes.is_empty() {
107+
GreenEvidenceOutcome::Satisfied {
108+
required_level: self.required_level,
109+
observed_level: evidence.observed_level,
110+
}
111+
} else {
112+
GreenEvidenceOutcome::Unsatisfied {
113+
required_level: self.required_level,
114+
observed_level: evidence.observed_level,
115+
missing,
116+
blocking_flakes,
117+
}
118+
}
119+
}
120+
55121
#[must_use]
56122
pub fn is_satisfied_by(self, observed_level: GreenLevel) -> bool {
57123
observed_level >= self.required_level
58124
}
59125
}
60126

127+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128+
pub struct GreenEvidence {
129+
pub observed_level: GreenLevel,
130+
pub test_commands: Vec<TestCommandProvenance>,
131+
pub base_branch_fresh: bool,
132+
pub known_flakes: Vec<KnownFlake>,
133+
pub recovery_attempt_context_recorded: bool,
134+
}
135+
136+
impl GreenEvidence {
137+
#[must_use]
138+
pub fn new(observed_level: GreenLevel) -> Self {
139+
Self {
140+
observed_level,
141+
test_commands: Vec::new(),
142+
base_branch_fresh: false,
143+
known_flakes: Vec::new(),
144+
recovery_attempt_context_recorded: false,
145+
}
146+
}
147+
148+
#[must_use]
149+
pub fn with_test_command(mut self, command: impl Into<String>, exit_code: i32) -> Self {
150+
self.test_commands.push(TestCommandProvenance {
151+
command: command.into(),
152+
exit_code,
153+
});
154+
self
155+
}
156+
157+
#[must_use]
158+
pub fn with_base_branch_fresh(mut self, is_fresh: bool) -> Self {
159+
self.base_branch_fresh = is_fresh;
160+
self
161+
}
162+
163+
#[must_use]
164+
pub fn with_known_flake(mut self, test_name: impl Into<String>, blocks_green: bool) -> Self {
165+
self.known_flakes.push(KnownFlake {
166+
test_name: test_name.into(),
167+
blocks_green,
168+
});
169+
self
170+
}
171+
172+
#[must_use]
173+
pub fn with_recovery_attempt_context(mut self, recorded: bool) -> Self {
174+
self.recovery_attempt_context_recorded = recorded;
175+
self
176+
}
177+
178+
#[must_use]
179+
pub fn has_passing_test_command(&self) -> bool {
180+
self.test_commands.iter().any(TestCommandProvenance::passed)
181+
}
182+
}
183+
184+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185+
pub struct TestCommandProvenance {
186+
pub command: String,
187+
pub exit_code: i32,
188+
}
189+
190+
impl TestCommandProvenance {
191+
#[must_use]
192+
pub fn passed(&self) -> bool {
193+
self.exit_code == 0 && !self.command.trim().is_empty()
194+
}
195+
}
196+
197+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198+
pub struct KnownFlake {
199+
pub test_name: String,
200+
pub blocks_green: bool,
201+
}
202+
203+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
204+
#[serde(rename_all = "snake_case")]
205+
pub enum GreenContractRequirement {
206+
RequiredLevel,
207+
TestCommandProvenance,
208+
BaseBranchFreshness,
209+
RecoveryAttemptContext,
210+
}
211+
212+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213+
#[serde(tag = "outcome", rename_all = "snake_case")]
214+
pub enum GreenEvidenceOutcome {
215+
Satisfied {
216+
required_level: GreenLevel,
217+
observed_level: GreenLevel,
218+
},
219+
Unsatisfied {
220+
required_level: GreenLevel,
221+
observed_level: GreenLevel,
222+
missing: Vec<GreenContractRequirement>,
223+
blocking_flakes: Vec<KnownFlake>,
224+
},
225+
}
226+
227+
impl GreenEvidenceOutcome {
228+
#[must_use]
229+
pub fn is_satisfied(&self) -> bool {
230+
matches!(self, Self::Satisfied { .. })
231+
}
232+
}
233+
61234
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62235
#[serde(tag = "outcome", rename_all = "snake_case")]
63236
pub enum GreenContractOutcome {
@@ -149,4 +322,83 @@ mod tests {
149322
}
150323
);
151324
}
325+
#[test]
326+
fn merge_ready_contract_requires_provenance_beyond_test_level() {
327+
// given
328+
let contract = GreenContract::merge_ready(GreenLevel::Workspace);
329+
let evidence = GreenEvidence::new(GreenLevel::Workspace)
330+
.with_test_command("cargo test --manifest-path rust/Cargo.toml", 0);
331+
332+
// when
333+
let outcome = contract.evaluate_evidence(&evidence);
334+
335+
// then
336+
assert_eq!(
337+
outcome,
338+
GreenEvidenceOutcome::Unsatisfied {
339+
required_level: GreenLevel::Workspace,
340+
observed_level: GreenLevel::Workspace,
341+
missing: vec![
342+
GreenContractRequirement::BaseBranchFreshness,
343+
GreenContractRequirement::RecoveryAttemptContext,
344+
],
345+
blocking_flakes: vec![],
346+
}
347+
);
348+
assert!(!outcome.is_satisfied());
349+
}
350+
351+
#[test]
352+
fn merge_ready_contract_accepts_complete_test_provenance_context() {
353+
// given
354+
let contract = GreenContract::merge_ready(GreenLevel::Workspace);
355+
let evidence = GreenEvidence::new(GreenLevel::MergeReady)
356+
.with_test_command("cargo test --manifest-path rust/Cargo.toml", 0)
357+
.with_base_branch_fresh(true)
358+
.with_recovery_attempt_context(true);
359+
360+
// when
361+
let outcome = contract.evaluate_evidence(&evidence);
362+
363+
// then
364+
assert_eq!(
365+
outcome,
366+
GreenEvidenceOutcome::Satisfied {
367+
required_level: GreenLevel::Workspace,
368+
observed_level: GreenLevel::MergeReady,
369+
}
370+
);
371+
}
372+
373+
#[test]
374+
fn known_blocking_flake_prevents_green_contract_satisfaction() {
375+
// given
376+
let contract = GreenContract::merge_ready(GreenLevel::Workspace);
377+
let evidence = GreenEvidence::new(GreenLevel::MergeReady)
378+
.with_test_command("cargo test --manifest-path rust/Cargo.toml", 0)
379+
.with_base_branch_fresh(true)
380+
.with_recovery_attempt_context(true)
381+
.with_known_flake(
382+
"session_lifecycle_prefers_running_process_over_idle_shell",
383+
true,
384+
);
385+
386+
// when
387+
let outcome = contract.evaluate_evidence(&evidence);
388+
389+
// then
390+
assert_eq!(
391+
outcome,
392+
GreenEvidenceOutcome::Unsatisfied {
393+
required_level: GreenLevel::Workspace,
394+
observed_level: GreenLevel::MergeReady,
395+
missing: vec![],
396+
blocking_flakes: vec![KnownFlake {
397+
test_name: "session_lifecycle_prefers_running_process_over_idle_shell"
398+
.to_string(),
399+
blocks_green: true,
400+
}],
401+
}
402+
);
403+
}
152404
}

rust/crates/runtime/src/policy_engine.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ impl PolicyCondition {
5858
Self::Or(conditions) => conditions
5959
.iter()
6060
.any(|condition| condition.matches(context)),
61-
Self::GreenAt { level } => context.green_level >= *level,
61+
Self::GreenAt { level } => {
62+
context.green_contract_satisfied && context.green_level >= *level
63+
}
6264
Self::StaleBranch => context.branch_freshness >= STALE_BRANCH_THRESHOLD,
6365
Self::StartupBlocked => context.blocker == LaneBlocker::Startup,
6466
Self::LaneCompleted => context.completed,
@@ -134,6 +136,7 @@ pub enum DiffScope {
134136
pub struct LaneContext {
135137
pub lane_id: String,
136138
pub green_level: GreenLevel,
139+
pub green_contract_satisfied: bool,
137140
pub branch_freshness: Duration,
138141
pub blocker: LaneBlocker,
139142
pub review_status: ReviewStatus,
@@ -156,6 +159,7 @@ impl LaneContext {
156159
Self {
157160
lane_id: lane_id.into(),
158161
green_level,
162+
green_contract_satisfied: false,
159163
branch_freshness,
160164
blocker,
161165
review_status,
@@ -171,6 +175,7 @@ impl LaneContext {
171175
Self {
172176
lane_id: lane_id.into(),
173177
green_level: 0,
178+
green_contract_satisfied: false,
174179
branch_freshness: Duration::from_secs(0),
175180
blocker: LaneBlocker::None,
176181
review_status: ReviewStatus::Pending,
@@ -179,6 +184,12 @@ impl LaneContext {
179184
reconciled: true,
180185
}
181186
}
187+
188+
#[must_use]
189+
pub fn with_green_contract_satisfied(mut self, satisfied: bool) -> Self {
190+
self.green_contract_satisfied = satisfied;
191+
self
192+
}
182193
}
183194

184195
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -257,7 +268,8 @@ mod tests {
257268
ReviewStatus::Approved,
258269
DiffScope::Scoped,
259270
false,
260-
);
271+
)
272+
.with_green_contract_satisfied(true);
261273

262274
// when
263275
let actions = engine.evaluate(&context);
@@ -266,6 +278,36 @@ mod tests {
266278
assert_eq!(actions, vec![PolicyAction::MergeToDev]);
267279
}
268280

281+
#[test]
282+
fn merge_rule_blocks_when_green_tests_lack_contract_provenance() {
283+
// given
284+
let engine = PolicyEngine::new(vec![PolicyRule::new(
285+
"merge-to-dev",
286+
PolicyCondition::And(vec![
287+
PolicyCondition::GreenAt { level: 2 },
288+
PolicyCondition::ScopedDiff,
289+
PolicyCondition::ReviewPassed,
290+
]),
291+
PolicyAction::MergeToDev,
292+
20,
293+
)]);
294+
let context = LaneContext::new(
295+
"lane-7",
296+
3,
297+
Duration::from_secs(5),
298+
LaneBlocker::None,
299+
ReviewStatus::Approved,
300+
DiffScope::Scoped,
301+
false,
302+
);
303+
304+
// when
305+
let actions = engine.evaluate(&context);
306+
307+
// then
308+
assert!(actions.is_empty());
309+
}
310+
269311
#[test]
270312
fn stale_branch_rule_fires_at_threshold() {
271313
// given
@@ -468,7 +510,8 @@ mod tests {
468510
ReviewStatus::Pending,
469511
DiffScope::Full,
470512
false,
471-
);
513+
)
514+
.with_green_contract_satisfied(true);
472515

473516
// when
474517
let actions = engine.evaluate(&context);

0 commit comments

Comments
 (0)