Skip to content

P1: перевести Pair на общий joint graph (генерик joint) - #372

Merged
lemone112 merged 4 commits into
mainfrom
claude/color-engine-semantic-drift-jadrtr
Jul 21, 2026
Merged

P1: перевести Pair на общий joint graph (генерик joint)#372
lemone112 merged 4 commits into
mainfrom
claude/color-engine-semantic-drift-jadrtr

Conversation

@lemone112

@lemone112 lemone112 commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Что это

Чистый P1 поверх C8c: лоуверит Pair в общий генерик joint graph.

fill Paint + page Surface → fill-on-page Occurrence → surfaceFrom = emitted fill Surface
label Paint + emitted fill Surface → label-on-fill Occurrence

Ключевое исправление относительно #365/#366

joint остаётся генерик-solver'ом (Pointwise*<E>, инстанс ExactSrgb8IdentityV1) и НЕ импортирует WCAG-specific payload — по V2a exit-критерию «production joint не импортирует WCAG payload». Прежняя попытка зашивала JointHardConstraintV1::Wcag22 прямо в ядро solver'а — это дрейф семантики в core; здесь он убран. Readability-floor label остаётся в замороженном semantic/legacy слое до F1/R1, а не в generic-ядре.

Дизайн взят из #370 (agent/p1-correct-rebuild @ 08b2e828, старый агент); здесь доведён до зелёного на моей ветке.

Инварианты

  • PairSide / pair_fill / resolve_pair_label и pair-физика удалены;
  • обе роли решаются одним point-компоситором и одним joint hard-report через lower → surfaceFrom → upper;
  • public Rust/WASM/TypeScript/FFI surface без изменений; .github и Swift native-conformance gate целы;
  • различие старых PairFill/PairLabel поверхностей намеренно не сохраняется (SSOT-дефект).

Согласовано с DAG

V2a + C8b → P1; предпосылки G1a/V1a/V2a/C8b в main. WCAG-в-joint убран строго по V2a exit-критерию.

Статус

Локально зелёное: fmt --all --check, clippy --workspace --all-targets -D warnings, 19 core test-бинарников, workspace build (core/wasm/ffi/conformance). WASM size-budget переизмеряется на CI и будет записан отдельным коммитом.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LzZyjuo5ahzmW6V8pPJqSJ

Summary by CodeRabbit

  • Новые возможности

    • PairFill теперь использует точный авторский цвет с полной непрозрачностью.
    • PairLabel подбирается с учётом фактически отображаемой поверхности заливки и проверяется на соответствие WCAG-контрасту.
    • Поведение парных бейджей стало единообразным для разных условий просмотра.
  • Исправления

    • Некорректные значения прозрачности для PairLabel теперь отклоняются при проверке конфигурации.
    • Обновлены эталонные цвета заливок брендовых и статусных бейджей.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • packages/colors/test/wasm-boundary.golden.json
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 39518761-e2ce-4872-8222-7bbc0a3de64e

📥 Commits

Reviewing files that changed from the base of the PR and between d0beb8a and 07ff7c5.

📒 Files selected for processing (1)
  • packages/colors/test/wasm-boundary.golden.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

PairFill и PairLabel переведены на frozen frontend с единой pointwise joint-цепочкой, WCAG 2.2 constraints и fresh recheck. Удалены старые pair-side и H-K production-пути, обновлены валидация, тесты, golden outputs и WASM budget metadata.

Changes

Единый Pair frontend

