fix: three gaps the P0 validation runs exposed
ci / gates (push) Failing after 7s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped

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:
Omar Sobh
2026-08-01 19:54:34 -07:00
co-authored by Claude Opus 5
parent 491449f3ce
commit d90a42b759
5 changed files with 165 additions and 3 deletions
+35 -2
View File
@@ -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;
}