Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 7 additions & 4 deletions dag/extdeps/accessibility/wcag_contrast.dag
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority {
}

// Cited upstream: W3C WCAG 2.2, definitions of relative luminance and contrast ratio (retrieved
// 2026-07-21). Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B where each channel c (sRGB,
// 0..1) linearizes as: c <= 0.03928 -> c/12.92, else ((c+0.055)/1.055)^2.4 (the 0.03928 knee is
// WCAG's published constant). Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05); thresholds
// 2026-10-05). Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B where each channel c (sRGB,
// 0..1) linearizes as: c <= 0.04045 -> c/12.92, else ((c+0.055)/1.055)^2.4 — the 0.04045 knee is
// the cited constant (the definition's Note 2 records 0.03928 as the threshold before May 2021;
// 2.2 aligned it with sRGB). Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05); thresholds
// 4.5:1 (AA normal text), 3:1 (AA large text and non-text graphical objects, per SC 1.4.11), 7:1
// (AAA). Arithmetic here is integer at scale 10000 (one unit = 0.0001). The single approximated
// kernel is the exponent 2.4, computed as x^2.4 = x^2 * (x^2)^(1/5) with the fifth root by integer
Expand Down Expand Up @@ -74,8 +75,10 @@ fn pow_2_4_scaled(x_scaled: Int) -> Int {
}
}