Layer / File(s) Summary
Pointwise joint evaluator и recheck
crates/labcolors-core/src/constraints/*, crates/labcolors-core/src/joint.rs, crates/labcolors-core/src/joint_tests.rs
Joint-исполнение параметризовано evaluator и observation; добавлены WCAG constraints, generic reports, ошибки evaluator и fresh recheck.
Pair lowering и semantic wiring
crates/labcolors-core/src/pair.rs, crates/labcolors-core/src/semantic.rs, crates/labcolors-core/src/config.rs
PairFill эмитирует opaque source occurrence, PairLabel строит candidates относительно emitted PairFill Surface и проходит joint selection с проверкой evidence.
Acceptance-тесты и golden outputs
crates/labcolors-core/src/pair_label_tests.rs, crates/labcolors-core/src/config/tests.rs, crates/labcolors-core/tests/data/*
Проверены opaque fill, WCAG selection, общая emitted Surface, независимость имён и отклонение non-opaque alpha; значения badge-fill обновлены.
Удаление устаревших H-K и pair-side путей
crates/labcolors-core/src/lpc.rs, crates/labcolors-core/src/exposure_support.rs, crates/labcolors-core/src/solve.rs, crates/labcolors-core/tests/empirical_inventory.rs, docs/*
H-K helpers ограничены тестовой сборкой или удалены, PAIR_CROSSOVER_Y исключён из кода и инвентаря, сопровождающие описания обновлены.
Release metadata
packages/colors/bench/wasm.json, scripts/check-wasm-size-budget.mjs
Измерение WASM budget и ожидаемый SHA-256 синхронизированы с новой контрольной точкой.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ThemeConfig
  participant SemanticResolver
  participant PairFrontend
  participant PointwiseJointPointProgramV1
  participant JointPointEvaluatorV1
  ThemeConfig->>SemanticResolver: validates PairLabel opaque alpha
  SemanticResolver->>PairFrontend: lowers PairFill and PairLabel
  PairFrontend->>PointwiseJointPointProgramV1: builds candidates and constraints
  PointwiseJointPointProgramV1->>JointPointEvaluatorV1: assesses visible occurrences
  JointPointEvaluatorV1-->>PointwiseJointPointProgramV1: returns hard decisions
  PointwiseJointPointProgramV1-->>PairFrontend: returns feasible selection
  PairFrontend-->>SemanticResolver: returns verified emitted paints
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Заголовок точно отражает основное изменение: перевод Pair на общий generic joint graph.
Docstring Coverage ✅ Passed Docstring coverage is 98.98% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/color-engine-semantic-drift-jadrtr

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/solve.rs`:
- Line 228: Update the documentation comment in solve.rs around the `bg_luma`
reference to remove the broken intra-doc link, replacing it with plain text or a
valid existing symbol while preserving the surrounding explanation.

In `@docs/decisions/0003-hk-scope.md`:
- Around line 19-20: В разделе решения про Pair после P1 замените англоязычные
термины «fill/label occurrences», «WCAG constraints» и «point graph algebra» на
русские эквиваленты, сохранив исходный смысл утверждения и остальную
формулировку без изменений.
🪄 Autofix (Beta)

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: 5361b744-8aba-4272-811f-b5aabf7c9824

📥 Commits

Reviewing files that changed from the base of the PR and between b023def and c61f990.

📒 Files selected for processing (18)
  • crates/labcolors-core/src/agnostic_gates.rs
  • crates/labcolors-core/src/appearance.rs
  • crates/labcolors-core/src/config.rs
  • crates/labcolors-core/src/config/tests.rs
  • crates/labcolors-core/src/constraints/mod.rs
  • crates/labcolors-core/src/constraints/wcag22.rs
  • crates/labcolors-core/src/exposure_support.rs
  • crates/labcolors-core/src/joint.rs
  • crates/labcolors-core/src/joint_tests.rs
  • crates/labcolors-core/src/lib.rs
  • crates/labcolors-core/src/lpc.rs
  • crates/labcolors-core/src/pair.rs
  • crates/labcolors-core/src/pair_label_tests.rs
  • crates/labcolors-core/src/semantic.rs
  • crates/labcolors-core/src/solve.rs
  • crates/labcolors-core/tests/data/labui_emission_golden.txt
  • docs/decisions/0003-hk-scope.md
  • docs/empirical-inventory.md
💤 Files with no reviewable changes (3)
  • crates/labcolors-core/src/appearance.rs
  • docs/empirical-inventory.md
  • crates/labcolors-core/src/exposure_support.rs

Comment thread crates/labcolors-core/src/solve.rs Outdated
Comment thread docs/decisions/0003-hk-scope.md Outdated
Comment on lines +19 to +20
- Pair после P1 не использует H-K: его fill/label occurrences и WCAG constraints
исполняются общей point graph algebra. H-K не влияет на скрытый выбор стороны.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Замените англоязычные описательные термины русскими.

Фрагмент содержит fill/label occurrences, WCAG constraints и point graph algebra, что нарушает правило русской терминологии для docs/**.

Предлагаемая формулировка
-- Pair после P1 не использует H-K: его fill/label occurrences и WCAG constraints
-  исполняются общей point graph algebra. H-K не влияет на скрытый выбор стороны.
+- После P1 путь Pair не использует H-K: его вхождения заливки и метки, а также
+  ограничения WCAG исполняются общей алгеброй графа точек. H-K не влияет на
+  скрытый выбор стороны.

As per path instructions, документация в docs/** должна быть на русском и проверяться на терминологию без англицизмов.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Pair после P1 не использует H-K: его fill/label occurrences и WCAG constraints
исполняются общей point graph algebra. H-K не влияет на скрытый выбор стороны.
- После P1 путь Pair не использует H-K: его вхождения заливки и метки, а также
ограничения WCAG исполняются общей алгеброй графа точек. H-K не влияет на
скрытый выбор стороны.
🤖 Prompt for 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.

In `@docs/decisions/0003-hk-scope.md` around lines 19 - 20, В разделе решения про
Pair после P1 замените англоязычные термины «fill/label occurrences», «WCAG
constraints» и «point graph algebra» на русские эквиваленты, сохранив исходный
смысл утверждения и остальную формулировку без изменений.

Source: Path instructions

Заменяет предыдущую попытку (WCAG-в-joint) корректным по DAG дизайном из #370
(ветка agent/p1-correct-rebuild @ 08b2e82, старый агент). Joint остаётся
генерик-solver'ом (Pointwise*<E>, инстанс ExactSrgb8IdentityV1) и НЕ импортирует
WCAG-specific payload — по V2a exit-критерию «production joint не импортирует
WCAG payload». Readability-floor label остаётся в замороженном semantic/legacy
слое до F1/R1, а не зашивается в ядро solver'а.

- fill Paint -> fill-on-page occurrence -> surfaceFrom -> label-on-fill occurrence;
- PairSide / pair_fill / resolve_pair_label и pair-физика удалены;
- public Rust/WASM/TS/FFI surface без изменений; .github и Swift-гейт не тронуты.

Локально: fmt --all --check, clippy --all-targets -D warnings, 19 core
test-бинарников и workspace build — зелёные.

WASM size-budget сброшен на baseline (386547): размер дизайна #370 будет
переизмерен на CI и записан отдельным коммитом (raw локально 875313 vs baseline
831652 — на ~6KB меньше прежнего WCAG-в-joint варианта).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzZyjuo5ahzmW6V8pPJqSJ
@lemone112
lemone112 force-pushed the claude/color-engine-semantic-drift-jadrtr branch from a8bf45a to fa53d51 Compare July 21, 2026 03:09
@lemone112 lemone112 changed the title P1: перевести Pair на общий joint graph (чистая реконструкция) P1: перевести Pair на общий joint graph (генерик joint) Jul 21, 2026
…6 B)

Дизайн #370 (генерик joint, WCAG снаружи) измерен на CI run 29797754817:
runtime artifact 421876 B (gzip 184975). Это честное изменение current measured
contract по C3b: maxRawBytes == rawBytes (ноль headroom), ratchet fail-closed.

На 6113 B меньше отменённого WCAG-в-joint варианта (427989): WCAG-evaluator
больше не тянется в достижимый joint-путь. Основной рост (+35329 B vs baseline)
— сам joint-solver и мономорфизация его типов (замерено ранее twiggy).

Self-referential guard синхронизирован: WASM_BUDGET_FILE_SHA256 пересчитан.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzZyjuo5ahzmW6V8pPJqSJ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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/pair.rs`:
- Around line 88-97: Update VerifiedPairV1::execution so the missing-execution
case is handled through the module’s typed error path instead of unreachable! or
another panic. Propagate that result through callers such as ordinal,
fill_paint, and label_paint, or enforce the invariant structurally by storing
the selected JointExecutionRecordV1 during construction while preserving the
existing valid execution behavior.

In `@crates/labcolors-core/src/semantic.rs`:
- Around line 2687-2781: Preserve the typed unreachable result from
select_label_candidates in lower_pair_label_frontend: map WcagInfeasible to the
appropriate Wcag22 unreachable SolveFailure, while continuing to wrap unexpected
selection failures as InternalInvariant. Keep the existing selection context and
validation behavior unchanged.

In `@docs/whitepaper.md`:
- Around line 111-115: В описании pipeline замените англоязычные термины
hard-report, total order, fresh recheck и physical lowering на русские
эквиваленты, сохранив смысл контракта и остальной порядок этапов без изменений.
- Around line 82-83: Переведите англоязычные контрактные формулировки в строках
таблицы `PairFill` и `PairLabel` на русский, включая термины `frozen frontend`,
`exact authored source`, `opaque` и `finite label candidate domain`, сохранив
исходный смысл и формат таблицы.
🪄 Autofix (Beta)

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: aed3ab8e-f80e-4bfb-b2ff-59234339b475

📥 Commits

Reviewing files that changed from the base of the PR and between c61f990 and d0beb8a.

📒 Files selected for processing (26)
  • crates/labcolors-core/src/agnostic_gates.rs
  • crates/labcolors-core/src/appearance.rs
  • crates/labcolors-core/src/config.rs
  • crates/labcolors-core/src/config/preset.rs
  • crates/labcolors-core/src/config/tests.rs
  • crates/labcolors-core/src/constraints/exact.rs
  • crates/labcolors-core/src/constraints/mod.rs
  • crates/labcolors-core/src/constraints/wcag22.rs
  • crates/labcolors-core/src/exposure_support.rs
  • crates/labcolors-core/src/joint.rs
  • crates/labcolors-core/src/joint_tests.rs
  • crates/labcolors-core/src/lib.rs
  • crates/labcolors-core/src/lpc.rs
  • crates/labcolors-core/src/pair.rs
  • crates/labcolors-core/src/pair_label_tests.rs
  • crates/labcolors-core/src/scale.rs
  • crates/labcolors-core/src/semantic.rs
  • crates/labcolors-core/src/solve.rs
  • crates/labcolors-core/tests/agnostic_production_surface.rs
  • crates/labcolors-core/tests/data/labui_emission_golden.txt
  • crates/labcolors-core/tests/empirical_inventory.rs
  • docs/decisions/0003-hk-scope.md
  • docs/empirical-inventory.md
  • docs/whitepaper.md
  • packages/colors/bench/wasm.json
  • scripts/check-wasm-size-budget.mjs
💤 Files with no reviewable changes (3)
  • crates/labcolors-core/src/exposure_support.rs
  • crates/labcolors-core/src/appearance.rs
  • crates/labcolors-core/src/solve.rs

Comment on lines +88 to 97
impl VerifiedPairV1 {
fn execution(&self) -> &JointExecutionRecordV1 {
let executions = match &self.evidence {
PairSelectionEvidenceV1::Unconstrained(evidence) => evidence.fresh_executions(),
PairSelectionEvidenceV1::Wcag22(evidence) => evidence.fresh_executions(),
};
let (mut lo, mut hi) = (0.0_f64, 0.5_f64);
for _ in 0..48 {
let mid = 0.5 * (lo + hi);
if inside(mid) {
lo = mid;
} else {
hi = mid;
}
}
lo
}

/// Насыщенная ячейка: (L, h, доля максимальной хромы) → display-байты.
fn swatch(l: f64, h_rad: f64, rel_c: f64) -> [f64; 3] {
let c = rel_c * max_chroma(l, h_rad);
encode_clamped(oklab_to_srgb_linear([l, c * h_rad.cos(), c * h_rad.sin()]))
executions.first().unwrap_or_else(|| {
unreachable!("one selected Pair tuple over one case has one execution")
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

execution() паникует на "unreachable" состоянии вместо типизированного возврата.

executions.first().unwrap_or_else(|| unreachable!(...)) — хотя инвариант действительно держится (recheck всегда над 1 candidate / 1 case → execution_count == 1), само правило репозитория требует, чтобы unreachable-состояния возвращались типизированно, а не паниковали. Это приватный helper, но заводить панику на «invariant drift» новым кодом — прямое нарушение декларированной политики.

As per coding guidelines, "Новый или изменяемый public path не должен вызывать panic и не должен получать plausible fallback; invalid, unreachable, unsupported и incomplete context должны возвращаться типизированно."

🛡️ Пример типизированного варианта
-impl VerifiedPairV1 {
-    fn execution(&self) -> &JointExecutionRecordV1 {
-        let executions = match &self.evidence {
-            PairSelectionEvidenceV1::Unconstrained(evidence) => evidence.fresh_executions(),
-            PairSelectionEvidenceV1::Wcag22(evidence) => evidence.fresh_executions(),
-        };
-        executions.first().unwrap_or_else(|| {
-            unreachable!("one selected Pair tuple over one case has one execution")
-        })
-    }
+impl VerifiedPairV1 {
+    fn execution(&self) -> Result<&JointExecutionRecordV1, PairInvariantDriftV1> {
+        let executions = match &self.evidence {
+            PairSelectionEvidenceV1::Unconstrained(evidence) => evidence.fresh_executions(),
+            PairSelectionEvidenceV1::Wcag22(evidence) => evidence.fresh_executions(),
+        };
+        executions.first().ok_or(PairInvariantDriftV1)
+    }

Далее вызывающие методы (ordinal/fill_paint/label_paint/...) пробрасывают эту ошибку типизированно, либо execution() можно оставить &self без Result, если весь модуль согласится нести инвариант через конструктор (например, хранить JointExecutionRecordV1 напрямую в VerifiedPairV1 при создании, а не искать .first() каждый раз).

🤖 Prompt for 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.

In `@crates/labcolors-core/src/pair.rs` around lines 88 - 97, Update
VerifiedPairV1::execution so the missing-execution case is handled through the
module’s typed error path instead of unreachable! or another panic. Propagate
that result through callers such as ordinal, fill_paint, and label_paint, or
enforce the invariant structurally by storing the selected
JointExecutionRecordV1 during construction while preserving the existing valid
execution behavior.

Source: Coding guidelines

Comment on lines +2687 to 2781
fn lower_pair_label_frontend(
bg: &BgInput,
tint: crate::ladder::LadderTint,
tint: LadderTint,
fraction: f64,
floor: Floor,
surface_alpha_light: f64,
surface_alpha_dark: f64,
vc: &ViewingConditions,
) -> PendingResolution {
let alpha = if vc.is_dark_theme() {
surface_alpha_dark
} else {
surface_alpha_light
};
let tint_q = quantise_encoded(tint.for_vc(vc));
let surface_hex =
match crate::alpha::composite_hex_from_encoded(tint_q, alpha, bg.encoded_display()) {
Ok(hex) => hex,
Err(error) => {
return Err(SolveFailure::InvalidInput(format!(
"тинт-поверхность бейджа вне encoded-sRGB8 reference-домена: {error}"
)));
}
};
let Ok(surface_bg) = BgInput::solid(&surface_hex) else {
return Err(SolveFailure::InternalInvariant(
"тинт-поверхность бейджа вне кодированного домена sRGB".into(),
if surface_alpha_light.to_bits() != 1.0_f64.to_bits()
|| surface_alpha_dark.to_bits() != 1.0_f64.to_bits()
{
return Err(SolveFailure::InvalidInput(
"PairLabel surface representation must be opaque after P1".into(),
));
};
}

let source = tint.srgb8_for_vc(vc);
let backdrop = pair_root_surface(bg)?;
let fill = lower_pair_fill_occurrence(bg, tint, vc)?;
let surface = fill.visible();
let surface_bg = BgInput::solid(&surface.to_hex()).map_err(|error| {
SolveFailure::InternalInvariant(format!("generated PairFill Surface was rejected: {error}"))
})?;
let surface_ctx = ResolveContext::new(&surface_bg, vc);
let anchor = TextAnchor::new(fraction, floor)?;
resolve_hued_anchor(&surface_bg, anchor, tint, vc, &surface_ctx)

let mut resolved_candidates = Vec::new();
let mut physical_candidates = Vec::new();
let mut order = Vec::new();

match pair_candidate(&surface_bg, fraction, Floor::None, source, vc, &surface_ctx) {
Ok((resolved, bytes)) => {
let ordinal = crate::joint::CandidateOrdinalV1::new(1);
resolved_candidates.push((ordinal, resolved));
physical_candidates.push(crate::pair::PairLabelCandidateV1::new(ordinal, bytes));
order.push(ordinal);
}
Err(error) if matches!(floor, Floor::None) => return Err(error),
Err(error) if error.boundary().is_none() => return Err(error),
Err(_) => {}
}

if !matches!(floor, Floor::None) {
let (resolved, bytes) =
pair_candidate(&surface_bg, fraction, floor, source, vc, &surface_ctx)?;
if physical_candidates
.iter()
.all(|candidate| candidate.source() != bytes)
{
let ordinal = crate::joint::CandidateOrdinalV1::new(2);
resolved_candidates.push((ordinal, resolved));
physical_candidates.push(crate::pair::PairLabelCandidateV1::new(ordinal, bytes));
order.push(ordinal);
}
}

if physical_candidates.is_empty() {
return Err(SolveFailure::InternalInvariant(
"Pair frontend produced an empty candidate domain".into(),
));
}

let verified = crate::pair::select_label_candidates(
source,
crate::composition::AdmittedOpacityV1::OPAQUE,
physical_candidates,
order,
backdrop,
pair_requirement(floor),
)
.map_err(|error| {
SolveFailure::InternalInvariant(format!(
"Pair joint selection failed after typed proposal admission: {error:?}"
))
})?;

if verified.fill_occurrence() != fill.occurrence()
|| verified.fill_paint() != fill.paint()
|| verified.label_occurrence().visible() != verified.label_paint().source().bytes()
{
return Err(SolveFailure::InternalInvariant(
"Pair joint evidence drifted from the selected physical chain".into(),
));
}

resolved_candidates
.into_iter()
.find(|(ordinal, _)| *ordinal == verified.ordinal())
.map(|(_, resolved)| resolved)
.ok_or_else(|| {
SolveFailure::InternalInvariant(
"Pair selection returned an ordinal outside the proposal domain".into(),
)
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Сравнить формулу контраста в WCAG22-эвалюаторе joint-модуля с legacy wcag.rs
fd -a 'wcag22.rs' crates/labcolors-core/src
fd -a 'joint.rs' crates/labcolors-core/src
rg -n -A15 'struct Wcag22Srgb8V1' crates/labcolors-core/src/wcag22.rs crates/labcolors-core/src/joint.rs 2>/dev/null
rg -n -A10 'impl.*Wcag22Srgb8V1' crates/labcolors-core/src/wcag22.rs crates/labcolors-core/src/joint.rs 2>/dev/null
rg -n -B2 -A10 'fn contrast_ratio' crates/labcolors-core/src/wcag.rs

Repository: Labpics-Team/lab-colors

Length of output: 1458


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant symbols and inspect the exact error path.
ast-grep outline crates/labcolors-core/src/semantic.rs --view expanded >/tmp/sg_semantic.txt || true
ast-grep outline crates/labcolors-core/src/joint.rs --view expanded >/tmp/sg_joint.txt || true
ast-grep outline crates/labcolors-core/src/constraints/wcag22.rs --view expanded >/tmp/sg_wcag22.txt || true
ast-grep outline crates/labcolors-core/src/wcag22.rs --view expanded >/tmp/sg_wcag22_top.txt || true

printf '\n== semantic excerpt ==\n'
sed -n '2700,2775p' crates/labcolors-core/src/semantic.rs

printf '\n== joint.rs relevant symbols ==\n'
rg -n -A6 -B4 'select_label_candidates|PairLabelCandidateV1|CandidateOrdinalV1|boundary\(' crates/labcolors-core/src/joint.rs

printf '\n== constraints/wcag22.rs relevant symbols ==\n'
rg -n -A8 -B4 'Wcag22Srgb8V1|Wcag22CriterionV1|contrast_ratio|infeasible|criterion' crates/labcolors-core/src/constraints/wcag22.rs

printf '\n== wcag22.rs top-level ==\n'
rg -n -A8 -B4 'Wcag22Srgb8V1|Wcag22CriterionV1|contrast_ratio|infeasible|criterion' crates/labcolors-core/src/wcag22.rs

Repository: Labpics-Team/lab-colors

Length of output: 21451


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' crates/labcolors-core/src/joint.rs

Repository: Labpics-Team/lab-colors

Length of output: 8755


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect pair-specific requirements and the selection error surface.
rg -n -A8 -B8 'fn pair_requirement|enum Floor|fn pair_candidate|select_label_candidates\(' crates/labcolors-core/src/semantic.rs crates/labcolors-core/src/pair.rs crates/labcolors-core/src/joint.rs

Repository: Labpics-Team/lab-colors

Length of output: 5246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' crates/labcolors-core/src/pair.rs

Repository: Labpics-Team/lab-colors

Length of output: 9209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the candidate construction and the exact Wcag22 infeasible/error paths.
sed -n '2664,2795p' crates/labcolors-core/src/semantic.rs
printf '\n== pair errors in pair.rs ==\n'
rg -n -A6 -B4 'ExactInfeasible|WcagInfeasible|ExactRecheck|WcagRecheck|Infeasible\(report\)' crates/labcolors-core/src/pair.rs

Repository: Labpics-Team/lab-colors

Length of output: 7460


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find whether SolveFailure has a typed pair infeasible variant or similar paths.
rg -n -A4 -B4 'enum SolveFailure|WcagInfeasible|ExactInfeasible|PairLoweringErrorV1|InternalInvariant' crates/labcolors-core/src

Repository: Labpics-Team/lab-colors

Length of output: 43970


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding public API and whether pair-label failures are mapped
# to a typed SolveFailure elsewhere.
rg -n -A5 -B5 'lower_pair_label_frontend|PairLoweringErrorV1|FloorUnreachable|WcagInfeasible|select_label_candidates\(' crates/labcolors-core/src

Repository: Labpics-Team/lab-colors

Length of output: 28189


Не сворачивайте Wcag22-недостижимость в InternalInvariant crates/labcolors-core/src/semantic.rs:2757-2761 — если select_label_candidates возвращает WcagInfeasible для обычного no-solution case, это должно выходить как типизированная unreachable-ошибка, а не как внутренний баг.

🤖 Prompt for 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.

In `@crates/labcolors-core/src/semantic.rs` around lines 2687 - 2781, Preserve the
typed unreachable result from select_label_candidates in
lower_pair_label_frontend: map WcagInfeasible to the appropriate Wcag22
unreachable SolveFailure, while continuing to wrap unexpected selection failures
as InternalInvariant. Keep the existing selection context and validation
behavior unchanged.

Comment thread docs/whitepaper.md
Comment on lines +82 to +83
| `PairFill` | frozen frontend: exact authored source как opaque Paint/Occurrence |
| `PairLabel` | finite label candidate domain против фактически emitted PairFill Surface |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Переведите контрактные формулировки на русский.

Новые строки содержат frozen frontend, exact authored source, opaque и finite label candidate domain, что нарушает требования к русскоязычной документации и терминологии без англицизмов.

Предлагаемая формулировка
-| `PairFill` | frozen frontend: exact authored source как opaque Paint/Occurrence |
-| `PairLabel` | finite label candidate domain против фактически emitted PairFill Surface |
+| `PairFill` | замороженный фронтенд: точный авторский источник как непрозрачные Paint/Occurrence |
+| `PairLabel` | конечная область кандидатов метки относительно фактически выданной поверхности PairFill |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `PairFill` | frozen frontend: exact authored source как opaque Paint/Occurrence |
| `PairLabel` | finite label candidate domain против фактически emitted PairFill Surface |
| `PairFill` | замороженный фронтенд: точный авторский источник как непрозрачные Paint/Occurrence |
| `PairLabel` | конечная область кандидатов метки относительно фактически выданной поверхности PairFill |
🤖 Prompt for 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.

In `@docs/whitepaper.md` around lines 82 - 83, Переведите англоязычные контрактные
формулировки в строках таблицы `PairFill` и `PairLabel` на русский, включая
термины `frozen frontend`, `exact authored source`, `opaque` и `finite label
candidate domain`, сохранив исходный смысл и формат таблицы.

Sources: Coding guidelines, Path instructions

Comment thread docs/whitepaper.md
Comment on lines +111 to +115
`PairLabel` использует фактически emitted `PairFill` occurrence как derived
Surface. Frontend формирует конечный label candidate domain; общий joint engine
исполняет `fill → surfaceFrom → label`, строит полный hard-report, выбирает по
явному total order и повторяет fresh recheck. Имена клиентских токенов и
`FillPrimary` в physical lowering не участвуют.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Уберите англоязычные термины из описания pipeline.

hard-report, total order, fresh recheck и physical lowering оставляют ключевую часть контракта на английском. Используйте русские эквиваленты; например, «явно заданный общий порядок» и «повторная проверка свежими данными».

🧰 Tools
🪛 LanguageTool

[uncategorized] ~113-~113: Слово пишется через дефис: «по-явному».
Context: ...l`, строит полный hard-report, выбирает по явному total order и повторяет fresh recheck. ...

(Pravopisanie_po-prezhnemu)

🤖 Prompt for 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.

In `@docs/whitepaper.md` around lines 111 - 115, В описании pipeline замените
англоязычные термины hard-report, total order, fresh recheck и physical lowering
на русские эквиваленты, сохранив смысл контракта и остальной порядок этапов без
изменений.

Sources: Coding guidelines, Path instructions

claude added 2 commits July 21, 2026 03:54
…(30 leaves)

P1 убирает старый pair_fill притемняющий nudge: brand/danger/info accent-fill
эмитируются как сырые identity-якоря. 30 листьев resolveVars (5 fill-ключей x
6 фонов, light-тема) обновлены на фактический выход движка. recheck-секция
байт-идентична (0/2752 drift, замерено локально). Значения сверены собранным
pkg через engine.resolveTheme; полный npm-suite зелёный (dev-deps установлены).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzZyjuo5ahzmW6V8pPJqSJ
Прежняя фраза утверждала «ни один fill не изменён» (верно для glow-регена
ADR-0004, но ложно после P1). Дополнена правдой: 30 fill-leaves перешли на
identity-якорь, pair_fill nudge удалён, recheck 0/2752 drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzZyjuo5ahzmW6V8pPJqSJ

Copy link
Copy Markdown
Collaborator Author

Canonical execution tracker

The post-P1 hardening program, file-ownership rules and recovery procedure are now centralized in #399.

P1 remains intentionally isolated: do not fold #386/#393/#382/#378, #395/#392/#394/#397 or other optimization slices into this PR. After merge, record the merge SHA and newly released file ownership in #399.

@lemone112
lemone112 merged commit 09f2c78 into main Jul 21, 2026
10 checks passed
@lemone112
lemone112 deleted the claude/color-engine-semantic-drift-jadrtr branch July 21, 2026 12:19
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.

2 participants