Core: доверенный сертификат допускается только к спрошенному региону (V5b2d-5b) - #561
Conversation
An intact artifact passed both existing boundaries while being the image of a DIFFERENT region: contextual_region only mints the definition address, and FamilyArtifactLoaderV1 only proves the bytes match the trusted certificate. Nothing asked whether that certificate is about the region the consumer requested. family_definition_binding closes the chain: DefinitionBoundFamilyLoaderV1 takes (pipeline, region, trusted certificate, encoded bytes), compares the asked definition address with the one the certificate carries, and returns a typed ForeignDefinition failure — on the record, before the envelope parse, the payload digest and the image decode. The refusal returns the same owned bytes, so diagnosis needs no refetch. No name-to-certificate table enters the core: the family stays external data. The module registration rebinds the pinned point-support source closure and the clean-set product receipt without any semantic change.
… (V5b2d-5b) Mutation testing of the definition-binding slice left three mutants alive. A partial address comparison survived every test: reducing the guard to `asked.as_bytes()[0] != certified.as_bytes()[0]` (or to the last 31 bytes) still refused the one foreign region the suite used, so nothing established that the address is compared in full. A comparison over a proper part of the address admits a foreign image that collides on exactly that part. `every_byte_of_the_definition_address_is_compared` perturbs each of the 32 positions separately and requires an individual refusal; its transport is empty, which also re-states that the guard refuses before the envelope parse. `FamilyImageCertificateV2::with_definition_digest_for_test` supplies the fabricated addresses without re-encoding a 2 MiB artifact per position. The failure's redacting Debug was untested: making it print the owned transport left every test green, although the sibling transport boundary locks that same invariant. `a_refused_binding_reports_only_its_typed_cause` closes it. `#[derive(PartialEq, Eq)]` on the failure was dead surface -- no consumer compares failures by value, and the crate compiles without it. Kill matrix over the nine mutants (guard inverted, guard disabled, report swapped, cost before guard, first-byte-only, last-31-bytes, cause flattened, bytes reallocated, Debug dumping bytes): no survivors.
Обе стороны добавили доступ к адресу определения. Оставлен один: FamilyImageCertificateV2::definition_digest из main, с указанием, что сверка принадлежит family_definition_binding и является её единственным местом. Guard приведён к единственному входу main: certificate он получает только из FamilyImageCertificateV2::parse_trusted, своего разбора записи не заводит. Тестовый мутатор адреса определения стал когерентным (definition_with_coherent_certificate_for_test): отказ доказывается на записи, которую parse_trusted принимает, а не на недостижимом значении. NoPrematureFamilyArtifactApi расширен на family_definition_binding: срез не открывает поверхность, поэтому гейт не сужается. Цепь аттестаций перегенерирована под новый lib.rs; относительно origin/main сдвинулись только дайджесты над исходником, ни одно число сертификата.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 31 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 (2)
WalkthroughДобавлен внутренний ChangesПривязка family artifact
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
The type's rustdoc promises that any failure hands the same owned bytes back, so a caller never refetches or clones. Only the foreign-definition branch proved it: the artifact branch could reallocate and all 1043 tests stayed green. Shown by injecting exactly that reallocation — red now, at the assertion added here.
|
Независимая проверка нашла выжившего мутанта — закрыт в Дефект. Rustdoc типа Проверка чувствительности. Внёс ровно ту переаллокацию, которую описал проверяющий ( Не раскрытое в теле PR. В диффе есть расширение семи элементов Про doc-тесты, для тех кто будет перепроверять. На этом репозитории |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/labcolors-core/src/family_artifact.rs`:
- Around line 285-304: Вынесите повторяющийся пересчёт когерентности из
definition_with_coherent_certificate_for_test,
image_mismatch_with_coherent_certificate_for_test и
member_count_with_coherent_certificate_for_test в единый приватный #[cfg(test)]
helper. Helper должен последовательно пересчитывать semantic_release, затем
artifact_receipt, а каждый из трёх мутирующих методов должен вызывать его после
изменения соответствующего поля.
In `@crates/labcolors-core/src/family_definition_binding_tests.rs`:
- Around line 162-164: Замените жёстко заданную границу 32 в цикле внутри теста
с bytes на значение, полученное непосредственно из asked или
FamilyDefinitionDigestV2, чтобы проверялась каждая позиция digest независимо от
его текущей ширины.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1fdbfe7c-c791-46c6-86d7-4787e6017828
📒 Files selected for processing (9)
crates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.jsoncrates/labcolors-core/contracts/clean-set-srgb8-v1/receipt-v1.sha256crates/labcolors-core/contracts/point-support-reference-surplus-q55-bps-proof-v1.jsoncrates/labcolors-core/src/contextual_region_tests.rscrates/labcolors-core/src/family_artifact.rscrates/labcolors-core/src/family_definition_binding.rscrates/labcolors-core/src/family_definition_binding_tests.rscrates/labcolors-core/src/lib.rsscripts/verify_point_support_surplus.py
Three test mutators repeated the same two-step block, and the order in it is mandatory: the receipt covers `semantic_release`, so recomputing the release second yields a record `parse_trusted` refuses — and a test built on it would prove a refusal on an input the loader can never see. A fourth mutator repeating the block wrongly is exactly the trap; one helper makes it unrepresentable. Proven by inverting the order inside the helper: three tests go red. The digest-width loop now takes its bound from the value. With the literal, a narrower or wider digest would silently leave positions uncompared while the test stayed green. Verified: 1049 core tests, fmt and clippy clean.
|
Оба замечания приняты и закрыты в Пересчёт когерентности вынесен в один Граница цикла берётся у самого значения. С литералом смена ширины digest молча оставила бы часть позиций несравнёнными при зелёном тесте. Прогнано: 1049 тестов ядра, |
Оба замечания закрыты в 0c989be: пересчёт когерентности в одном helper (обязательность порядка доказана — перестановка роняет три теста), граница цикла выведена из ширины digest. Ответ в треде PR.
Что это
Доверенный сертификат семейства доказывает, что артефакт цел. Он не доказывает, что артефакт — образ того региона, который спросили. Целый артефакт ДРУГОГО региона проходит и
contextual_region, иfamily_artifact: обе границы отвечают на свой вопрос и ни одна — на этот.Срез добавляет
crates/labcolors-core/src/family_definition_binding.rs— единственное место, где адрес спрошенного определения сверяется с адресом в сертификате:asked = ContextualRegionFamilyProviderV1::definition_digest(pipeline, region)противcertified = certificate.definition_digest(). Расхождение — типизированный отказForeignDefinition { asked, certified }, возвращающий те же owned bytes (без refetch и clone).Инвариант против рецептов
Таблицы «имя → семейство» нет и не появилось. Модуль сравнивает два content address и не хранит ни одного имени семейства:
pubэлементов — вся поверхностьpub(crate)(6 элементов);expect(dead_code)и меткаdebug_struct; ни одного имени семейства;Это ровно второе условие открытия слоя из
NoPrematureFamilyArtifactApi— ядро владеет механизмом проверки, а не перечнем.Один путь, а не два источника истины
main принёс
FamilyImageCertificateV2::parse_trusted— единственный вход, которым потребитель предъявляет доверенную 254-байтную запись. Срез приведён к нему:definition_digest(); оставлен один (версия main), с указанием, что сверка принадлежитfamily_definition_bindingи является её единственным местом. Текстовый auto-merge молча собрал два одинаковых метода — это поймано и снято;FamilyImageCertificateV2только изparse_trusted, своего разбора записи не заводит;parse_trustedи пинится равенством с minted-сертификатом, чтобы не стать вторым источником;definition_with_coherent_certificate_for_test: пересчитывает semantic release и receipt), поэтому отказ доказывается на записи, которуюparse_trustedпринимает, а не на недостижимом значении.Порядок отказа: чем именно доказан
Чужой регион отвергается до дорогой работы. Доказательство — не рассуждение, а два независимых наблюдения:
FAMILY_ARTIFACT_PAYLOAD_DIGEST_CALLSравен0после отказа на валидном (целом) артефакте чужого региона размером 2 MiB. Anti-vacuity в том же тесте: при совпавшем адресе тот же счётчик становится1— то есть счётчик действительно наблюдает эту работу.Vec::new()): отказ наступает раньше, чем появляется что-либо, что можно разобрать, захешировать или декодировать.Обход образа (
raw_bitmap24::verify_image) вverify_bound_envelopeидёт строго ПОСЛЕpayload_digest, поэтому ноль на счётчике закрывает и его.Честная оговорка:
parse_trustedвыполняет два SHA-256 фиксированного размера (над 254-байтной записью и над semantic-тройкой) ещё до guard-а. Это цена получения сертификата вообще, она константна и не зависит от размера артефакта; «дорогая работа», о которой идёт речь, — это 2 MiB payload.Гейт
NoPrematureFamilyArtifactApiГейт не сужен. Срез не открывает поверхность (модуль приватный, элементы
pub(crate)), поэтому гейт РАСШИРЕН на новый модуль: было 6 записейcompile_fail, стало 8. В rustdoc записано, какое из двух условий затронуто: срез двигает второе (полнота механизма), первое (ядро не аутентифицирует запись) им не затрагивается и остаётся невыполненным — слой закрыт целиком.Проверено отдельно:
family_definition_bindingотсутствует в разрешённой публичной документации (0 файлов вtarget/doc/labcolors_core),verify_program_public_surface— PASS,public_items=152.Мутации: каждый тест убивает мутацию своего инварианта
Все 8 мутаций убиты, выживших нет.
if asked != certified→if false)every_byte_…,a_refused_binding_reports_only_its_typed_cause,an_intact_artifact_of_another_region_is_refused_by_type,a_foreign_definition_is_refused_before_any_payload_workevery_byte_of_the_definition_address_is_compareda_foreign_definition_is_refused_before_any_payload_workDebugпечатает transport-байтыa_refused_binding_reports_only_its_typed_causean_intact_artifact_of_another_region_is_refused_by_typethe_asked_definition_does_not_weaken_the_transport_contractevery_byte_of_the_definition_address_is_comparedpub mod+pub struct)NoPrematureFamilyArtifactApi(line 513) и (line 517)M8 — доказательство того, что две новые записи гейта не вакуумные: при публикации модуля они краснеют.
Аттестации
Мерж трогает
crates/labcolors-core/src/lib.rs, поэтому цепь перегенерирована целиком:receipt-v1.json→receipt-v1.sha256→EXPECTED_SOURCE_CAPSULE_SHA256→--emitпроверяемого proof.Перед регенерацией оба верификатора были прогнаны и упали — то есть они действительно наблюдают
lib.rs, а не проходят по инерции.Сверка поле-за-полем с
origin/main(плоское сравнение всех ключей):point-support-…-proof-v1.json: изменено ровно 4 поля —source_files[9].sha256(этоlib.rs),source_closure_sha256,verifier_sha256,proof_payload_sha256. Все четыре — дайджесты над исходником;receipt-v1.json: изменено ровно 2 поля —artifacts[6].bytesиartifacts[6].sha256(этоlib.rs);checks,q55_scale,drop_domain_inclusive,i128_max,anchor_identity_checksи прочие — без изменений).Итог:
PRODUCT_IDENTITY_VERIFIEDиPASS.Проверки (что реально запущено)
Сборка в WSL, toolchain
+stable, послеcargo clean -p labcolors-core(полная пересборка):cargo test --workspace --locked— зелено, ни одной строкиFAILED;labcolors-core1043 passed / 7 ignored;cargo test -p labcolors-core --locked --doc— 41 passed, включая все 8 записейNoPrematureFamilyArtifactApi;cargo fmt --all --check— OK;cargo clippy --workspace --all-targets --locked -- -D warnings— OK;RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked— OK;verify_clean_set_receipt.py product—PRODUCT_IDENTITY_VERIFIED, плюсtest_verify_clean_set_receipt.py(31 тест) — OK;verify_point_support_surplus.py—PASS, плюсtest_point_support_surplus_source_binding.py(5 тестов) — OK;test_program_public_surface.py+verify_program_public_surface.py— PASS.Отдельно стоит записать: первый прогон doc-тестов ПОСЛЕ мутационного стенда показал 2 падения именно на новых записях гейта. Это оказался устаревший артефакт сборки от мутации M8, а не регрессия: после
cargo clean -p labcolors-core— 41/41 зелено при исходнике сmod(приватный) иpub(crate) struct. Числа выше сняты с чистой пересборки.Что НЕ доказано и не закрыто
FamilyArtifactLoaderV1::loadможно вызвать напрямую, минуя guard. Снаружи это недостижимо (весь слой приватен), но принудительного «единственного пути» на уровне типов нет. Cutover, делающий связанный путь единственным, принадлежит срезу публичного семейного шва (v5b2d-5c), а не этому.FAMILY_ARTIFACT_DECODER_CALLSинкрементируется только в fixture-кодеке; путьraw_bitmap24инструментирован лишь счётчиком хеширования payload. Утверждение о том, что обход образа не выполнялся, опирается на счётчик payload плюс порядок внутриverify_bound_envelope, а не на отдельный счётчик декодера.Откат
Срез изолирован и обратим:
git revert -m 1 <merge-commit>либо просто не мержить PR; ничего внеlabcolors-coreи цепи аттестаций не затронуто;family_definition_bindingприватен и не имеет вызывающих в production-путях (expect(dead_code)до cutover), поэтому откат не может сломать потребителя;mod family_definition_binding;иmod family_definition_binding_tests;, сам модуль, два новыхcompile_failв гейте иdefinition_with_coherent_certificate_for_test, затем обязательно перегенерировать цепь аттестаций (lib.rsменяется) и прогнать оба верификатора.Summary by CodeRabbit
Новые возможности
Исправления
Проверки