// The cited knee is 0.04045; c_scaled is an integer at scale 10000, so c <= 0.04045 is
// c_scaled <= 404 (404.5 is not representable, and 404 is the largest integer at or below it).
fn linearize_channel_scaled(c_scaled: Int) -> Int {
if c_scaled <= 392 {
if c_scaled <= 404 {
(c_scaled * 100) / 1292
} else {
pow_2_4_scaled(x_scaled: ((c_scaled + 550) * 10000) / 10550)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ data a_citation_attests_a_decision_rule_the_code_does_not_implement: RecurringFa

"**RECEIPT (vivid-ibex-684, 2026-10-04, gunbc#13258, side-chat review `5406002589`; defect live in the tree).** `dag/extdeps/accessibility/wcag_contrast.dag` cites WCAG 2.2 (www.w3.org/TR/WCAG22/#dfn-relative-luminance) and its comment asserts the linearization knee is the page's published constant at 0.03928 -- and the code does use 392/10000. But the cited definition specifies the threshold 0.04045 and notes 0.03928 as the older value, so the citation attests a definition the code does not implement. The module was authored 2026-07-21 with the comment noting the page as retrieved that day, so the pairing was already false when authored -- there is no upstream-drift story here, only a misattestation that name-level checks (live URL, subject ownership) do not see. Pulled from #13258 per that review rather than fixed; the defect is live as of this receipt.",

"**REPAIR RECEIPT (vivid-lynx-377, 2026-10-05, frontier; wcag half).** `dag/extdeps/accessibility/wcag_contrast.dag`'s linearization knee is now the cited 0.04045 (integer `c_scaled <= 404` at scale 10000) and the comment cites the definition's Note 2 recording 0.03928 as the threshold before May 2021 (live page re-read 2026-10-05). The threshold half of the #13258 receipt was checked against the live page and already matched: 450/300/700 transcribe SC 1.4.3 (4.5:1 normal, 3:1 large-scale), SC 1.4.11 (3:1 graphical objects) and SC 1.4.6 (7:1 AAA); pinned by `witness_cited_knee_threshold_rows_match_criterion_text`, no change made. The knee's discriminating red samples the only window where the two constants disagree -- no 8-bit channel at unity white lands there (10/255 = 0.03922 is below both knees), which is why the module's earlier pins never saw it -- and pins the outcome flip: `linearize_channel_scaled(393)` returns 30 under the cited knee (29 under the defective one), and the colour pair (393,0,500)/(8000,1000,8000) FAILS AA normal text under the cited knee while the defective knee called it sufficient. Both green after the fix (dag/test/claim/wcag_contrast_witness_test.dag, remote claim_batch --claim-run). The semver half of this row is repaired in a separate PR (#13412).",

"**WHY NO EXISTING WALL SEES IT, stated as a boundary rather than a gap to be closed by the neighbour.** The extdeps scope placement gate adjudicates the diff-added pairing of a subject declaration to a citation anchor at the level of NAMES: it can see that a subject is not the cited upstream's surface (tool CLI vs standard), because that mismatch is visible in what the module declares and what the anchor names. The lexical-vs-precedence difference lives in the code the declaration dispatches through, not in any name, module path, or anchor field; DESIGN section 4c makes annotations invisible to semantic passes, and a scope block's citation is an annotation about authority, not a type on the code path. So a fully green placement gate and a green namespace wave are both positive evidence about ownership and binding, and NO evidence about whether the decision the code implements is the one the standard defines.",

"**THE SAME FAILURE WITHOUT A CITATION HUNK.** The false attestation propagates: any caller that takes a module's compared/verified value as standard-ordered inherits the gap, and any new comparison path added under an already-cited module inherits the citation without touching prose. Neither edit produces a citation hunk the recognition rule below would see, which is why the rule names the code path, not only the anchor.",
Expand Down
42 changes: 41 additions & 1 deletion dag/test/claim/wcag_contrast_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ module test.claim.wcag_contrast_witness

import extdeps.accessibility.wcag_contrast {
linearize_channel_scaled, relative_luminance_scaled,
contrast_ratio_x100,
contrast_ratio_x100, contrast_at_least_hundredths,
rgb_scaled_luminance,
hsl_contrast_ratio_x100, hsl_contrast_at_least,
wcag_aa_normal_text_hundredths, wcag_aa_large_text_and_graphics_hundredths,
wcag_aaa_normal_text_hundredths,
}
import extdeps.color.srgb { RgbScaled }
import gunbc.design.theme {
Theme, assign, theme_contrast_ok,
night_theme, day_theme, night_canvas,
Expand Down Expand Up @@ -36,6 +39,43 @@ test fn witness_gamma_kernel_pinned() -> Bool {
&& low == (300 * 100) / 1292
}

// WCAG 2.2 #dfn-relative-luminance (https://www.w3.org/TR/WCAG22/#dfn-relative-luminance),
// cited by the module: for the sRGB colourspace, R = R_srgb/12.92 if R_srgb <= 0.04045,
// else ((R_srgb+0.055)/1.055)^2.4 — same for G and B. (Note 2 there records that the
// threshold was 0.03928 before May 2021; the current threshold is 0.04045, aligned with sRGB.)
//
// The cited knee and the code's knee disagree only on channels between 0.03928 and 0.04045
// (scale-10000: 393..404) — no 8-bit channel at unity white lands there, so the existing
// pins never sample the window. A channel at 0.0393 (393) does.

// 0.0393 <= 0.04045 -> the cited formula linearizes with c/12.92: (393*100)/1292 = 30.
// The defective 0.0392 knee sends 393 to the gamma curve instead (29).
test fn witness_cited_knee_linearizes_below_threshold() -> Bool {
linearize_channel_scaled(c_scaled: 393) == (393 * 100) / 1292
}
// The verdict FLIPS at a real WCAG threshold for a fresh input: A = (393, 0, 500) scaled —
// nearly black, red channel inside the knee window. Cited: L_A = (2126*30 + 722*39)/10000 = 9.
// Defective knee: L_A = (2126*29 + 722*39)/10000 = 8. B = (8000, 1000, 8000) scaled: L_B = 1789
// either way. AA normal text is 4.5:1 (450 hundredths); 100*(1789+500) = 228900:
// cited knee: 450*(9+500) = 229050 -> 228900 < 229050 -> contrast is NOT sufficient;
// defective knee: 450*(8+500) = 228600 -> 228900 >= 228600 -> (wrongly) sufficient.
test fn witness_cited_knee_flips_aa_normal_verdict() -> Bool {
let lum_a = rgb_scaled_luminance(rgb: RgbScaled { r: 393, g: 0, b: 500 })
let lum_b = rgb_scaled_luminance(rgb: RgbScaled { r: 8000, g: 1000, b: 8000 })
lum_a == 9
&& lum_b == 1789
&& !contrast_at_least_hundredths(lum_a_scaled: lum_a, lum_b_scaled: lum_b, threshold_hundredths: wcag_aa_normal_text_hundredths)
}

// Threshold half of the citation: SC 1.4.3 (AA) 4.5:1 normal text and 3:1 large-scale text;
// SC 1.4.11 (AA) 3:1 for graphical objects; SC 1.4.6 (AAA) 7:1. The module's rows must keep
// transcribing those numbers.
test fn witness_cited_knee_threshold_rows_match_criterion_text() -> Bool {
wcag_aa_normal_text_hundredths == 450
&& wcag_aa_large_text_and_graphics_hundredths == 300
&& wcag_aaa_normal_text_hundredths == 700
}

test fn witness_reviewer_oracle_night_text() -> Bool {
let r = hsl_contrast_ratio_x100(hue_a: 35, sat_a: 14, light_a: 78, hue_b: 226, sat_b: 30, light_b: 7)
r >= 1100 && r <= 1200
Expand Down