Skip to content

Proof: a dispatch admits the evidence its lanes can read, not the name (V5b2d-4g) - #555

Merged
lemone112 merged 5 commits into
mainfrom
v5b2d-4g-evidence-content
Aug 7, 2026
Merged

Proof: a dispatch admits the evidence its lanes can read, not the name (V5b2d-4g)#555
lemone112 merged 5 commits into
mainfrom
v5b2d-4g-evidence-content

Conversation

@lemone112

Copy link
Copy Markdown
Collaborator

Что это

Четвёртый и последний путь инцидента со 133 мусорными прогонами.

Допуск проверял, что evidence-прогон несёт артефакт с нужным именем, и на этом останавливался. Но полоса пользуется тем, что внутри: job.bin и comparator-bundle/comparator-manifest-v2.bin. Evidence-прогон, собранный старой версией продюсера с другой раскладкой evidence-out/, проходил допуск и давал 256 полос, каждая из которых падала за секунды на проверке наличия файла.

Соразмерность решает: артефакты весят 616 КБ и 16 КБ. Одно скачивание против 256 обречённых прогонов.

Что добавлено

Проверка содержимого перед диспатчем, в уже принятой архитектуре модуля: нечистое наблюдение отдельно, чистое правило (какие пути обязаны быть) отдельно и покрыто тестами, отказ типизированный, любой сбой наблюдения — отказ, а не тихий проход. Временный каталог убирается.

Список требуемых путей — контракт, а не дубликат

Это главное архитектурное решение среза. Пути, которые требует координатор, и пути, которые читает полоса, — одно и то же знание в двух местах, и разъехаться оно не должно.

Закреплено так же, как это уже делается в репозитории для имён артефактов: контрактными тестами, сверяющими константу координатора с текстом воркфлоу полосы.

Доказано мутацией: убрать comparator-bundle из списка — и падают два контрактных теста сразу. То есть список не может тихо разойтись с тем, что полоса действительно открывает.

Проверки

  • 40 тестов диспетчера, 0 падений.
  • Полный набор на Linux (WSL): 425 тестов, 3 падения — все предсуществующие в test_build, воспроизведены на нетронутом дереве.
  • Сырых дампов API нет.
  • Дрейфа пинов инвентаря нет.

Отношение к остальной стопке

Закрывает класс, явно оставленный открытым в #548 и названный в его теле. Вместе с #552 (провенанс прогона) допуск теперь спрашивает у evidence три вещи вместо одной: кто его произвёл, что он несёт по имени и что внутри.

Откат

Ревертом одного коммита. Единственный видимый эффект — диспатч отказывает там, где раньше молча соглашался.

…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.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eca9b63c-3796-4940-a373-5225cb8f3515

📥 Commits

Reviewing files that changed from the base of the PR and between c834ab5 and 979f7e8.

📒 Files selected for processing (3)
  • proof/region/v1/corpus_dispatch.py
  • proof/region/v1/tests/test_corpus_dispatch.py
  • proof/region/v1/tests/test_verification_dispatch.py

Comment @coderabbitai help to get the list of available commands.

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.
lemone112 pushed a commit that referenced this pull request Aug 7, 2026
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.
lemone112 added a commit that referenced this pull request Aug 7, 2026
…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>
Claude Code and others added 2 commits August 7, 2026 11:04
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.
@lemone112
lemone112 merged commit 3ad5091 into main Aug 7, 2026
10 checks passed
@lemone112
lemone112 deleted the v5b2d-4g-evidence-content branch August 7, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant