feat(templates): enforce required metadata - #2
Merged
Conversation
This was referenced May 22, 2026
forkwright
added a commit
that referenced
this pull request
Aug 16, 2026
#153) Closes #64 Not closing #136 — see "Notes for the reviewer" below for why. ## Session update (rebase + genuine CI failure + comment-tag gap) This branch was cut before four PRs landed on `main` (#141 favicon config, #150 asset-provenance label fix, #152 home-page heading, #154 consumer schema registry). Rebased onto `origin/main` (3d9432a). One real conflict, in `ci/run-fixtures.sh` — main added four new check invocations (`check-favicon-path.py`, `check-init-favicon-path.py`, `check-home-heading.py`, `check-fixture-corpus-exemption.sh`) in the same region this branch adds two (`check-interactive-contrast.py`, `check-interactive-contrast-selftest.py`). Both sides were right; composed rather than picked one — every check from both branches now runs (`ci/run-fixtures.sh:8-24`). `gate / full-gate-build` was failing on this branch (run 31921507701) with a genuine defect, not a stale base: `interactive-state-contrast-selftest.spec.ts` imported `assertContrast`/`contrastRatio`/`textContrastFloor` directly from `interactive-state-contrast.spec.ts`. Playwright refuses to load a test file that another test file imports and fails the entire collection, not just the importing file: ``` Error: test file "smoke/interactive-state-contrast-selftest.spec.ts" should not import test file "smoke/interactive-state-contrast.spec.ts" Listing tests: Total: 0 tests in 0 files ``` That's what `{"stage":"playwright-smoke","status":"fail","duration_ms":1050,...}` in the CI log was — a collection-time failure before any test ran, which is why the log carried no further detail. (The `cargo` exit-101 also visible in that run's log is unrelated and pre-existing: `Swatinem/rust-cache`'s `cargo metadata` probe fails on every typikon run because this repo has no `Cargo.toml` — documented and treated as non-fatal in `.github/workflows/gate-attestation.yml`'s own header comment. It logs an error but the job continues past it; the actual failure is the playwright stage above.) Fixed by extracting the pure contrast/threshold math into a new non-spec module, `ci/smoke/interactive-contrast.ts`, and pointing both spec files at it instead of one importing the other (`ci/smoke/interactive-contrast.ts` new; `ci/smoke/interactive-state-contrast.spec.ts:26-33` + `ci/smoke/interactive-state-contrast-selftest.spec.ts:24` updated). `readEffectiveStyle` stays in the browser-driven spec — it needs a Playwright `Locator` and only that file uses it. **Negative fixture: `ci/smoke/interactive-state-contrast-selftest.spec.ts` — watched failing by `npx playwright test --config ci/playwright.config.ts --list`, which produced exactly the "should not import test file" / "Total: 0 tests in 0 files" error above before the fix, and now lists 7 tests across 3 files (verified again against the real `examples/sample-blog` build: `npx playwright test` — 16/16 passed, including the browser-driven interactive-state assertions on every real route).** This isn't a check I added — it's Playwright's own test-collection step, which already runs on every `bin/typikon-check`/CI invocation with no additional wiring; the defect was that this branch's own new file tripped it. While rebasing I re-audited `check-interactive-contrast.py` against its own established comment-tag convention (the earlier review round tagged two blocks — FAQ deep-link anchor, home-triad-mark — that had rationale prose with no `WHY:`/`NOTE:`/etc. tag). Two more blocks in the same file had the identical gap and were missed by that fix: the `.nav-links a:hover::after` "why no separate entry" note (`ci/check-interactive-contrast.py:216-219`) and the home-nav-secondary-nav fallthrough note (`:221-224`), plus the `NOT_TEXT_CONTRAST` dict's own rationale (`:337-343`). Tagged all three `WHY:`, matching the file's own convention. Also tagged `ci/smoke/interactive-state- contrast.spec.ts`'s `TARGETS` comment (`:56-63`) — same defect class, same file inconsistency (the file's other rationale comment, `readEffectiveStyle`'s, already carried `WHY:`). ## Addressed in the prior independent adversarial review An independent adversarial review returned `must_fix` on three findings against an earlier version of this PR. All three were fixed at that time and verified intact after this session's rebase: 1. **Correctness — the property regex had no left-boundary anchor.** `_prop_var_re()` (`ci/check-interactive-contrast.py:118-126`) built `(?:{prop_alt})\s*:\s*...` with nothing anchoring the start, unlike the sibling `COLOR_AFFECTING_RE` two lines above it, which uses `(?:^|;)\s*(...)`. `prop_alt="color"` matched the substring `color:` inside `background-color:`/`text-decoration-color:`/etc., so a selector with both properties on the same rule could silently resolve to the WRONG property's value with no error. Fixed by mirroring `COLOR_AFFECTING_RE`'s `(?:^|;)` boundary in both `_prop_var_re()` and its sibling `_prop_inherit_re()` (`ci/check-interactive-contrast.py:118-136`). Reproduce the pre-fix bug: ``` python3 -c " import sys; sys.path.insert(0, 'ci') import importlib.util spec = importlib.util.spec_from_file_location('m', 'ci/check-interactive-contrast.py') m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) print(m.find_declared_var('.widget { background-color: var(--danger); }', '.widget', 'color')) " ``` prints `None` now; printed `danger` before the fix. Re-verified this session: `python3 ci/check-interactive-contrast.py` still passes clean post-rebase. 2. **Missing negative-case fixture for the checker's own logic.** Fixed by committing `ci/check-interactive-contrast-selftest.py`, wired into `ci/run-fixtures.sh` immediately after the checker it tests — function-level synthetic-CSS assertions (including the reviewer's own two repro cases) plus an end-to-end mutation of the real `static/css/style.css` (reverts the #64 fix, asserts the exact WCAG-floor failure, restores byte-identical, confirms a second uncovered-rule mutation fails closed too). The Playwright side got the same treatment via `ci/smoke/interactive-state-contrast-selftest.spec.ts`. Re-run this session: both pass clean (`python3 ci/check-interactive-contrast-selftest.py`; `npx playwright test ci/smoke/interactive-state-contrast-selftest.spec.ts` — 5/5). 3. **Two comment blocks broke the closed comment-tag set.** `ci/check-interactive-contrast.py:295-299` (FAQ deep-link anchor) and the home-triad-mark block were tagged `WHY:`. (This session found and fixed two more instances of the same gap — see above.) ## Enumerated acceptance criteria Issue #64's `Done when:` (restated identically in the reopening comment): **"a matrix covering every token/background/usage combination, assertions across the interactive states, and a gate stage that goes red when any of them regresses."** | Bullet | Satisfied by | Evidence | |---|---|---| | a matrix covering every token/background/usage combination | `MATRIX` in `ci/check-interactive-contrast.py:200-335` — 41 entries, each resolved from the live CSS source (not hand-typed), covering every selector this theme renders in a non-default interactive state that touches `color`, a text-bearing `background`, or `opacity` | `ci/check-interactive-contrast.py:426-461` walks each entry via `resolve_chain()`; run output lists all 41 with their resolved token/ratio | | assertions across the interactive states | Static: `resolve_chain()` + WCAG floor check per `MATRIX` entry (`ci/check-interactive-contrast.py:426-461`). Browser: `ci/smoke/interactive-state-contrast.spec.ts:104-146` drives real Chromium through default → focus → hover → active per element via `getComputedStyle`. `:visited` covered by source-absence check (browser cannot observe it — history-sniffing protection, documented `ci/smoke/interactive-state-contrast.spec.ts:14-20`) | Real run against `examples/sample-blog`: 16/16 Playwright tests pass across every route in the build's sitemap | | a gate stage that goes red when any of them regresses | Coverage scan `ci/check-interactive-contrast.py:488-512` fails closed on any new/unreviewed state rule; `ci/run-fixtures.sh:8-24` runs both the static and selftest checks on every `bin/typikon-check`/CI invocation; `ci/smoke/` specs run in the same `npx playwright test` CI already invokes | Negative fixtures (this PR's own selftest files) prove the red path — see "Addressed" #2 above and the negative-fixture line above for this session's fix | Issue #136's `Done when:` — **not this PR's job**: its specific defect (`--rule` used as a form-control border) already merged in #140 (`ci/check-control-contrast.py` verifies `.purchase-box select` / `.buttondown-form input[type="email"]` both clear 3:1 — confirmed still passing post-rebase: `python3 ci/check-control-contrast.py`). The one thing that kept #136 open — the home page's missing `<h1>` — was split into #142 and landed on `main` as #152 before this branch rebased past it (`ci/check-home-heading.py`, now composed into `ci/run-fixtures.sh:16` alongside this PR's own additions). See "Notes for the reviewer" below. ## Evidence (from the original submission, still accurate) - `ci/check-interactive-contrast.py:200-335` (`MATRIX`) — every selector this theme renders in a non-default interactive state that touches `color`, a text-bearing `background`, or `opacity`, each entry resolved from the live CSS source rather than hand-typed. - `ci/check-interactive-contrast.py:139-160` (`find_declared_var`, left-boundary-anchored) + `:163-177` (`resolve_chain`) — walks an explicit selector chain exactly mirroring real CSS cascade/inheritance. - `ci/check-interactive-contrast.py:208-209` — `.nav-links a:nth-child(3):hover`, resolved from source, must equal `--aporia-interactive` (4.91:1 against `--bg`) — the exact rule #64 originally fixed. - `ci/check-interactive-contrast.py:488-512` (coverage scan) — parses every `:hover`/`:focus`/`:focus-visible`/`:active`/`:visited`/`:disabled` rule touching `color`/`background`/`opacity` and fails if it isn't in `MATRIX`, `NOT_TEXT_CONTRAST`, or the opacity special-case. - `ci/contrast.py` — the WCAG luminance/contrast formula and `:root` parser, extracted from `ci/check-control-contrast.py` (#136) so both checks share one formula. - `ci/smoke/interactive-state-contrast.spec.ts:128-148` (`readEffectiveStyle`) drives real Chromium and reads `getComputedStyle`'s actual composited output, catching a cascade bug a static parse structurally cannot see. - `:visited` is explicitly out of the Playwright spec's reach (`ci/smoke/interactive-state-contrast.spec.ts:14-20`): every major browser engine reports `:visited` computed style as if unvisited, to block history-sniffing. `ci/check-interactive-contrast.py` covers it instead by parsing the CSS source directly and confirming no `:visited` rule exists. - `ci/run-fixtures.sh:8-24` + `.kanon-ci.toml` — both checks and their negative-case fixtures wired into the gate. ## What a reviewer would have to disprove - That any `MATRIX` entry's resolved token is wrong — reproducible by running `python3 ci/check-interactive-contrast.py` and reading the printed line for that entry against `static/css/style.css` directly. - That the coverage scan misses a real state-changing rule — reproducible by adding any `:hover`/`:focus`/`:active`/`:visited`/`:disabled` color rule anywhere in `style.css` and confirming the script fails before making it pass by adding a matching entry. - That `find_declared_var()`'s property regex can still cross-match a wrong property — reproducible by running `python3 ci/check-interactive-contrast-selftest.py`, which fails closed on that exact class today. - That the Playwright cross-import defect from this session is still present — reproducible by running `npx playwright test --config ci/playwright.config.ts --list` and confirming it lists tests rather than erroring. ## Notes for the reviewer - Issue #136 is not closed by this PR and doesn't need to be — its form-control-border-contrast defect already merged in #140, and the one thing keeping #136 open (the home page's missing `<h1>`) was correctly split into #142, landed as #152, and is out of this PR's scope. - No CSS colors changed. `git diff origin/main...HEAD -- static/css/style.css` is empty by design. - `gate / full-gate-build` and `gate / gate` are green on this branch's current tip (run 31923043711, post-rebase + post-fix) — mergeable, clean. - `verda-build` (the fleet's local-gate box) is currently unusable for this repo's `fixtures-gate` stage for two reasons unrelated to this branch: its disk is at 100% (286M free on 2.4T — tracked in `metis-ops/collab/OPERATOR.md`), and separately it has no `pip`/`jsonschema` installed at all (CI's own `check_cmd` installs `jsonschema`/`fonttools`/`Brotli` via pip before running `ci/run-fixtures.sh`; verda's gate script does not). Both are environment gaps on that box, not code defects — confirmed by running every `ci/run-fixtures.sh` script individually on this branch, plus the real Playwright suite against a real `zola build`, all green. GitHub Actions' `gate / full-gate-build` is the authoritative signal here and is green. --------- Co-authored-by: forkwright <cody@forkwright.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #1.
Validation
price_sourcefails validationNot rerun locally
zolais not on PATH here. The worker reported Zola 0.22.1checkandbuildfor both fixtures, CSP checks against isolated outputs, and negative render assertion checks before handoff.Breaking change
This is a breaking schema change for consumers with FAQ, journal, product, or sizing-guide content. No automatic migration is included because meaningful
audienceand source metadata cannot be inferred safely.Source note
The Ergon source citation path
src/default-templates/deliverable-pdf/README.md:13is absent on currentorigin/main; this implementation follows the repo's live schema/template patterns.