Proof: a dispatch admits the evidence its lanes can read, not the name (V5b2d-4g) - #555
Conversation
…e (V5b2d-4g) The verification dispatch binds an evidence run and an exact artifact name, and a name is not a layout. An evidence run built by an older producer, with a different evidence-out/ shape, carries the right artifact name and the wrong contents: it passes admission, fans out to 256 lanes, and every one of them dies seconds in on `test -f`. That is the incident of the 133 wasted runs again, on its third axis. So the admission reads what it names. The coordinator downloads the bound artifact once — two files well under a megabyte — and dispatches only when it carries every path the lane workflow guards. The module's shape is kept: the download is the one impure boundary and decides nothing; which paths must be there is a pure rule a test can reach without a network; a missing input and any failure to observe are the same typed refusal, never a crash and never a silent proceed. The temporary directory is owned by the download and removed on both paths. A dry run stays offline by contract. The path list is a contract with verification-lanes.yml, so it is not restated by hand: tests read it back out of the workflow text in both directions — every guarded path must be admitted, and every evidence path the lane uses must be covered — so the two cannot drift apart silently.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 59 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Comment |
Final-state review anchored on the commit object — after catching its own first mutation pass reading a stale file through WSL — ran 26 mutants and found two blockers plus one theatre test. THE CRASH. A refused command set is not iterable, and `main` fell straight into the loop with it: a mistyped artifact name — the likeliest operator slip — crashed with a TypeError while this very commit's docstring promises never a crash. The same fallthrough was fixed on the #548 branch, but this branch grows from main, where it still lived. Typed exit now, and the CLI test covers three foreign coordinates while also proving the expensive download never ran for any of them — a coordinate the pure builder already refused must not cost a network call. THE FALSE CLAIM. The comment said `CalledProcessError.__repr__` drops stderr. Measured: it does not — `BaseException` keeps every constructor argument in `args`. The preference for `stderr` over `repr` is legibility, not recovery, and the comment now says so instead of inventing a stronger reason. THE THEATRE. The foreign-entries test fed bytes, None and an int to the type guard — inputs that can never equal a string, so the guard was semantically free and the test held without the code it pointed at. An unhashable list is what makes the guard load-bearing: without it the observation raises instead of refusing. The test feeds one now, and stripping the guard fails it. Also: the download carries a deadline. A hung `gh` stalled the coordinator forever on the last check standing between an operator and 256 dispatches, indistinguishable from work in progress. Each repair proven by its mutant: the restored fallthrough reddens the new CLI test, the stripped guard reddens the strengthened one. Verified: 31 dispatch tests pass; 426 on Linux with the three pre-existing `test_build` failures that also fail on an untouched tree.
Independent verification found the class this branch claimed closed was closed only at the zero boundary: a positive but truncating --run-limit spent the query and then reported the campaign incomplete — sending the operator to re-dispatch 256 lanes when the fix is one flag. `gh run list --limit N` drops the oldest runs, so a listing that came back exactly at the limit cannot distinguish a real hole from its own truncation. The refusal now names the flag when the listing is saturated, and keeps blaming the campaign when there is room to spare — the anti-vacuity case, because hiding real damage behind the flag would be the opposite failure. Also corrected here: the comment claiming CalledProcessError.__repr__ drops stderr — measured false on the #555 branch, repr carries every constructor argument; stderr is preferred for legibility, not recovery. Verified: 36 dispatch tests pass, both new ones read the refusal text.
The interleave hit again — the content observer and the artifact observer begin identically, so the zone was reconstructed with both sides whole. Resolution by intent: - one OBSERVATION_TIMEOUT_SECONDS_V1 for every observation — a listing, a projection, or the two-small-file download; - the content admission moves out of its pre-polarity guard into the live admission chain, strictly last: origin, then the listing, then the download, so the free refusals never cost a fetch; - the seam helper admits a run for both cheaper observations, keeping the download as the one variable those tests actually probe; - the content CLI tests take the live polarity (`--execute --expect-lanes`), and the old dry-run case becomes the printing-stays-offline case. Verified: 86 dispatch tests pass, and again under PYTHONOPTIMIZE=2.
…5b2d-4e) (#553) * Proof: a dispatched campaign hands the dual proof its lane run ids (V5b2d-4e) Наблюдаемый дефект: полное покрытие домена — 512 отдельных прогонов полос (256 на движок), и дуальному доказательству нужен список их идентификаторов. Собрать его нечем: `corpus_dispatch.py` печатает команды `gh workflow run`, а они id не возвращают; сбор по времени создания ломается ровно там, где он и нужен — два движка реплеят одни и те же окна одной evidence-сборки одновременно. Закон: прогон полосы назван своими координатами (`verification-lanes.yml` рендерит `lane <artifact> <start>+<points> of <evidence_run_id>`), поэтому список кампании — запрос по именам, а не догадка по часам. Кампанию отделяют артефакт и evidence run id, не время. Режим `--mode collect` устроен по границе, уже принятой в модуле: - `gh_lane_runs_v1` — единственное нечистое наблюдение. Проекция сделана самим `--jq` в запросе, поэтому в процесс входят ровно три поля (databaseId, displayTitle, conclusion) и ни одно непросмотренное поле ответа API не может попасть ни в решение, ни в лог. Запись, которая не проецируется точно, отвергается, а не угадывается. - `match_lane_runs_v1` — чистое сопоставление имён с планом. Все правила — чья кампания, чей движок, какое заключение считается, что такое полное покрытие — достижимы тестом без сети. - `collect_lane_runs_v1` — шов между ними: недоступный API, отсутствующий токен или нечитаемый ответ становятся типизированным отказом с причиной, а не пустой кампанией; пустая кампания и сломанный запрос ведут оператора к противоположным действиям. Неполный сбор — типизированный отказ `LaneRunCollectionRejectedV1`, а не тихий частичный список: окна без прогона и окна с двумя едут в нём данными (`missing`/`duplicated`), оператор передиспатчит ровно их, а CLI не пишет ничего и выходит 64. Частичный список неотличим от полного для того, кто прочитает его следующим. Парсер имени допускает ровно канонический рендеринг: `int()` принял бы `+7`, `007`, `1_0` и не-ASCII цифры, и каждое из них позволило бы чужому заголовку занять окно плана. Доказательства (WSL, python3 -m unittest): - RED до реализации: 17 ошибок «module 'corpus_dispatch' has no attribute parse_lane_run_name_v1 / match_lane_runs_v1 / collect_lane_runs_v1». - 19 новых тестов; каждый заявленный класс убивает мутацию: порядок плана → порядок наблюдения (M1); дыра терпится (M2); дубль терпится (M3); фильтр артефакта снят (M4); фильтр evidence run снят (M5); заключение игнорируется (M6); ordinals по `isdigit()` (M7); `except Exception` сужен до `OSError` на шве наблюдения (M8). Все восемь падают на финальном коде. - Контакт с реальностью: `gh run list --workflow verification-lanes.yml --json databaseId,displayTitle,conclusion --jq ...` на живом репозитории возвращает реальные записи; `--mode collect` против прогона 31104030757 отказывает с 256 missing и не пишет ничего (в `main` run-name ещё нет — он приходит с V5b2d-4d, до него ни один заголовок не является полосой кампании). - Набор proof: 427 тестов, 3 падения — ровно предсуществующие в test_build на Linux (два golden-digest и post_popen_handler_gap). Связность: режим полагается на `run-name` из `verification-lanes.yml` (V5b2d-4d). Без него сбор не выдумывает совпадений — он отказывает громко. * Proof: defend the three invariants the collection only asserted Independent verification ran mutants and found the same class three times: an invariant stated in code and in the comment beside it, with nothing that would notice its removal. The expensive one is the second engine. Both engines replay the same evidence build, so a listing carries both campaigns — and a collector that hardcoded the first artifact answered an MPFI request with Arb's run ids and exited 0. Half a dual proof, silently wrong, reported as success. Every CLI case used the Arb artifact, so nothing could tell the two apart. The overlap guard was theatre in the literal sense: its docstring calls overlap "the one thing that must not pass", and the refusal suite contained no overlapping plan at all — the case it did contain died on the tuple-shape branch beside it. A plan of two windows sharing an ordinal is in the suite now. Deduplicating by run id had the same shape: a listing that repeats a run must not manufacture a duplicate-cover refusal, and removing the check left everything green. Each is proven by the mutant it kills, one test apiece: the hardcoded artifact at the collect call site, `window[0] < cursor` weakened to a tautology, and the unconditional append. Verified: 31 dispatch tests pass; 429 on Linux with the three pre-existing `test_build` failures that also fail on an untouched tree. * Proof: the collect CLI judges its arguments before it spends a query Замечание ревью верно по существу: `--mode collect` передавал свои аргументы в `collect_lane_runs_v1` без единой проверки, а тот первым делом делает сетевой вызов. Наблюдённое поведение до правки (WSL, наблюдатель-заглушка вместо `gh`): - `--evidence-run-id 0` и `-1`: запрос выполнен (observer_called=[2000]), отказ гласит «lane run collection cannot observe verification-lanes.yml: …» — вина переложена на workflow, а не на аргумент; - `--evidence-artifact verification-evidence-flint`: то же самое; - `--run-limit 0` и `-5`: значение уходит в `gh run list --limit` дословно, а пустой ответ превращается в отказ «cover is incomplete: missing=…» с 256 окнами — оператор идёт передиспатчивать кампанию, которая цела. Закон: координата, которая будет потрачена на сетевой запрос, проверяется до запроса, и отказ называет виновный аргумент. Код возврата назвать его не может — он 64 у всех соседних отказов, поэтому именно на текст и на отсутствие запроса опираются тесты. Allowlist не продублирован: он рендерится из `EVIDENCE_ARTIFACTS_V1`, так что оператор узнаёт допустимый набор из самого отказа. Тот же инвариант рядом (предсуществующий дефект, закрыт этим же срезом): `--mode verification-dispatch` с непозитивным run id или чужим артефактом обходил построитель команд, тот возвращал типизированный отказ, а `main` обходил его циклом как список команд — оператор получал `TypeError: 'ShardCorpusRejectedV1' object is not iterable` вместо причины. Теперь отказ построителя печатается с его собственной причиной и выходит 64. Доказательства (WSL, python3 -m unittest): - RED до реализации: три падения по заявленной причине («--evidence-run-id» не найден в тексте отказа; «--run-limit» не найден в отказе про неполное покрытие) и одна ошибка TypeError в verification-dispatch. - 8 мутантов, каждый убит: M1/M2/M3 — снятие каждой из трёх проверок; M4 — общий текст «requires valid arguments» вместо имени аргумента (убивает все три теста); M5 — проверки перенесены ПОСЛЕ запроса (observed=[2000] != []); M6 — проверка лимита сделана слишком строгой (`>= 0`), убита тестом на допустимые аргументы; M7 — отказ без причины; M8 — снятие защиты от обхода отказа (тот самый TypeError). - Прежний `test_collect_requires_both_evidence_coordinates` заменён: его наблюдатель звал `self.fail`, а `collect_lane_runs_v1` глотает любое исключение наблюдателя в типизированный отказ с тем же кодом 64 — тест не мог упасть по своей заявленной причине. - Набор proof: 433 теста, 3 падения — ровно предсуществующие в test_build (два golden-digest и post_popen_handler_gap), они же падают на нетронутом дереве (429 тестов, те же три). - Контакт с реальностью: после правки все пять случаев дают observer_called=[], ничего не записано, текст называет аргумент. Rust не затронут: изменены три файла в proof/region/v1. * Proof: a saturated listing blames the limit, not the campaign Independent verification found the class this branch claimed closed was closed only at the zero boundary: a positive but truncating --run-limit spent the query and then reported the campaign incomplete — sending the operator to re-dispatch 256 lanes when the fix is one flag. `gh run list --limit N` drops the oldest runs, so a listing that came back exactly at the limit cannot distinguish a real hole from its own truncation. The refusal now names the flag when the listing is saturated, and keeps blaming the campaign when there is room to spare — the anti-vacuity case, because hiding real damage behind the flag would be the opposite failure. Also corrected here: the comment claiming CalledProcessError.__repr__ drops stderr — measured false on the #555 branch, repr carries every constructor argument; stderr is preferred for legibility, not recovery. Verified: 36 dispatch tests pass, both new ones read the refusal text. --------- Co-authored-by: Claude Code <daniilerosov12@gmail.com>
One conflict: two independent blocks — main's collect observation chain and this branch's content admission — claimed the same seam. Both stand, collect first. 113 dispatch tests pass, and again under PYTHONOPTIMIZE=2.
Что это
Четвёртый и последний путь инцидента со 133 мусорными прогонами.
Допуск проверял, что evidence-прогон несёт артефакт с нужным именем, и на этом останавливался. Но полоса пользуется тем, что внутри:
job.binиcomparator-bundle/comparator-manifest-v2.bin. Evidence-прогон, собранный старой версией продюсера с другой раскладкойevidence-out/, проходил допуск и давал 256 полос, каждая из которых падала за секунды на проверке наличия файла.Соразмерность решает: артефакты весят 616 КБ и 16 КБ. Одно скачивание против 256 обречённых прогонов.
Что добавлено
Проверка содержимого перед диспатчем, в уже принятой архитектуре модуля: нечистое наблюдение отдельно, чистое правило (какие пути обязаны быть) отдельно и покрыто тестами, отказ типизированный, любой сбой наблюдения — отказ, а не тихий проход. Временный каталог убирается.
Список требуемых путей — контракт, а не дубликат
Это главное архитектурное решение среза. Пути, которые требует координатор, и пути, которые читает полоса, — одно и то же знание в двух местах, и разъехаться оно не должно.
Закреплено так же, как это уже делается в репозитории для имён артефактов: контрактными тестами, сверяющими константу координатора с текстом воркфлоу полосы.
Доказано мутацией: убрать
comparator-bundleиз списка — и падают два контрактных теста сразу. То есть список не может тихо разойтись с тем, что полоса действительно открывает.Проверки
test_build, воспроизведены на нетронутом дереве.Отношение к остальной стопке
Закрывает класс, явно оставленный открытым в #548 и названный в его теле. Вместе с #552 (провенанс прогона) допуск теперь спрашивает у evidence три вещи вместо одной: кто его произвёл, что он несёт по имени и что внутри.
Откат
Ревертом одного коммита. Единственный видимый эффект — диспатч отказывает там, где раньше молча соглашался.