froot's scan loops tick on a daily timer. There was no way to force a scan in between โ which is exactly what you want after a fresh deploy, or to chase a finding you just introduced. This adds python -m froot.trigger: a non-destructive "scan now".
The design choice is the interesting part. The obvious mechanism โ terminate the running scan loop and let it restart โ is destructive (and was blocked as interfering with cluster workloads). A signal that interrupts the loop's sleep would need a non-backward-compatible sleep โ wait_condition change plus Temporal patching plus a one-time transition before it even works. Instead, the trigger starts a separate one-shot scan. ScanWorkflow already supports it (continuous=False does a single tick and returns), so this needs zero workflow-code change and works on the currently-deployed image immediately.
The module's own framing: a separate one-shot, distinct id, same idempotent dispatch.
src/froot/trigger.py ยท 156 lines
src/froot/trigger.py156 lines ยท Python
1"""Trigger an immediate one-shot scan of every configured acting loop.
The plan is pure: one one-shot per acting loop ร repo, filtered to FROOT_LOOPS (and an optional only). The acting/advisory split comes straight from the registry, so a new acting loop is reachable here by registration alone โ advisory loops have no ScanWorkflow and are skipped. Each one-shot carries the loop's scan id with a -now suffix, distinct from the long-lived loop so the two never collide.
One plan per (acting loop, repo); the -now id keeps it apart from the scheduled loop.
src/froot/trigger.py ยท 156 lines
src/froot/trigger.py156 lines ยท Python
โฏ 70 lines hidden (lines 1โ70)
1"""Trigger an immediate one-shot scan of every configured acting loop.
Each one-shot starts with ALLOW_DUPLICATE reuse: a finished one re-runs on the next trigger, and one still in flight is left to finish (caught and skipped). The connection mirrors the starter (the pydantic data converter, the same task queue).
Connect, start each one-shot, tolerate one already in flight.
src/froot/trigger.py ยท 156 lines
src/froot/trigger.py156 lines ยท Python
โฏ 106 lines hidden (lines 1โ106)
1"""Trigger an immediate one-shot scan of every configured acting loop.
Operationally it's a k8s manage Job (infra/k8s/froot/manage/scan-now.yaml, in the infra repo) that runs python -m froot.trigger, mirroring start-loops: kubectl apply -f manage/scan-now.yaml. FROOT_TRIGGER_LOOPS narrows it to specific loops (e.g. just dead-code); empty scans every acting loop. Pure tests cover the plan and filter logic; full suite green at 517 tests + both determinism gates.