Skip to content

C8d(4): packed WASM recheck boundary + typed verdict + numeric theme handle (hard-cut string overloads, semver-major) - #407

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

C8d(4): packed WASM recheck boundary + typed verdict + numeric theme handle (hard-cut string overloads, semver-major)#407
lemone112 merged 1 commit into
mainfrom
claude/color-engine-semantic-drift-jadrtr

Conversation

@lemone112

@lemone112 lemone112 commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Слайс C8d, шаг 4 — публичная граница WASM (semver-major)

Стек на влитых шагах 1-3 (#404/#405/#406). Первый не-core-only шаг: заменяет строковую Vec<String> recheck-границу на packed numeric wire. Пакет @labpics/colors пока 0.11.0 (pre-1.0), так что major-слом уместен.

Ратификация форков (видимый DAG-лог)

Отдельная ratify-фаза воркфлоу сверила три needs_ratification форка с роадмап-SSOT — все RATIFIED с цитатами:

  • F1 — packed Uint32Array RGB24 (0x00RRGGBB, старший байт required-zero) + скалярный numeric theme handle. Опора: §12 строка 1788 «packed typed-array batch», §16 C8d строки 2600-2601 «Vec<String> … заменяется одним numeric-handle/packed typed-array batch-ем».
  • F2stateless packed batch сейчас (JS делает Pointwise-OR + обязательный full-support re-verify перед commit); stateful Session-handle отложен в O1/compiled-runtime. Опора: §12 строки 1786-1789 (numeric graph-handle привязан к compiled-runtime; strings только на cold authoring edges).
  • F3hard-cut строковых перегрузок (один публичный корень, без транзитного дубля). Опора: слово «заменяется» (2600) + no-two-public-roots/leaf-first дисциплина C7c (2550-2551), C8a (2579), 2365.

Что сделано

  • F1: recheckContrast(bg:number 0x00RRGGBB, fgs:Uint32Array, themeHandle:number):Float64Array и recheckContrastMulti(bgs:Uint32Array, fgs:Uint32Array, themeHandle:number):Float64Array (lib.rs + engine.rs), маршрутизированы через core recheck_against_u32/_multi_u32 (шаг 1) — один contiguous-копи, ноль hex-parse и строковых аллокаций на update-пути. Старший байт валидируется один раз без аллокации.
  • numeric theme handle (engine.themeHandle(theme):number): ключ темы разрешается один раз на холодном крае, в горячем цикле адресуется числом (F2, stateless).
  • Типизированный full-support verdict Verified|Violation{worstSample,worstRole,provenance} + packed readout.
  • F3 hard-cut: строковые перегрузки удалены; .d.ts генерится wasm-bindgen, публичную поверхность enforce-ят public-claims/public-api-cleanup/release-contract тесты (обновлены под packed surface, один публичный корень).
  • Консюмер adapt-theme.js мигрирован механически на packed вход (packRgb24Hex + мит handle); алгоритм worst→full-support НЕ тронут — это шаг 5.

Заморожено (byte-identity держится)

Float64Array OUTPUT layout (interleaved / background-major) и resolveTheme wide-JSON hex — не изменены. wasm-boundary.golden.json пуст в диффе. Тесты C1 (byte-identity к frozen golden через packed вход) и C2 (background-major layout = N per-sample вызовов) закрепляют это в CI. FFI не тронут (остаётся строковым — F6, C8e).

Ревью (adversarial) + применённые фиксы

3 high-effort read-only линзы → синтез READY_WITH_FIXES. Применил все находки:

  • blocker: parity-тест pk не разворачивал #RGB shorthand, а frozen golden содержит shorthand-фикстуры (#fff/#123) — CI покраснел бы. Fix: pk теперь зеркалит packRgb24Hex (разворот shorthand); golden не тронут. Проверено на всех golden-кейсах.
  • major: bench/wasm-boundary.bench.mjs кормил hex-строки в packed ABI (wasm-bindgen молча коэрсил в 0 → бенч мерил мусор). Fix: мигрирован на packed (themeHandle + Uint32Array).
  • minor: bench/hotpath.bench.mjs был уже мёртв (импорт удалённого effectiveBackground), не мигрируем без несвязанной работы → удалён (не в CI/scripts; реальную границу мерит wasm-boundary.bench.mjs).
  • nit: README задокументирован под packed сигнатуры + themeHandle.

Гейты (локально, зелёные где применимо)

cargo build -p labcolors-wasm --target wasm32-unknown-unknown, cargo test -p labcolors-wasm (74), cargo test -p labcolors-core (606), cargo fmt --all --check, cargo clippy --workspace --all-targets -D warnings, cargo doc -D warnings, tsc --noEmit, node --test pure-JS (89 на затронутых). C1/C2 byte-identity и размер валидирует CI после wasm-pack build (wasm-pack локально недоступен).

Ожидаемо: размер бинарника изменится

Удаление строкового пути меняет codegen → size-джоба сообщит новый rawBytes. bench/wasm.json + WASM_BUDGET_FILE_SHA256 НЕ тронуты в этом коммите — честный re-baseline на фактические байты CI сделаю сразу после первого прогона (дисциплина C3b).

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

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

    • Добавлена числовая адресация тем через themeHandle.
    • Проверка контраста поддерживает упакованные цвета 0x00RRGGBB.
    • Пакетная проверка контраста для нескольких фонов с плоским результатом.
  • Изменения API

    • recheckContrast / recheckContrastMulti переведены на упакованные входы и themeHandle вместо строковых hex и строки темы (старые строковые варианты удалены).
  • Устойчивость / Bug Fixes

    • Некорректные packed-данные теперь возвращают типизированные ошибки вместо сбоев (в т.ч. при неверном старшем байте).
  • Документация

    • Обновлены публичные описания сигнатур и форматов входных/выходных данных.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Добавлен packed API для проверки контраста: цвета передаются как 0x00RRGGBB, темы — через числовые handles. Обновлены WASM-экспорты, adaptTheme, документация, тесты и benchmark boundary.

Changes

Packed contrast recheck

Layer / File(s) Summary
Числовой API Engine
crates/labcolors-wasm/src/engine.rs
Добавлены theme_handle, recheck_u32 и recheck_multi_u32 с типизированной валидацией handle, конфигурации и reserved byte.
WASM и публичный контракт
crates/labcolors-wasm/src/lib.rs, crates/labcolors-wasm/tests/wasm_parity.rs, packages/colors/README.md, packages/colors/adapt-theme.d.ts, packages/colors/test/wasm-boundary-parity.test.mjs
Экспорты и документация переведены на packed цвета, числовые theme handles и background-major multi-результаты; parity-тесты проверяют эквивалентность single и multi путей.
Адаптер и кэширование темы
packages/colors/adapt-theme.js, packages/colors/test/adapt-theme.test.mjs, packages/colors/test/chain-invariants.test.mjs
Добавлены упаковка hex, проверка входов, кэширование handles и packed single/multi вызовы с соответствующими тестами.
Parity, boundary и benchmark-проверки
packages/colors/bench/wasm-boundary.bench.mjs, packages/colors/bench/wasm.json, packages/colors/test/hotpath-parity.test.mjs, scripts/check-wasm-size-budget.mjs
Обновлены boundary benchmark, packed-извлечение тона, параметры WASM budget и его контрольный SHA-256.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant adaptTheme
  participant themeArgFor
  participant WASMEngine
  participant ThemeDictionary
  adaptTheme->>themeArgFor: resolve and cache theme handle
  themeArgFor-->>adaptTheme: numeric theme handle
  adaptTheme->>WASMEngine: recheckContrastMulti(bgs, fgs, theme handle)
  WASMEngine->>ThemeDictionary: resolve ViewingConditions by handle
  ThemeDictionary-->>WASMEngine: return ViewingConditions
  WASMEngine-->>adaptTheme: flat contrast results
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Заголовок точно отражает основной сдвиг PR: packed WASM recheck boundary, numeric theme handle и удаление строковых overloads.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/color-engine-semantic-drift-jadrtr

Warning

Tools execution failed with the following error:

Failed to run tools: 14 UNAVAILABLE: read ECONNRESET


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

…handle (hard-cut string overloads)

Replace the Vec<String> recheck overloads with the ratified Option A packed
wire (F1): recheckContrast(bg:u32 0x00RRGGBB, fgs:Uint32Array, theme:number)
and recheckContrastMulti(bgs:Uint32Array, fgs:Uint32Array, theme:number),
each one contiguous typed-array copy into linear memory — zero hex parse, zero
String/Cow per foreground, reserved high byte validated once without alloc.
The interleaved / background-major Float64Array OUTPUT layout stays frozen and
byte-identical to the golden (core recheck_against_u32/_multi_u32 from step 1).

Mint a numeric theme handle (themeHandle) so the stateless recheck path (F2)
addresses the theme dictionary by slot instead of re-scanning it by key every
frame; the begin/observe/end Session lifecycle stays deferred.

Hard-cut the string overloads (F3, semver-major): engine.rs recheck/recheck_multi
/recheck_vc removed in favour of recheck_u32/recheck_multi_u32/recheck_vc_by_handle
+ theme_handle; wasm boundary annotations regenerate the .d.ts. Internal consumers
migrated mechanically to the packed input in the same commit — adaptTheme packs
samples/foregrounds and mints the handle at the cold recheck seam (worst->full-
support algorithm unchanged, that is step 5) — and every affected test.

FFI stays on the string path (C8e/F6). resolveTheme wide-JSON output untouched.

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 46b2e9c to 30513b8 Compare July 21, 2026 15:45

@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 `@packages/colors/adapt-theme.js`:
- Around line 310-324: Validate the result of mintThemeHandle(themeName) inside
themeArgFor before caching or returning it. Reject undefined, NaN, strings, and
other non-numeric or non-finite handles with a clear adaptTheme diagnostic,
while preserving the existing memoization and raw-theme fallback when
mintThemeHandle is unavailable.
- Around line 310-324: Update themeArgFor so the newly minted handle remains
local until checkpoint(owner) completes successfully; only then publish both
themeArgValue and themeArgKey together. Preserve the existing memoization and
raw-theme fallback behavior, ensuring cancellation or reentrant callbacks cannot
leave the key and handle out of sync.
🪄 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: 562e0642-7256-46f5-9e8f-7cf13b5c9890

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9ce25 and 46b2e9c.

📒 Files selected for processing (12)
  • crates/labcolors-wasm/src/engine.rs
  • crates/labcolors-wasm/src/lib.rs
  • crates/labcolors-wasm/tests/wasm_parity.rs
  • packages/colors/README.md
  • packages/colors/adapt-theme.d.ts
  • packages/colors/adapt-theme.js
  • packages/colors/bench/hotpath.bench.mjs
  • packages/colors/bench/wasm-boundary.bench.mjs
  • packages/colors/test/adapt-theme.test.mjs
  • packages/colors/test/chain-invariants.test.mjs
  • packages/colors/test/hotpath-parity.test.mjs
  • packages/colors/test/wasm-boundary-parity.test.mjs
💤 Files with no reviewable changes (1)
  • packages/colors/bench/hotpath.bench.mjs

Comment on lines +310 to +324
// Numeric theme-handle memo. Mint at most once per distinct theme key; the
// recheck loop then passes the numeric handle (or the raw key, when the engine
// exposes no themeHandle capability).
let themeArgKey = null;
let themeArgValue = null;
const themeArgFor = (themeName, owner) => {
if (!mintThemeHandle) return themeName;
if (themeName !== themeArgKey) {
themeArgValue = mintThemeHandle(themeName);
checkpoint(owner);
themeArgKey = themeName;
}
return themeArgValue;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Возвращаемое значение mintThemeHandle не валидируется.

В отличие от recheckContrast/recheckContrastMulti (проверка длины буфера, финитности значений) и isStableGlowPointNoop (проверка typeof noop !== "boolean"), результат mintThemeHandle(themeName) никак не проверяется перед передачей в recheckContrast/recheckContrastMulti. Некорректное значение (например, undefined, NaN, строка) даст малопонятную ошибку уже на границе WASM вместо чёткой диагностики adaptTheme.

🤖 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 `@packages/colors/adapt-theme.js` around lines 310 - 324, Validate the result
of mintThemeHandle(themeName) inside themeArgFor before caching or returning it.
Reject undefined, NaN, strings, and other non-numeric or non-finite handles with
a clear adaptTheme diagnostic, while preserving the existing memoization and
raw-theme fallback when mintThemeHandle is unavailable.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Мутация кэша themeArgKey/themeArgValue происходит до checkpoint — риск рассинхронизации при отменённой операции.

themeArgValue перезаписывается ДО checkpoint(owner), а themeArgKey — только после. Если mintThemeHandle (внешний колбэк потребителя, потенциально реентрантный — как и остальные capability-колбэки в этом файле) успевает инициировать более новую операцию до того, как checkpoint бросит CANCELLED, кэш остаётся в несогласованном состоянии: themeArgKey указывает на старую тему, а themeArgValue уже содержит handle для ДРУГОЙ темы. Следующий вызов с тем же старым themeName вернёт чужой handle без какой-либо ошибки — тихая порча состояния, нарушающая тот же инвариант «публикуем состояние только после подтверждения владения», который соблюдается везде в остальном файле (applyHexes, commitResolved и т.п.).

🔒️ Предлагаемый фикс: не публиковать состояние до checkpoint
   const themeArgFor = (themeName, owner) => {
     if (!mintThemeHandle) return themeName;
     if (themeName !== themeArgKey) {
-      themeArgValue = mintThemeHandle(themeName);
-      checkpoint(owner);
-      themeArgKey = themeName;
+      const minted = mintThemeHandle(themeName);
+      checkpoint(owner);
+      themeArgValue = minted;
+      themeArgKey = themeName;
     }
     return themeArgValue;
   };
📝 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
// Numeric theme-handle memo. Mint at most once per distinct theme key; the
// recheck loop then passes the numeric handle (or the raw key, when the engine
// exposes no themeHandle capability).
let themeArgKey = null;
let themeArgValue = null;
const themeArgFor = (themeName, owner) => {
if (!mintThemeHandle) return themeName;
if (themeName !== themeArgKey) {
themeArgValue = mintThemeHandle(themeName);
checkpoint(owner);
themeArgKey = themeName;
}
return themeArgValue;
};
// Numeric theme-handle memo. Mint at most once per distinct theme key; the
// recheck loop then passes the numeric handle (or the raw key, when the engine
// exposes no themeHandle capability).
let themeArgKey = null;
let themeArgValue = null;
const themeArgFor = (themeName, owner) => {
if (!mintThemeHandle) return themeName;
if (themeName !== themeArgKey) {
const minted = mintThemeHandle(themeName);
checkpoint(owner);
themeArgValue = minted;
themeArgKey = themeName;
}
return themeArgValue;
};
🤖 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 `@packages/colors/adapt-theme.js` around lines 310 - 324, Update themeArgFor so
the newly minted handle remains local until checkpoint(owner) completes
successfully; only then publish both themeArgValue and themeArgKey together.
Preserve the existing memoization and raw-theme fallback behavior, ensuring
cancellation or reentrant callbacks cannot leave the key and handle out of sync.

@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: 1

🤖 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 `@packages/colors/adapt-theme.js`:
- Around line 151-161: Make the numeric theme-handle capability mandatory in the
initialization logic around themeHandleCapability and mintThemeHandle: reject
colors immediately when themeHandle is missing, while retaining the existing
TypeError for non-function values. Remove the null/string fallback path so all
later recheckContrast calls receive a numeric theme handle.
🪄 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: 37438b93-1981-4e75-a0c7-d70b7a4cd685

📥 Commits

Reviewing files that changed from the base of the PR and between 46b2e9c and 30513b8.

📒 Files selected for processing (14)
  • crates/labcolors-wasm/src/engine.rs
  • crates/labcolors-wasm/src/lib.rs
  • crates/labcolors-wasm/tests/wasm_parity.rs
  • packages/colors/README.md
  • packages/colors/adapt-theme.d.ts
  • packages/colors/adapt-theme.js
  • packages/colors/bench/hotpath.bench.mjs
  • packages/colors/bench/wasm-boundary.bench.mjs
  • packages/colors/bench/wasm.json
  • packages/colors/test/adapt-theme.test.mjs
  • packages/colors/test/chain-invariants.test.mjs
  • packages/colors/test/hotpath-parity.test.mjs
  • packages/colors/test/wasm-boundary-parity.test.mjs
  • scripts/check-wasm-size-budget.mjs
💤 Files with no reviewable changes (1)
  • packages/colors/bench/hotpath.bench.mjs

Comment on lines +151 to +161
// Optional numeric theme handle (like recheckContrastMulti, it is an engine
// capability the controller uses when offered). When present, a theme key is
// lowered to its numeric handle ONCE per distinct theme at a cold recheck
// edge, then addressed numerically — the hot loop never re-scans the theme
// dictionary by string. Engines without it keep the string theme key.
const themeHandleCapability = colors.themeHandle;
if (themeHandleCapability !== undefined && typeof themeHandleCapability !== "function") {
throw new TypeError("adaptTheme: themeHandle must be a function");
}
const mintThemeHandle =
typeof themeHandleCapability === "function" ? themeHandleCapability.bind(colors) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Не передавайте строковый fallback в numeric-only ABI.

recheckContrast уже объявлен с theme: number, а string overload удалён. При отсутствии themeHandle здесь передаётся строка, поэтому incomplete capability contract падает лишь поздно на WASM boundary. Сделайте themeHandle обязательным и отклоняйте такой colors сразу.

Предлагаемое исправление
-  colors: Pick<LabColors, "resolveTheme" | "recheckContrast"> &
-    Partial<Pick<LabColors, "recheckContrastMulti" | "themeHandle" | "isStableGlowPointNoop">>;
+  colors: Pick<LabColors, "resolveTheme" | "recheckContrast" | "themeHandle"> &
+    Partial<Pick<LabColors, "recheckContrastMulti" | "isStableGlowPointNoop">>;
-  if (themeHandleCapability !== undefined && typeof themeHandleCapability !== "function") {
+  if (typeof themeHandleCapability !== "function") {
     throw new TypeError("adaptTheme: themeHandle must be a function");
   }
-    if (!mintThemeHandle) return themeName;

Как предписывает coding guidelines: «Новый или изменяемый public path не должен … получать plausible fallback; … incomplete context должны возвращаться типизированно».

Also applies to: 315-316

🤖 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 `@packages/colors/adapt-theme.js` around lines 151 - 161, Make the numeric
theme-handle capability mandatory in the initialization logic around
themeHandleCapability and mintThemeHandle: reject colors immediately when
themeHandle is missing, while retaining the existing TypeError for non-function
values. Remove the null/string fallback path so all later recheckContrast calls
receive a numeric theme handle.

Source: Coding guidelines

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