--- key: process.lock-ownership-enumerate-producers title: 2026-07-21 — A lock/channel "no cross-release" verdict must enumerate every producer via grep (#542) status: active since: '2026-07-21' supersedes: none superseded-by: none rule: Before trusting any "single owner / no double release / no cross-release" claim, grep the whole host project for every writer of that channel message (or acquirer of that lock) — the background scheduler/worker is the usual missing producer. signals: 'cross-release · lock ownership · channel message producers · scheduler enqueues too · grep the host project · paths: n/a · issues: #542, #235, #267, #250' mechanics: 'grep for every sender of the message type across the host project (e.g. all `Synchronize{X}Collections(` call sites), not just the controllers present in the PR diff. General form of "re-derive from a grep rather than enumerating from a list": see `sched.weighted-shuffle`.' --- On #235/#267 a cold fork cleared a new `scan-collections` per-provider lock as sound — "only the acquirer enqueues, so only its message triggers the scanner unlock" — having checked the three new controllers in the diff and stopped there. Codex grepped: `SchedulerService` also enqueues those messages periodically and unlocked, and `ScannerService`'s `finally` released the lock whenever held, so a scheduled scan cross-releases an API scan's lock (the #250 class). Corollary: the independent pass earns its keep even when the first reviewer returns MERGEABLE. The first verdict was not careless — it was complete over the evidence it looked at, which is exactly the failure mode a second, differently-scoped reviewer catches.