Proof: one process that mints both receipts is the only place a dual proof can be sealed (V5b2d-4d) - #549
Conversation
…proof can be sealed (V5b2d-4d) `join_dual_proof_v1` needs five evidence chains, and two of them — the engines' source-bound receipts — have no wire form by design: a receipt that could be parsed from bytes would let foreign code mint provenance. So the join has no valid two-job topology at all; it can only happen inside a process that minted both receipts itself. This adds that process. `tests/dual_proof_gate.py` builds and runs both engines over the full manifest back to back, re-seals each engine's semantic receipt from the verification lanes, and seals the dual proof. The lane cover comes from an earlier run and admits here only because a lane binds the comparator's source identity (#547); the same identity sorts the cover by engine, so nothing trusts an artifact's name. Both engine lane modules now expose `seal_full_domain_receipt_v1`, so the gate mints the same receipt those lanes do instead of restating the build coordinates — two copies would drift, and the drift would surface as an unrelated admission failure an hour into a native run. The gate fails, never skips, on an incomplete environment: it is only ever invoked deliberately, so a silent pass would be a proof that did not happen. Measured, not assumed: - both engines' source locks pin byte-identical GMP and MPFR, so one job can serve both without an env collision; a future divergence exits 64 - the native RUNs took 63 min (Arb) and 43 min (MPFI) on run 31116022208, so the sequential pair fits one job's envelope - executor cgroups are named per process with a counter and removed on close, so two sequential executions do not collide `test_decorator_placement.py` closes the class of defect this change itself introduced: extracting the helper put it between the module's env gate and the class that gate guarded, which silently made the helper uncallable and left the native test ungated. Syntax checks and the fast suite both saw nothing — the lane modules live outside the `test_*.py` inventory. The new gate reads the tree as source, like the arity gate beside it.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 43 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 (12)
Comment |
|
Два независимых ревью финального состояния нашли блокеры. Перевожу в черновик: в текущем виде воркфлоу не может запечатать доказательство ни разу. Блокер 1 — containment. После первого движка процесс навсегда остаётся в Блокер 2 — доставка покрытия. Полоса = один dispatch = один прогон = один артефакт, то есть покрытие 2^24 распределено по 512 прогонам, а Прочее: тавтологичная антивакуумная проверка (identity квитанции не кодирует покрытие вообще), неполный предполётный список переменных, непроверенный union-возврат Чиню; переоткрою после исправлений и повторного независимого ревью. |
…review
Two independent reviews of the final state found that the job this branch
added could never have sealed anything. Both are fixed here.
CONTAINMENT. The controller that observes a BUILD to RUN enters the observer
group and stays there: non-test code writes `cgroup.procs` in exactly two
places — the observer placement and the executed child's attach — and neither
walks it back. With one shared subtree at `pids.max=2`, the second engine's
BUILD forks docker into a saturated budget and dies, an hour into the run.
The single-engine lanes never met this because `arb.yml` puts the engines in
separate steps and `full-domain-run.yml` in separate jobs; one process for
both engines happens first here, and the dual proof admits no other topology.
So each engine now gets its own observer subtree, and `enter_task_cgroup_v1`
returns the controller to the unconstrained group between them — where a
fresh process would have started. No run's containment widens: the observer
budget still governs each execution.
DELIVERY. A lane is one dispatch, one run, one artifact, so a full-domain
cover is spread across 512 runs while the job read a single `run-id` — and
the input was declared `lane_run_ids` but read as `lane_run_id`. Worse, lane
artifacts carried no engine in their name, so an Arb lane and its MPFI twin
claimed one name and could not coexist under one root. The cover now spans
many runs through the shape `full-domain-corpus.yml` already uses, and the
artifact name carries the engine. Which engine a lane serves is still
decided by the comparator source identity in its manifest, never by a name.
A cover that is short, overlapping, or single-engine is now refused in
seconds, before the first build, instead of after both native runs.
Also from review:
- the anti-vacuity check was theatre: receipt identity seals job,
comparator, run and transcript and never the cover, so two engines could
not collide in it by construction. Replaced with the property it claimed
— the two covers are disjoint sets of lane directories.
- the preflight listed 4 of the 10 variables the seal functions read, so a
partial environment surfaced as a bare KeyError instead of the refusal
this gate promises.
- `seal_full_domain_receipt_v1` returns a union; an unchecked rejection
became `AttributeError` two hours in, losing the reason. Typed now.
- the gate skipped the hostile `replay_evidence_is_well_bound_v1` check its
single-engine lanes run, and this is the only place a full-domain receipt
is ever sealed. Restored, with the transcript's domain and comparator
coordinates.
- module resolution across the engines' five colliding stems was correct
only by the order of two loads. Asserted instead of assumed.
Verified: 260 local tests, 7 errors — the exact Windows baseline, unchanged.
The new `enter_task_cgroup_v1` tests cannot run here at all: `test_executor`
needs `fcntl`. CI is the only place they execute.
The dispatch workflows are pinned by the coordinates they must carry, which catches a missing coordinate but not a misspelt one — and a misspelt one is what happened: the dual-proof input was declared `lane_run_ids` and read as `lane_run_id`. GitHub expands an undeclared input to the empty string, so nothing fails until the job has run. On this job that is two hours of native execution before noticing the lane cover was empty. `test_every_referenced_input_is_declared` closes the class for every workflow at once, reading the declarations by indentation rather than parsing: the proof tree carries no YAML dependency. Proven red on the real drift, not a mock — reintroducing exactly `inputs.lane_run_id` into the live workflow makes the gate name it undeclared, and the fixed file is clean. It covers three workflows and eleven references today, with an anti-vacuity floor so a gate that stopped finding anything cannot pass quietly. `DualProofContainmentContractTests` pins the two properties whose absence made the job unrunnable: each engine owns its observer subtree (a shared one admits two tasks, and the second engine's BUILD forks into it), and the cover is gathered from many runs rather than one `run-id`. It also pins that the cover is checked before the first build, so the cheap refusal cannot drift back behind the expensive work.
Re-review of the final state found that the previous commit would have failed
CI deterministically and that one of its own repairs repeated the defect it
replaced.
THE RED. `arb/tests/gate.py` pins the exact inventory of the fast suite, and
that suite includes `tests/test_executor.py` through its shared patterns.
Adding three tests there moved the hash and left the pin behind, so the first
step of `arb.yml` would have printed inventory drift and returned 1 before
building anything. Recomputed independently with an AST replica of the gate's
own algorithm — the replica reproduces the parent commit's pinned value byte
for byte, which is what makes its answer for HEAD trustworthy: 270 tests,
b1bc948c… None of this is observable here; the gate needs `fcntl`, so the
machine that wrote the drift could not run the check that catches it.
THE SECOND TAUTOLOGY. Replacing the theatre assert with "the two covers are
disjoint" was theatre again: `_lane_cover_v1` partitions by comparator source
identity, and the two engines cannot share one — the sets are disjoint by
construction, and the line could not fail in any state the gate reaches. What
is genuinely unguaranteed is that the partition consumed the whole cover: a
lane of a third identity is foreign to both engines and disappears silently.
The union of both covers must now equal every lane directory present.
Also from review:
- the MPFI hostile check was vacuous — that receipt's constructor already
requires the predicate, so the assert could not fail. Only Arb's remains,
where the constructor binds something narrower. The previous commit
claimed to restore both; it restored one.
- `enter_task_cgroup_v1` took the caller's word for where it landed. The
observer placement is always followed by a budget probe; this one now
verifies its own post-condition, because the path arrives from
configuration and a wrong one silently moves where the next BUILD runs.
- the module-collision guard treated an absent import as safety. Absence
retires the check instead of failing it, so it is a refusal now.
- PROTOCOL.md still called the observer placement the only versioned
cross-module placement operation. There are two, and the second is
documented with why it cannot widen a run's containment.
Verified: 265 local tests, 7 errors — the exact Windows baseline, unchanged.
The recomputed inventory now equals the pin.
A full-domain cover is 512 separate runs, and the dual proof needs their ids. Without a name carrying the coordinates the only way to collect them is a guess about creation times, which breaks the moment two campaigns overlap. The run name makes the list a query — and makes a lane identifiable in the UI, where 512 identically-named runs are otherwise indistinguishable.
… does The post-condition added a moment earlier compared paths by spelling, while every other placement check in this module compares by inode — `fstat` on both descriptors — precisely because one group can be reached by more than one name. A false refusal here is not a cheap failure: it aborts a job two hours into two native runs, which is the same cost as having no check at all.
…howed The last two rounds shipped a red CI each because the checks that catch inventory drift cannot run on Windows — and WSL, a real Linux where this whole layer imports, was available the entire time. Everything below was measured by running it there, not derived. MY OWN TEST WAS RED. The post-condition added last round refuses when the process is not in the named group, and the test I wrote for that placement builds a fake cgroup in a temporary directory — where no fixture can move this process. It now patches the current-cgroup reader the way the observer probe test beside it already does, and the post-condition finally has a test that proves it fires: an invariant added without one was exactly the omission the reviews keep finding. THE SECOND PIN. `tests/test_build.py` keeps an independent outer oracle of the same inventory — count, inventory digest and discovery-order digest — on purpose, so a coordinated edit to the gate cannot hide drift. Updating only the gate left it stale, and the oracle did its job by failing. All three constants now carry values the gate actually printed on Linux: 271 tests, inventory 86c723aa…, order fe5a3419… The order digest is over discovery order and cannot honestly be derived by hand; it was read off a run. Both gates verified green there: the fast Arb gate reports 271 tests with its exact 15-skip manifest, and the outer oracle passes. PROTOCOL.md's new paragraph was mechanically spliced — a 200-column line, an orphaned clause whose "this descriptor protocol" had drifted onto the wrong subject, and wording describing a string comparison the code no longer does. Rewritten as its own paragraph. Verified on Linux: 419 tests, 3 failures. All three are pre-existing and mine only by proximity — I reproduced them on a worktree whose proof tree is untouched `main`, where the same three fail identically. Windows still gives 265 tests and 7 load errors; that is the platform's ceiling, not a result.
|
Оба блокера прошлого ревью закрыты, поэтому вывожу из черновика. Что именно проверено и чем: Блокер 1 — мой собственный тест был красным. Пост-условие Блокер 2 — второй пин инвентаря. Как проверено. Ключевое: эти два гейта на Windows не запускаются вовсе (нужен
Незакрытый гейт называю прямо: независимого ревью состояния после |
…ded last The cover is gathered from many runs into one directory, so a re-run of the same window overwrites the evidence already there. The result still looks exact — the window is present once, the arithmetic adds up — while one of two answers silently won, and nothing records which. Each run now lands in its own staging directory and the move into the flat cover refuses a name that is already taken. Lane run ids are validated as numbers, the way `verification-lanes.yml` already validates its own: an element starting with `-` would be read by `gh` as a flag rather than a run. Both are pinned by the workflow contract tests, so they cannot drift back. Verified: 7 contract tests pass; the workflow still parses to 11 steps. Left open and named rather than hidden: the cheap preflight proves the cover is two exact domains, but not that those two identities belong to the engines this job is about to build. Closing that needs the comparator's source identity derived from the checkout without building — the coordinates are all static, but no such derivation is exposed today.
Что это
Механизм критерия выхода V5b2d №3: место, где полнодоменное дуальное доказательство вообще может быть запечатано.
join_dual_proof_v1принимает пять цепей доказательств. Две из них — source-bound квитанции движков — не имеют wire-формы намеренно: квитанция, разбираемая из байтов, позволила бы чужому коду начеканить провенанс. Отсюда следует, что валидной двух-job топологии не существует в принципе: join возможен только внутри процесса, который сам начеканил обе квитанции.Этот PR добавляет такой процесс.
Состав
proof/region/v1/tests/dual_proof_gate.py— собирает и запускает оба движка над полным манифестом подряд, пересобирает семантические квитанции из полос верификации и печатает дуальное доказательство..github/workflows/dual-proof.yml— один job под этот гейт: объединённое замыкание исходников, оба OCI-манифеста, один делегированный cgroup, покрытие полос из указанного прогона.seal_full_domain_receipt_v1в обоих модулях полос — гейт чеканит ту же квитанцию, что и полоса, вместо повторного описания координат сборки. Две копии разошлись бы, и расхождение вылезло бы как посторонний отказ допуска через час нативного прогона.proof/region/v1/tests/test_decorator_placement.py— гейт против класса дефекта, который эта же правка и внесла (см. ниже).Почему полосы прошлого прогона допускаются свежими квитанциями
Ровно из-за #547: полоса связывает source-идентичность компаратора, а она воспроизводится между раннерами. Полная идентичность сворачивает наблюдение сборки и не воспроизводится — полоса, связанная с ней, умерла бы вместе с прогоном, давшим ей evidence.
Та же source-идентичность разделяет покрытие по движкам: принадлежность полосы выводится из её манифеста, а не из имени артефакта. Первая версия читала выдуманный ключ
comparator_kind, которого в схеме нет.Замерено, а не предположено
labcolors-executor-{pid}-{счётчик}и снимается вclose()Гейт не умеет проходить молча
При неполной среде тест падает, а не пропускается: модуль вызывается только намеренно, поэтому тихий проход означал бы доказательство, которого не было. Проверено локально — выход 1 с перечнем недостающих переменных.
Дефект, который эта же правка внесла, и закрытый класс
Вынос помощника поставил его между
@unittest.skipUnlessи классом, который этот декоратор охранял. Разом два дефекта: помощник стал невызываемым (любой вызов →SkipTest), а нативный тест лишился гейта, не пускавшего его в среду, которая его не тянет.Ни проверка синтаксиса, ни быстрый набор этого не увидели — модули полос лежат вне инвентаря
test_*.py. Всплыло бы только отказом в диспатченном job'е через час.test_decorator_placement.pyчитает дерево как исходники (как соседний гейт арности) и запрещаетunittest.skip*на модульной функции. Доказательство чувствительности: гейт зелёный на исправленном дереве и находит ровно этот дефект на реконструкции реального сломанного файла.Проверки
fcntl). До правки было 258/7. Новых ошибок нет.Чего этот PR НЕ делает
Не запускает дуальное доказательство. Для этого нужны 512 полос верификации против evidence-прогона; они ещё не диспатчены. Этот PR даёт механизм, а не результат.
Откат
Ревертом одного коммита. Ничего существующего не переиспользуется, кроме двух вынесенных функций; их прежние вызывающие — те же тесты полос, покрытые быстрым набором.