fix: three gaps the P0 validation runs exposed
Validating P0 against production found one bug in each of the three pieces, none of which any test would have caught. **The scanners were installed but not allow-listed.** Mission 019fc058's condition asked for a gitleaks result; `gitleaks detect` came back `ran=false`, and the judge said it could not verify. P0.3 put the binaries in the image and never added them to `evaluator_tools::ALLOWED_PROGRAMS`, so the judge could not invoke the tools installed for it. Adds gitleaks, trivy, semgrep and `which`. **Every `continue` after a fire claim leaked the claim.** Introduced by the scheduler fix itself: the orphan-agent and empty-action paths skipped `complete_fire`, so the row stayed `claimed` — which reads as a crash mid-fire, meaning the routine is re-claimed forever and the table grows one stuck row per occurrence. Observed in production: five `claimed` rows, no dispatch, no `routine_runs`. Both paths now settle with a reason, and log it. **The agent writes its own identity files into the user's repository.** `workspace.path` is pinned to the repo root, so the runtime drops AGENTS.md, HEARTBEAT.md, IDENTITY.md, MEMORY.md, SOUL.md, TOOLS.md and USER.md into the checkout — SOUL.md opens "Who You Are / You're not a chatbot." Two consequences: every mission's tree is permanently dirty, so a `done_when` about a clean tree can never pass; and P1's `git add -A` would have committed the agent's SOUL.md into someone's repository and pushed it. The P1 deny-list covered build artifacts and would not have caught this. Fixed by writing the names to `.git/info/exclude` after clone — local to the checkout, never itself a change, and it suppresses only *untracked* files, so a repo that genuinely tracks its own AGENTS.md still reports modifications to it. Idempotent, and preserves any pre-existing exclude. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
491449f3ce
commit
d90a42b759
@@ -103,8 +103,28 @@ impl Scheduler {
|
||||
routines::set_next_run(&self.pool, routine.id, next).await?;
|
||||
|
||||
let agent_id = cm_domain::AgentId::from(routine.agent_id);
|
||||
let Ok(agent) = agents::get(&self.pool, agent_id).await else {
|
||||
continue; // deleted agent: routine is orphaned
|
||||
let agent = match agents::get(&self.pool, agent_id).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
// Orphaned routine (deleted agent, or a row we cannot
|
||||
// read). Settle the slot rather than leaving it `claimed`:
|
||||
// an unsettled claim looks like a crash mid-fire, so every
|
||||
// tick would re-claim the same routine forever and the
|
||||
// table would grow one stuck row per occurrence.
|
||||
eprintln!(
|
||||
"scheduler: routine {} references agent {agent_id} which could not be \
|
||||
read ({e}) — settling the occurrence as failed",
|
||||
routine.id
|
||||
);
|
||||
let _ = routines::complete_fire(
|
||||
&self.pool,
|
||||
routine.id,
|
||||
slot,
|
||||
Some(&format!("agent {agent_id} unreadable: {e}")),
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Topology routine: fire the whole team's stored topology as one
|
||||
@@ -149,6 +169,19 @@ impl Scheduler {
|
||||
|
||||
let message = routine.action["message"].as_str().unwrap_or_default();
|
||||
if message.is_empty() {
|
||||
// Neither a topology nor a message action: there is nothing to
|
||||
// dispatch. Settle it so the slot is not mistaken for a crash.
|
||||
eprintln!(
|
||||
"scheduler: routine {} has no `topology` or `message` action — nothing to fire",
|
||||
routine.id
|
||||
);
|
||||
let _ = routines::complete_fire(
|
||||
&self.pool,
|
||||
routine.id,
|
||||
slot,
|
||||
Some("routine action has neither `topology` nor `message`"),
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user