Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ examine_globs = [
"crates/labcolors-core/src/numerical_plan.rs",
"crates/labcolors-core/src/numerics.rs",
"crates/labcolors-core/src/pair.rs",
"crates/labcolors-core/src/srgb8.rs",
"crates/labcolors-core/src/wcag22.rs",
"crates/labcolors-core/src/wcag22_evidence.rs",
"crates/labcolors-core/src/wcag22/kernel.rs",
]

# Запас времени на мутант: legacy proxy coordinate считается быстро, но набор в целом не
Expand Down
15 changes: 7 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ jobs:
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: cargo test
run: cargo test --workspace --locked
- name: prove WCAG22 sRGB8 Q55 artifact over the full finite domain
run: python3 scripts/verify_wcag22_q55.py

audit:
name: cargo audit (rustsec)
Expand Down Expand Up @@ -321,6 +323,11 @@ jobs:
node-version: ${{ env.NODE_TOOLCHAIN }}
cache: npm
cache-dependency-path: packages/colors/package-lock.json
- name: enforce measured WASM raw-byte budget
# Hard gate: exact accepted Issue #284 raw-byte measurement, without
# invented headroom. gzip is reported only as a transport diagnostic
# because compressed bytes may vary with the gzip implementation.
run: node scripts/check-wasm-size-budget.mjs
- name: "@labpics/colors: typecheck + runtime tests"
# Now that pkg/ exists (built above), the package's public types resolve
# against the *real* wasm-bindgen declarations — so the type-level smoke
Expand Down Expand Up @@ -459,14 +466,6 @@ jobs:
# Chrome is provisioned above; the parity smoke runs against native
# resolve_set inside the same wasm runtime. D1 default from the chapter.
run: wasm-pack test --headless --chrome --chromedriver "$CHROMEDRIVER_PATH" crates/labcolors-wasm --locked
- name: report bundle size (gzip)
# Size is part of the perf story (perf-bench). Not a hard gate yet — a
# budget gate lands with perf-bench; here we surface the number in logs.
run: |
wasm="packages/colors/pkg/labcolors_bg.wasm"
glue="packages/colors/pkg/labcolors.js"
echo "wasm: raw=$(wc -c <"$wasm")B gzip=$(gzip -9 -c "$wasm" | wc -c)B"
echo "glue: raw=$(wc -c <"$glue")B gzip=$(gzip -9 -c "$glue" | wc -c)B"
docs-drift:
name: docs-drift (нейминг-канон)
runs-on: [self-hosted, Linux, X64]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ target/
packages/colors/.release/
packages/colors/LICENSE
packages/colors/build-metadata.json
packages/colors/evidence/

# Психофизический харнесс: сгенерированные манифесты/раннеры/экспорты сессий —
# это артефакты запуска (детерминированы из seed), в гит не коммитятся.
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ CSS-строка, CSSOM и обход DOM сами по себе не повыш
registry с независимо пересчитываемым drift-checksum — входит в conformance
manifest. Он описывает возможности сборки и не повышает незарегистрированный
или explicit `Compatibility`-результат до determinate только потому, что тесты
на одной платформе зелёные.
на одной платформе зелёные. До появления внешних клиентов capability-контракт
исправлен атомарно: единственный `numericalCapabilityManifest()` возвращает
proof-capable schema V2; промежуточный public V1 и второй V2-entrypoint удалены.

## Источники и производные значения

Expand Down Expand Up @@ -216,7 +218,8 @@ anchors
- Нормативный floor применяется только там, где его требует контракт клиента или компонента.
- Core не определяет размер текста, essentialness, disabled/decorative status по имени роли.
- Экспериментальный LPC/APCA-shaped или appearance-результат не меняет WCAG pass/fail.
- До миграции на единый versioned WCAG 2.2 profile старое поле `wcagRatio` нельзя автоматически рекламировать как `Wcag22`.
- Для финальной пары sRGB8 новый `wcag22-srgb8-contrast-v1` принимает явно объявленный критерий и возвращает строгий `Pass | Fail`; профиль, Q55-артефакт и full-domain proof входят в релиз.
- Старое поле `wcagRatio` остаётся compatibility-диагностикой текущего resolver/runtime и не может автоматически рекламироваться как результат нового evaluator-а.
- Цвет не должен быть единственным носителем смысла; текст, иконка и форма принадлежат компоненту.

