fix(taint): record taint.hosts once per phase, not once per sweep tick
deploy / build (push) Canceled after 0s
deploy / test (push) Canceled after 1m51s

The container drain revisits finished phases for 30 minutes; the other drains
are idempotent because they truncate, and the taint file deliberately is not.
The first live mission (01a0cb7e) recorded the same event four times. Record
only when no event for the phase already carries at least as many hosts.

Live result otherwise as designed: curl https://example.com tainted iana.org
(the page's link), not example.com (the agent's own target), and grep -rn curl
added nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-22 18:43:24 -05:00
co-authored by Claude Opus 5.5
parent 597e76b261
commit decc680bcc
2 changed files with 41 additions and 1 deletions
+23 -1
View File
@@ -684,8 +684,30 @@ async fn drain_finished_container_phases(pool: &PgPool) -> Result<(), String> {
}
// What fetched content named. Observed, not enforced — see
// docs/TASK-PERMISSION-AND-TAINT.md, piece 2, stage 1.
//
// Once per phase. This sweep revisits every phase for 30 minutes, and
// the other drains are idempotent only because they truncate what they
// read; the taint file is deliberately never truncated, so without this
// check the first live run recorded the same event four times and would
// have gone on recording it every tick. A LARGER set is still recorded:
// that is new information.
let hosts = crate::container_tool_hooks::drain_taint(&docker, &container).await;
if !hosts.is_empty() {
let already: bool = if hosts.is_empty() {
true
} else {
sqlx::query_scalar(
"SELECT EXISTS (SELECT 1 FROM mission_events
WHERE phase_id = $1 AND kind = $2
AND (detail->>'count')::int >= $3)",
)
.bind(phase_id)
.bind(crate::container_tool_hooks::TAINT_HOSTS)
.bind(hosts.len() as i32)
.fetch_one(pool)
.await
.unwrap_or(false)
};
if !already {
crate::mission_events::record(
pool,
crate::mission_events::MissionEvent::new(