use super::semver_resolve::SemverResolveError;
use super::{Application, VersionSelector};
use crate::blueprint::store::{
blueprint_version, BlueprintEpoch, BlueprintId, BlueprintStore, BlueprintStoreError,
CommitMetadata, ContentHash, Traced,
};
use crate::blueprint::{AgentDef, Blueprint};
use crate::core::errors::EngineError;
use crate::enhance::blueprint::AG_PATCH_SPAWNER;
use crate::service::{TaskLaunchError, TaskLaunchInput, TaskLaunchOutput, TaskLaunchService};
use crate::store::enhance_log::{
EnhanceLogEntry, EnhanceLogStore, EnhanceLogStoreError, VerdictSummary,
};
use crate::store::enhance_setting::{
EnhanceSettingId, EnhanceSettingStore, EnhanceSettingStoreError,
};
use crate::store::issue::{IssueId, IssuePayload, IssueStatus, IssueStore, IssueStoreError};
use crate::types::Role;
use async_trait::async_trait;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EnhanceApplicationError {
#[error("issue store: {0}")]
Issue(#[from] IssueStoreError),
#[error("setting store: {0}")]
Setting(#[from] EnhanceSettingStoreError),
#[error("blueprint store: {0}")]
Bp(#[from] BlueprintStoreError),
#[error("enhance log store: {0}")]
Log(#[from] EnhanceLogStoreError),
#[error("launch: {0}")]
Launch(#[from] TaskLaunchError),
#[error("serialize directive: {0}")]
Serialize(#[from] serde_json::Error),
#[error("invalid semver version_label {label:?}: {source}")]
InvalidSemver {
label: String,
#[source]
source: semver::Error,
},
#[error("no version matches semver req: {req}")]
NoMatchingVersion {
req: String,
},
#[error("engine: {0}")]
Engine(#[from] EngineError),
#[error("commit shape: {0}")]
CommitShape(String),
#[error("system time before UNIX epoch: {0}")]
Clock(#[from] std::time::SystemTimeError),
#[error("spawner override: orbit blueprint declares no agent named {name:?}")]
SpawnerAgentNotFound {
name: String,
},
#[error(
"enhance epoch exceeded the {ttl_secs}s ceiling declared by enhance setting \
{setting_id:?} (ttl_secs); nothing was committed and the target Blueprint is \
unchanged. Only the wait ended: a worker already running in an in-process lane \
is still running, and re-posting now would put a second writer under the same \
project_root. Check that it has exited (and what it left there) before \
re-posting, and raise ttl_secs if the epoch legitimately needs longer"
)]
EpochCeilingExceeded {
setting_id: String,
ttl_secs: u64,
},
#[error(
"enhance setting {setting_id:?} declares ttl_secs: 0, which would abort every epoch \
before its first step completes; set ttl_secs to the number of seconds one epoch \
may run"
)]
ZeroTtl {
setting_id: String,
},
}
impl From<SemverResolveError> for EnhanceApplicationError {
fn from(e: SemverResolveError) -> Self {
match e {
SemverResolveError::Store(e) => EnhanceApplicationError::Bp(e),
SemverResolveError::InvalidSemver { label, source } => {
EnhanceApplicationError::InvalidSemver { label, source }
}
SemverResolveError::NoMatchingVersion { req } => {
EnhanceApplicationError::NoMatchingVersion { req }
}
}
}
}
#[derive(Debug, Clone)]
pub struct TickOutcome {
pub issue_id: IssueId,
pub status: IssueStatus,
}
pub struct EnhanceApplicationConfig {
pub name: String,
pub setting_id: EnhanceSettingId,
pub operator_id: String,
pub role: Role,
}
pub struct EnhanceApplication {
name: String,
setting_id: EnhanceSettingId,
operator_id: String,
role: Role,
issue_store: Arc<dyn IssueStore>,
setting_store: Arc<dyn EnhanceSettingStore>,
bp_store: Arc<dyn BlueprintStore>,
log_store: Arc<dyn EnhanceLogStore>,
launch: Arc<TaskLaunchService>,
}
impl EnhanceApplication {
pub fn new(
cfg: EnhanceApplicationConfig,
issue_store: Arc<dyn IssueStore>,
setting_store: Arc<dyn EnhanceSettingStore>,
bp_store: Arc<dyn BlueprintStore>,
log_store: Arc<dyn EnhanceLogStore>,
launch: Arc<TaskLaunchService>,
) -> Self {
Self {
name: cfg.name,
setting_id: cfg.setting_id,
operator_id: cfg.operator_id,
role: cfg.role,
issue_store,
setting_store,
bp_store,
log_store,
launch,
}
}
pub fn issue_store(&self) -> &Arc<dyn IssueStore> {
&self.issue_store
}
pub fn bp_store(&self) -> &Arc<dyn BlueprintStore> {
&self.bp_store
}
pub fn log_store(&self) -> &Arc<dyn EnhanceLogStore> {
&self.log_store
}
pub async fn tick(&self) -> Result<Option<TickOutcome>, EnhanceApplicationError> {
let Some(payload) = self.issue_store.pop_pending().await? else {
return Ok(None);
};
match self.dispatch_one(&payload).await {
Ok(status) => {
self.issue_store
.update_status(&payload.issue_id, status.clone())
.await?;
Ok(Some(TickOutcome {
issue_id: payload.issue_id,
status,
}))
}
Err(e) => {
let reason = format!("dispatch failed: {e}");
self.issue_store
.update_status(&payload.issue_id, IssueStatus::Rejected { reason })
.await?;
Err(e)
}
}
}
async fn dispatch_one(
&self,
payload: &IssuePayload,
) -> Result<IssueStatus, EnhanceApplicationError> {
let setting = self.setting_store.get(&self.setting_id).await?;
if setting.ttl_secs == 0 {
return Err(EnhanceApplicationError::ZeroTtl {
setting_id: self.setting_id.to_string(),
});
}
let mut traced_orch = self
.resolve_blueprint(&setting.blueprint_id, &setting.version)
.await?;
apply_spawner_override(&mut traced_orch.value, setting.spawner.as_ref())?;
let traced_target = self.bp_store.read_head(&payload.blueprint_id).await?;
let prev_bp_yaml = serde_yaml::to_string(&traced_target.value).map_err(|e| {
EnhanceApplicationError::Serialize(serde::ser::Error::custom(format!(
"prev_bp yaml: {e}"
)))
})?;
let prev_version = blueprint_version(&traced_target.value).map_err(|e| {
EnhanceApplicationError::Serialize(serde::ser::Error::custom(format!("prev_hash: {e}")))
})?;
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_millis() as i64;
let epoch = BlueprintEpoch::new(payload.blueprint_id.clone(), prev_version, now_ms);
let prev_hash_hex = hex::encode(prev_version.0 .0);
let init_ctx = serde_json::json!({
"issue": {
"issue_id": payload.issue_id.as_str(),
"blueprint_id": payload.blueprint_id.as_str(),
"intent": payload.intent,
},
"prev_bp_yaml": prev_bp_yaml,
"prev_hash": prev_hash_hex.clone(),
"epoch_id": epoch.clone(),
"verifiers": setting.verifier_axes.clone(),
});
let ttl = Duration::from_secs(setting.ttl_secs);
let launch = self.launch.launch(TaskLaunchInput::automate(
traced_orch.value,
self.operator_id.clone(),
self.role,
ttl,
init_ctx,
));
let TaskLaunchOutput {
token: _,
final_ctx,
} = match tokio::time::timeout(ttl, launch).await {
Ok(launched) => launched?,
Err(_elapsed) => {
tracing::warn!(
issue_id = %payload.issue_id,
blueprint_id = %payload.blueprint_id,
setting_id = %self.setting_id,
ttl_secs = setting.ttl_secs,
prev_hash = %prev_hash_hex,
"enhance epoch hit its ttl_secs ceiling; this dispatcher stopped waiting \
and nothing was committed. A worker already running in an in-process \
lane is NOT stopped by this — it runs to its own end, so check that it \
has exited (and what it left under project_root) before re-posting, or \
raise ttl_secs"
);
return Err(EnhanceApplicationError::EpochCeilingExceeded {
setting_id: self.setting_id.to_string(),
ttl_secs: setting.ttl_secs,
});
}
};
let commit_decision = extract_commit(&final_ctx)?;
let (status, log_entry) = match commit_decision {
CommitDecision::Applied {
new_bp,
new_version_hex,
rationale,
bump,
verdicts,
} => {
let patch_hash = ContentHash::from_bytes(rationale.as_bytes());
let metadata = CommitMetadata {
epoch_id: epoch.clone(),
rationale: rationale.clone(),
patch_hash,
};
let new_version = self
.bp_store
.write_new(
&payload.blueprint_id,
&new_bp,
std::slice::from_ref(&prev_version),
metadata,
)
.await?;
let new_version_hex_actual = hex::encode(new_version.0 .0);
if new_version_hex_actual != new_version_hex {
return Err(EnhanceApplicationError::CommitShape(format!(
"new_version mismatch: committer={new_version_hex} store={new_version_hex_actual}"
)));
}
let entry = EnhanceLogEntry {
issue_id: payload.issue_id.clone(),
blueprint_id: payload.blueprint_id.clone(),
prev_hash: prev_hash_hex.clone(),
new_hash: new_version_hex_actual.clone(),
intent: payload.intent.clone(),
rationale: rationale.clone(),
verdicts,
status: "applied".into(),
reasons: vec![],
ts_ms: now_ms,
};
tracing::info!(%bump, issue_id = %payload.issue_id, "commit bump label (not persisted in CommitMetadata)");
(
IssueStatus::Applied {
new_version: new_version_hex_actual,
},
entry,
)
}
CommitDecision::Rejected {
reasons,
rationale,
verdicts,
} => {
let entry = EnhanceLogEntry {
issue_id: payload.issue_id.clone(),
blueprint_id: payload.blueprint_id.clone(),
prev_hash: prev_hash_hex.clone(),
new_hash: String::new(),
intent: payload.intent.clone(),
rationale,
verdicts,
status: "rejected".into(),
reasons: reasons.clone(),
ts_ms: now_ms,
};
(
IssueStatus::Rejected {
reason: format!("verifier deny: {}", reasons.join("; ")),
},
entry,
)
}
};
self.log_store.append(log_entry).await?;
Ok(status)
}
async fn resolve_blueprint(
&self,
bp_id: &BlueprintId,
selector: &VersionSelector,
) -> Result<Traced<Blueprint>, EnhanceApplicationError> {
match selector {
VersionSelector::Latest => Ok(self.bp_store.read_head(bp_id).await?),
VersionSelector::Fixed { value } => {
Ok(self.bp_store.read_version(bp_id, *value).await?)
}
VersionSelector::SemverReq { req } => {
let v = super::semver_resolve::resolve_semver(self.bp_store.as_ref(), bp_id, req)
.await?;
Ok(self.bp_store.read_version(bp_id, v).await?)
}
}
}
pub async fn run_forever(self: Arc<Self>, interval: Duration) {
loop {
match self.tick().await {
Ok(Some(_)) => continue,
Ok(None) => tokio::time::sleep(interval).await,
Err(e) => {
eprintln!("[{}] tick error: {e}", self.name);
tokio::time::sleep(interval).await;
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct EnhanceApplicationInput {
pub blueprint_id: BlueprintId,
pub intent: String,
pub issue_id: IssueId,
}
fn apply_spawner_override(
blueprint: &mut Blueprint,
spawner: Option<&AgentDef>,
) -> Result<(), EnhanceApplicationError> {
let Some(spawner) = spawner else {
return Ok(());
};
let slot = blueprint
.agents
.iter_mut()
.find(|a| a.name == AG_PATCH_SPAWNER)
.ok_or_else(|| EnhanceApplicationError::SpawnerAgentNotFound {
name: AG_PATCH_SPAWNER.to_string(),
})?;
let mut swapped = spawner.clone();
swapped.name = AG_PATCH_SPAWNER.to_string();
*slot = swapped;
Ok(())
}
enum CommitDecision {
Applied {
new_bp: Box<Blueprint>,
new_version_hex: String,
rationale: String,
bump: String,
verdicts: Vec<VerdictSummary>,
},
Rejected {
reasons: Vec<String>,
rationale: String,
verdicts: Vec<VerdictSummary>,
},
}
fn extract_commit(
final_ctx: &serde_json::Value,
) -> Result<CommitDecision, EnhanceApplicationError> {
let shape_err =
|msg: String| -> EnhanceApplicationError { EnhanceApplicationError::CommitShape(msg) };
let commit = final_ctx
.get("commit")
.ok_or_else(|| shape_err("final_ctx missing $.commit".into()))?;
let committed = commit
.get("committed")
.and_then(|v| v.as_bool())
.ok_or_else(|| shape_err("commit.committed missing or not bool".into()))?;
let rationale = commit
.get("rationale")
.and_then(|v| v.as_str())
.ok_or_else(|| shape_err("commit.rationale missing or not string".into()))?
.to_string();
let verdicts = parse_verdicts_summary(commit)?;
if committed {
let new_version_hex = commit
.get("new_version")
.and_then(|v| v.as_str())
.ok_or_else(|| shape_err("commit.new_version missing or not string".into()))?
.to_string();
if new_version_hex.is_empty() {
return Err(shape_err("commit.new_version is empty (Applied)".into()));
}
let bump = commit
.get("bump")
.and_then(|v| v.as_str())
.ok_or_else(|| shape_err("commit.bump missing or not string".into()))?
.to_string();
let new_bp_json = commit
.get("new_bp_json")
.ok_or_else(|| shape_err("commit.new_bp_json missing".into()))?
.clone();
let new_bp: Box<Blueprint> = serde_json::from_value(new_bp_json)
.map_err(|e| shape_err(format!("commit.new_bp_json deserialize: {e}")))?;
Ok(CommitDecision::Applied {
new_bp,
new_version_hex,
rationale,
bump,
verdicts,
})
} else {
let reasons_arr = commit
.get("reasons")
.and_then(|v| v.as_array())
.ok_or_else(|| shape_err("commit.reasons missing or not array".into()))?;
let reasons: Vec<String> = reasons_arr
.iter()
.map(|v| {
v.as_str()
.map(|s| s.to_string())
.ok_or_else(|| shape_err("commit.reasons[] contains non-string element".into()))
})
.collect::<Result<_, _>>()?;
if reasons.is_empty() {
return Err(shape_err(
"commit.reasons is empty (Rejected requires at least 1)".into(),
));
}
Ok(CommitDecision::Rejected {
reasons,
rationale,
verdicts,
})
}
}
fn parse_verdicts_summary(
commit: &serde_json::Value,
) -> Result<Vec<VerdictSummary>, EnhanceApplicationError> {
let arr = commit
.get("verdicts_summary")
.and_then(|v| v.as_array())
.ok_or_else(|| {
EnhanceApplicationError::CommitShape(
"commit.verdicts_summary missing or not array".into(),
)
})?;
arr.iter()
.map(|v| {
let axis = v
.get("axis")
.and_then(|x| x.as_str())
.ok_or_else(|| {
EnhanceApplicationError::CommitShape("verdicts_summary[].axis missing".into())
})?
.to_string();
let status = v
.get("status")
.and_then(|x| x.as_str())
.ok_or_else(|| {
EnhanceApplicationError::CommitShape("verdicts_summary[].status missing".into())
})?
.to_string();
let detail = match status.as_str() {
"pass" => v
.get("evidence")
.and_then(|x| x.as_str())
.ok_or_else(|| {
EnhanceApplicationError::CommitShape(
"verdicts_summary[].evidence missing for pass".into(),
)
})?
.to_string(),
"deny" => v
.get("reason")
.and_then(|x| x.as_str())
.ok_or_else(|| {
EnhanceApplicationError::CommitShape(
"verdicts_summary[].reason missing for deny".into(),
)
})?
.to_string(),
other => {
return Err(EnhanceApplicationError::CommitShape(format!(
"verdicts_summary[].status must be pass|deny, got {other}"
)))
}
};
Ok(VerdictSummary {
axis,
status,
detail,
})
})
.collect()
}
#[async_trait]
impl Application for EnhanceApplication {
type Input = EnhanceApplicationInput;
type Output = IssueId;
type Error = EnhanceApplicationError;
fn name(&self) -> &str {
&self.name
}
async fn handle(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
self.issue_store
.create(IssuePayload {
issue_id: input.issue_id.clone(),
blueprint_id: input.blueprint_id,
intent: input.intent,
})
.await?;
Ok(input.issue_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blueprint::AgentKind;
use crate::enhance::blueprint::default_blueprint;
fn spawner_of(bp: &Blueprint) -> &AgentDef {
bp.agents
.iter()
.find(|a| a.name == AG_PATCH_SPAWNER)
.expect("blueprint declares a patch-spawner agent")
}
fn subprocess_spawner(name: &str) -> AgentDef {
serde_json::from_value(serde_json::json!({
"name": name,
"kind": "subprocess",
"spec": { "program": "true", "args": [] },
}))
.expect("literal is a valid AgentDef")
}
#[test]
fn no_override_keeps_the_blueprints_own_spawner() {
let mut bp = default_blueprint();
let before = spawner_of(&bp).clone();
apply_spawner_override(&mut bp, None).unwrap();
assert_eq!(spawner_of(&bp), &before);
assert_eq!(spawner_of(&bp).kind, AgentKind::AgentBlock);
}
#[test]
fn override_swaps_the_spawner_and_forces_the_referenced_name() {
let mut bp = default_blueprint();
let agents_before = bp.agents.len();
let def = subprocess_spawner("my-own-spawner");
apply_spawner_override(&mut bp, Some(&def)).unwrap();
let swapped = spawner_of(&bp);
assert_eq!(swapped.kind, AgentKind::Subprocess);
assert_eq!(swapped.name, AG_PATCH_SPAWNER);
assert_eq!(swapped.spec, def.spec);
assert_eq!(bp.agents.len(), agents_before);
assert!(!bp.agents.iter().any(|a| a.name == "my-own-spawner"));
}
use crate::blueprint::compiler::{Compiler, RustFnInProcessSpawnerFactory, SpawnerRegistry};
use crate::blueprint::store::{BlueprintId, CommitMetadata, InMemoryBlueprintStore};
use crate::core::config::EngineCfg;
use crate::core::engine::Engine;
use crate::enhance::setting::{EnhanceSetting, EnhanceSettingMeta};
use crate::store::enhance_log::InMemoryEnhanceLogStore;
use crate::store::enhance_setting::InMemoryEnhanceSettingStore;
use crate::store::issue::InMemoryIssueStore;
use crate::worker::adapter::WorkerResult;
use mlua_flow_ir::{Expr, Node as FlowNode};
use serde_json::json;
const TARGET_BP: &str = "target-ut";
fn orbit_bp(fn_id: &str) -> Blueprint {
let mut bp = default_blueprint();
bp.id = "orbit-ut".into();
bp.flow = FlowNode::Step {
ref_: AG_PATCH_SPAWNER.into(),
in_: Expr::Lit {
value: serde_json::Value::Null,
},
out: Expr::Path {
at: "$.commit".parse().expect("literal test path: $.commit"),
},
};
bp.agents = vec![serde_json::from_value(json!({
"name": AG_PATCH_SPAWNER,
"kind": "rust_fn",
"spec": { "fn_id": fn_id },
}))
.expect("literal is a valid AgentDef")];
bp
}
struct Harness {
app: EnhanceApplication,
issues: Arc<InMemoryIssueStore>,
bps: Arc<InMemoryBlueprintStore>,
logs: Arc<InMemoryEnhanceLogStore>,
target_id: BlueprintId,
}
async fn harness(
fn_id: &str,
factory: RustFnInProcessSpawnerFactory,
ttl_secs: u64,
) -> Harness {
let mut registry = SpawnerRegistry::new();
registry.register::<RustFnInProcessSpawnerFactory>(Arc::new(factory));
let launch =
TaskLaunchService::new(Engine::new(EngineCfg::default()), Compiler::new(registry));
let bps = Arc::new(InMemoryBlueprintStore::new());
let target_id = BlueprintId::new(TARGET_BP.to_string());
let target = default_blueprint();
let seed_version =
crate::blueprint::store::blueprint_version(&target).expect("seed hashes");
bps.write_new(
&target_id,
&target,
&[],
CommitMetadata::seed(target_id.clone(), seed_version, 0),
)
.await
.expect("seed the target Blueprint");
let settings = Arc::new(InMemoryEnhanceSettingStore::new());
let setting_id = EnhanceSettingId::default_id();
settings
.put(
&setting_id,
EnhanceSetting {
id: setting_id.to_string(),
blueprint_id: BlueprintId::new("orbit-ut".to_string()),
ttl_secs,
version: crate::application::VersionSelector::default(),
verifier_axes: vec![],
spawner: None,
meta: EnhanceSettingMeta::default(),
},
)
.await
.expect("put the setting");
let orbit = orbit_bp(fn_id);
let orbit_id = BlueprintId::new("orbit-ut".to_string());
let orbit_version =
crate::blueprint::store::blueprint_version(&orbit).expect("orbit hashes");
bps.write_new(
&orbit_id,
&orbit,
&[],
CommitMetadata::seed(orbit_id.clone(), orbit_version, 0),
)
.await
.expect("seed the orbit Blueprint");
let issues = Arc::new(InMemoryIssueStore::new());
let logs = Arc::new(InMemoryEnhanceLogStore::new());
let app = EnhanceApplication::new(
EnhanceApplicationConfig {
name: "ut".into(),
setting_id,
operator_id: "ut-op".into(),
role: Role::Operator,
},
issues.clone(),
settings,
bps.clone(),
logs.clone(),
Arc::new(launch),
);
Harness {
app,
issues,
bps,
logs,
target_id,
}
}
async fn post_issue(h: &Harness, issue_id: &str) -> IssueId {
let id = IssueId::new(issue_id);
h.app
.handle(EnhanceApplicationInput {
blueprint_id: h.target_id.clone(),
intent: "add a smoke tag".into(),
issue_id: id.clone(),
})
.await
.expect("enqueue");
id
}
#[tokio::test]
async fn epoch_that_outruns_ttl_secs_stops_the_wait_with_nothing_committed() {
let factory = RustFnInProcessSpawnerFactory::new().register_fn("hang", |_inv| async move {
tokio::time::sleep(Duration::from_secs(300)).await;
Ok(WorkerResult {
value: json!({}),
ok: true,
stats: None,
})
});
let h = harness("hang", factory, 1).await;
let issue_id = post_issue(&h, "h-ceiling").await;
let head_before = h.bps.read_head(&h.target_id).await.expect("head before");
let err = h
.app
.tick()
.await
.expect_err("an epoch past its ceiling must surface as an infra fault");
assert!(
matches!(
err,
EnhanceApplicationError::EpochCeilingExceeded { ttl_secs: 1, ref setting_id }
if setting_id == "default"
),
"the ceiling must abort with EpochCeilingExceeded, got: {err}"
);
match h.issues.status(&issue_id).await.expect("issue status") {
IssueStatus::Rejected { reason } => {
assert!(
reason.contains("exceeded the 1s ceiling") && reason.contains("ttl_secs"),
"the reason must name the ceiling and the knob to raise, got: {reason}"
);
assert!(
reason.contains("nothing was committed"),
"the reason must say the target Blueprint is untouched, got: {reason}"
);
assert!(
reason.contains("Only the wait ended"),
"the reason must say the worker may still be running, got: {reason}"
);
assert!(
reason.contains("second writer"),
"the reason must name the hazard a blind re-post creates, got: {reason}"
);
}
other => panic!("a timed-out epoch must be terminal Rejected, got {other:?}"),
}
let head_after = h.bps.read_head(&h.target_id).await.expect("head after");
assert_eq!(
head_before.value, head_after.value,
"a fired ceiling must not write to the BlueprintStore"
);
assert_eq!(
h.bps
.history(&h.target_id, 10)
.await
.expect("history")
.len(),
1,
"only the seed commit may exist after a timed-out epoch"
);
assert!(
h.logs.list_all().await.expect("log").is_empty(),
"an epoch that never reached the committer appends no log entry"
);
}
#[tokio::test]
async fn a_fired_ceiling_does_not_stop_the_worker() {
use std::sync::atomic::{AtomicBool, Ordering};
let ran_past_the_ceiling = Arc::new(AtomicBool::new(false));
let flag = ran_past_the_ceiling.clone();
let factory = RustFnInProcessSpawnerFactory::new().register_fn("outlive", move |_inv| {
let flag = flag.clone();
async move {
tokio::time::sleep(Duration::from_millis(1_600)).await;
flag.store(true, Ordering::SeqCst);
Ok(WorkerResult {
value: json!({}),
ok: true,
stats: None,
})
}
});
let h = harness("outlive", factory, 1).await;
post_issue(&h, "h-residue").await;
let err = h.app.tick().await.expect_err("the ceiling must fire");
assert!(
matches!(err, EnhanceApplicationError::EpochCeilingExceeded { .. }),
"expected the ceiling, got: {err}"
);
assert!(
!ran_past_the_ceiling.load(Ordering::SeqCst),
"precondition: the worker must still be mid-sleep when the ceiling fires, \
otherwise this test proves nothing"
);
tokio::time::sleep(Duration::from_millis(1_200)).await;
assert!(
ran_past_the_ceiling.load(Ordering::SeqCst),
"the worker was expected to run on past the ceiling — if it stopped, the \
ceiling now reaches the work and the reason text plus \
mse://guides/enhance-flow, which both tell operators to check for a live \
worker before re-posting, have to be corrected in the same change"
);
}
#[tokio::test]
async fn epoch_within_the_ceiling_still_reaches_the_committer() {
let factory =
RustFnInProcessSpawnerFactory::new().register_fn("commit", |_inv| async move {
Ok(WorkerResult {
value: json!({
"committed": false,
"rationale": "the patch was refused",
"reasons": ["noop: patch is no-op"],
"verdicts_summary": [
{"axis": "noop", "status": "deny", "reason": "new_hash == prev_hash"}
],
}),
ok: true,
stats: None,
})
});
let h = harness("commit", factory, 60).await;
let issue_id = post_issue(&h, "h-ok").await;
let outcome = h
.app
.tick()
.await
.expect("a within-ceiling epoch must not surface as an infra fault")
.expect("one issue was pending");
assert_eq!(outcome.issue_id, issue_id);
match outcome.status {
IssueStatus::Rejected { ref reason } => assert!(
reason.starts_with("verifier deny:"),
"a committer rejection must keep its own reason, not the ceiling's: {reason}"
),
ref other => panic!("expected a verifier rejection, got {other:?}"),
}
assert_eq!(
h.logs.list_all().await.expect("log").len(),
1,
"an epoch that reached the committer appends exactly one log entry"
);
}
#[tokio::test]
async fn zero_ttl_secs_is_refused_and_names_the_field() {
let factory =
RustFnInProcessSpawnerFactory::new().register_fn("unused", |_inv| async move {
Ok(WorkerResult {
value: json!({}),
ok: true,
stats: None,
})
});
let h = harness("unused", factory, 0).await;
let issue_id = post_issue(&h, "h-zero").await;
let err = h
.app
.tick()
.await
.expect_err("a zero ceiling must be refused, not treated as unbounded");
assert!(
matches!(
err,
EnhanceApplicationError::ZeroTtl { ref setting_id } if setting_id == "default"
),
"expected ZeroTtl, got: {err}"
);
match h.issues.status(&issue_id).await.expect("issue status") {
IssueStatus::Rejected { reason } => assert!(
reason.contains("ttl_secs: 0"),
"the reason must name the field and its bad value, got: {reason}"
),
other => panic!("expected terminal Rejected, got {other:?}"),
}
assert!(
h.logs.list_all().await.expect("log").is_empty(),
"a refused setting never reaches the committer"
);
}
#[test]
fn override_without_a_matching_agent_fails_loud() {
let mut bp = default_blueprint();
bp.agents.retain(|a| a.name != AG_PATCH_SPAWNER);
let err = apply_spawner_override(&mut bp, Some(&subprocess_spawner(AG_PATCH_SPAWNER)))
.expect_err("a missing override target must not be ignored");
assert!(matches!(
err,
EnhanceApplicationError::SpawnerAgentNotFound { ref name } if name == AG_PATCH_SPAWNER
));
assert!(err.to_string().contains(AG_PATCH_SPAWNER));
}
}