## Runtime и фон
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ final class ConformanceTests: XCTestCase {
}
}

func wcag22Criterion(_ key: String) -> Wcag22Criterion {
switch key {
case "sc-1.4.3-text-default": return .sc143TextDefault
case "sc-1.4.3-text-large-scale": return .sc143TextLargeScale
case "sc-1.4.11-ui-component-or-state": return .sc1411UiComponentOrState
case "sc-1.4.11-graphical-object": return .sc1411GraphicalObject
default: fatalError("unknown WCAG22 criterion in pack: \(key)")
}
}

func channels(_ hex: String) -> [Int] {
let s = hex.hasPrefix("#") ? String(hex.dropFirst()) : hex
precondition(s.count == 6, "ожидался #RRGGBB, получено \(hex)")
Expand Down Expand Up @@ -118,16 +128,16 @@ final class ConformanceTests: XCTestCase {
/// preimage ядра (labcolors-core/src/numerics.rs): length-prefixed (u32 LE
/// длина + байты) домен-сепаратор, u32 LE schema version, coverage key,
/// u32 LE счётчик sites (сортировка по сырым UTF-8 байтам siteId), на site —
/// siteId и шесть списков ключей; каждый список: u32 LE count (явный и для
/// siteId и семь списков ключей; каждый список: u32 LE count (явный и для
/// пустого) + отсортированные length-prefixed ключи. Кодирование повторено
/// здесь НАМЕРЕННО: тест — оракул, он не должен переиспользовать encoder,
/// который проверяет.
func testCapabilityManifestChecksumRecomputes() throws {
let manifest = try load("manifest.json", as: Manifest.self)
let caps = manifest.numericalCapabilities
// Оракул реализует canonical preimage V1: другая версия схемы обязана
// Оракул реализует canonical preimage V2: другая версия схемы обязана
// падать здесь, а не молча проходить с пересчитанным checksum.
XCTAssertEqual(caps.schemaVersion, 1, "неподдерживаемая версия capability-схемы")
XCTAssertEqual(caps.schemaVersion, 2, "неподдерживаемая версия capability-схемы")
XCTAssertEqual(caps.coverage, "migrated-sites-only-v1", "coverage capability manifest")
XCTAssertFalse(caps.sites.isEmpty, "capability manifest без единого migrated site пуст")
for site in caps.sites {
Expand Down Expand Up @@ -161,7 +171,7 @@ final class ConformanceTests: XCTestCase {
for key in sorted { pushLenPrefixed(key) }
}

pushLenPrefixed("labcolors.numerical-capability.v1")
pushLenPrefixed("labcolors.numerical-capability.v2")
pushU32LE(caps.schemaVersion)
pushLenPrefixed(caps.coverage)
let sites = caps.sites.sorted {
Expand All @@ -175,6 +185,7 @@ final class ConformanceTests: XCTestCase {
pushSortedKeyList(site.evidenceClasses)
pushSortedKeyList(site.artifactIds)
pushSortedKeyList(site.boundIds)
pushSortedKeyList(site.proofIds)
pushSortedKeyList(site.runtimeAttestations)
}

Expand Down Expand Up @@ -422,6 +433,41 @@ final class ConformanceTests: XCTestCase {
XCTAssertEqual(got, v.score, accuracy: Self.driftTol, "muddiness \(v.hex)")
}
}


// MARK: - Exact WCAG 2.2 final-sRGB8 assessment

func testWcag22() throws {
let vectors = try load("wcag22.json", as: [Wcag22Vec].self)
XCTAssertFalse(vectors.isEmpty)
for vector in vectors {
let got = try evaluateWcag22(
foreground: vector.foreground,
background: vector.background,
criterion: wcag22Criterion(vector.criterion))
XCTAssertEqual(got.profileId, vector.profileId)
XCTAssertEqual(got.criterion, wcag22Criterion(vector.criterion))
XCTAssertEqual(got.foreground, vector.foreground)
XCTAssertEqual(got.background, vector.background)
XCTAssertEqual(got.decision == .pass ? "pass" : "fail", vector.decision)
XCTAssertEqual(got.foregroundLuminance.lower, UInt64(vector.foregroundLowerQ55))
XCTAssertEqual(got.foregroundLuminance.upper, UInt64(vector.foregroundUpperQ55))
XCTAssertEqual(got.backgroundLuminance.lower, UInt64(vector.backgroundLowerQ55))
XCTAssertEqual(got.backgroundLuminance.upper, UInt64(vector.backgroundUpperQ55))
XCTAssertEqual(got.q55Scale, UInt64(vector.q55Scale))
XCTAssertEqual(got.evidence.kind, vector.evidenceKind)
XCTAssertEqual(got.evidence.artifactId, vector.artifactId)
XCTAssertEqual(got.evidence.artifactSha256, vector.artifactSha256)
XCTAssertEqual(got.evidence.boundId, vector.boundId)
XCTAssertEqual(got.evidence.proofId, vector.proofId)
XCTAssertEqual(got.evidence.proofSha256, vector.proofSha256)
XCTAssertEqual(got.evidence.proofPayloadSha256, vector.proofPayloadSha256)
XCTAssertEqual(got.evidence.generatorSha256, vector.generatorSha256)
XCTAssertEqual(got.evidence.verifierSha256, vector.verifierSha256)
XCTAssertEqual(got.evidence.profileChecksum, vector.profileChecksum)
XCTAssertEqual(got.evidence.profileSha256, vector.profileSha256)
}
}
}

// MARK: - Codable-зеркала схемы векторов
Expand All @@ -433,7 +479,7 @@ struct Manifest: Codable {
let numericalCapabilities: CapabilityManifest
}

/// Зеркало capability manifest (pack 3.0.0): typed-проекция core registry
/// Зеркало proof-capable capability manifest (pack 4.0.0): typed-проекция core registry
/// численных решений. Заменяет прозаический `numericalSites` из pack 2.x —
/// биндинг сверяет typed rows и drift-checksum, а не research-тексты.
struct CapabilityManifest: Codable {
Expand All @@ -452,9 +498,34 @@ struct CapabilitySite: Codable {
let evidenceClasses: [String]
let artifactIds: [String]
let boundIds: [String]
let proofIds: [String]
let runtimeAttestations: [String]
}

struct Wcag22Vec: Codable {
let foreground: String
let background: String
let criterion: String
let profileId: String
let decision: String
let foregroundLowerQ55: String
let foregroundUpperQ55: String
let backgroundLowerQ55: String
let backgroundUpperQ55: String
let q55Scale: String
let evidenceKind: String
let artifactId: String
let artifactSha256: String
let boundId: String
let proofId: String
let proofSha256: String
let proofPayloadSha256: String
let generatorSha256: String
let verifierSha256: String
let profileChecksum: String
let profileSha256: String
}

struct ContrastVec: Codable {
let fg: String
let bg: String
Expand Down
23 changes: 13 additions & 10 deletions conformance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,25 @@ decision. Legacy-идентификаторы сохранены только д
- `alpha.json` начиная с pack `2.0.0` обязательно содержит точный byte-reference
half-tie `#C0B2FA @ 0.122` над `#000000` → `#17161F`. Это mutation-killer
старого пути `(byte/255) · alpha · 255`, который выбирал соседний LSB.
- `manifest.numericalCapabilities` (схема пака 3.0.0) генерируется из
core-owned `numerical_capability_manifest_v1()` и заменяет прозаический
`numericalSites` пака 2.x. Форма:
- `manifest.numericalCapabilities` в pack `4.0.0` генерируется из
proof-capable core-owned `numerical_capability_manifest_v2()`. До появления
внешних клиентов промежуточная Glow-only capability-схема V1 удалена из
public API: один `numericalCapabilityManifest()` сразу возвращает V2, без
второго version-suffixed entrypoint. Это намеренная pre-client breaking
коррекция ложной схемы, а не поддержка двух конкурирующих контрактов. Форма V2:
`{schemaVersion, coverage, sites[], checksum}`, где `schemaVersion` —
независимый version domain capability-схемы (сейчас `1`); `coverage` —
независимый version domain capability-схемы (сейчас `2`); `coverage` —
`migrated-sites-only-v1` (перечислены только **уже мигрированные**
branch-sensitive sites, не утверждение полного аудита исторических
`f64`-ветвлений — он остаётся в scope #291); каждая строка `sites[]` несёт
`siteId` и шесть списков стабильных ключей (`stableOutcomes`,
`siteId` и семь списков стабильных ключей (`stableOutcomes`,
`compatibilityReleases`, `evidenceClasses`, `artifactIds`, `boundIds`,
`runtimeAttestations`; пустой список — явное «evidence отсутствует», не
пропуск); `checksum` — FNV-1a-32 (8 lowercase hex) над canonical
`proofIds`, `runtimeAttestations`; пустой список — явное «evidence отсутствует»,
не пропуск); `checksum` — FNV-1a-32 (8 lowercase hex) над canonical
length-prefixed preimage с домен-сепаратором
`labcolors.numerical-capability.v1`. Release verifier и Swift-тесты
пересчитывают checksum НЕЗАВИСИМО от Rust-кода. Сейчас в manifest только
`glow-target-or-maximum-v1`.
`labcolors.numerical-capability.v2`. Release verifier и Swift-тесты
пересчитывают checksum НЕЗАВИСИМО от Rust-кода. Manifest содержит
`glow-target-or-maximum-v1` и proof-bound `wcag22-srgb8-contrast-v1`.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Словарь **позиций лестницы** (не ролей): `label-*`, `fill-*`, `border-*`,
`focus-ring`, `glow`, `skeleton-*`, `neutral-fill-*`, `neutral-border-*`,
Expand Down
32 changes: 27 additions & 5 deletions conformance/vectors/manifest.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"packVersion": "3.0.0",
"packVersion": "4.0.0",
"coreVersion": "0.2.0",
"packDigest": "64a68cbd",
"packDigest": "dad8f0af",
"counts": {
"contrasts": 40,
"ladders": 25,
"alpha": 7,
"solve": 6,
"muddiness": 4,
"total": 82
"wcag22": 6,
"total": 88
},
"numericalCapabilities": {
"schemaVersion": 1,
"schemaVersion": 2,
"coverage": "migrated-sites-only-v1",
"sites": [
{
Expand All @@ -28,9 +29,30 @@
],
"artifactIds": [],
"boundIds": [],
"proofIds": [],
"runtimeAttestations": []
},
{
"siteId": "wcag22-srgb8-contrast-v1",
"stableOutcomes": [
"canonical-finite-bounded"
],
"compatibilityReleases": [],
"evidenceClasses": [
"canonical-finite-bounded"
],
"artifactIds": [
"wcag22-srgb8-luminance-q55-v1"
],
"boundIds": [
"wcag22-srgb8-outward-q55-v1"
],
"proofIds": [
"wcag22-srgb8-full-domain-q55-v1"
],
"runtimeAttestations": []
}
],
"checksum": "5a3f6cbc"
"checksum": "f8d0c63d"
}
}
Loading
Loading