Skip to content

Commit 12eaa17

Browse files
authored
Merge pull request #3 from snapsynapse/chore/pin-accessibility-gate
Pin accessibility gate across Knowledge-as-Code surfaces
2 parents 51abb92 + 4ef9a37 commit 12eaa17

33 files changed

Lines changed: 253 additions & 96 deletions

File tree

‎.github/workflows/build.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
- uses: actions/checkout@v4
7373
- uses: actions/setup-node@v4
7474
with:
75-
node-version: '20'
75+
node-version: '22'
7676

7777
- name: Build site
7878
run: node scripts/build.js
@@ -86,36 +86,36 @@ jobs:
8686
fi
8787
8888
- name: Run skill-a11y-audit (WCAG 2.1 AA)
89-
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@main
89+
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@6af2c95a56ff058cddf0c6febc71512db1d6d19b # v3.1.0
9090
with:
9191
serve-path: ./docs
9292
sitemap: http://localhost:8088/sitemap.xml
9393
sitemap-find: ${{ steps.site.outputs.base }}
9494
sitemap-replace: http://localhost:8088
95-
fail-on: errors
95+
fail-on: major
9696

9797
- name: Audit canonical root landing
9898
if: github.repository == 'snapsynapse/knowledge-as-code-template'
99-
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@main
99+
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@6af2c95a56ff058cddf0c6febc71512db1d6d19b # v3.1.0
100100
with:
101101
serve-path: ./
102102
port: 8089
103103
sitemap: http://localhost:8089/tests/fixtures/a11y-root-sitemap.xml
104104
sitemap-find: https://knowledge-as-code.com
105105
sitemap-replace: http://localhost:8089
106-
fail-on: errors
106+
fail-on: major
107107
artifact-name: a11y-root-results
108108

109109
- name: Audit canonical demo deployment
110110
if: github.repository == 'snapsynapse/knowledge-as-code-template'
111-
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@main
111+
uses: snapsynapse/skill-a11y-audit/.github/actions/scan@6af2c95a56ff058cddf0c6febc71512db1d6d19b # v3.1.0
112112
with:
113113
serve-path: ./demo
114114
port: 8090
115115
sitemap: http://localhost:8090/sitemap.xml
116116
sitemap-find: https://knowledge-as-code.com/demo
117117
sitemap-replace: http://localhost:8090
118-
fail-on: errors
118+
fail-on: major
119119
artifact-name: a11y-demo-results
120120

121121
# GitHub Pages serves directly from `main` branch root on this canonical repo.

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ pattern.
1414
1515
## [Unreleased]
1616

17+
### Changed
18+
- Pinned all three accessibility scan surfaces to A11y Audit v3.1.0 at its verified immutable commit, selected Node.js 22 explicitly for the scanner, and adopted the fail-closed major-findings policy.
19+
20+
### Fixed
21+
- Repaired shared generated-site and canonical landing contrast, link distinction, decorative marker, matrix-label, and theme-control behavior found while scanning all 45 required routes.
22+
1723
## [1.2.0] - 2026-09-05
1824

1925
### Added

‎MAINTENANCE.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ The maintained public path is:
1616

1717
The current supported runtime is Node.js 18 or later on macOS and Linux. Windows may work but is not part of the tested support promise.
1818

19+
## Accessibility gate
20+
21+
The canonical CI scans the generated template, canonical root landing, and canonical demo with the immutable A11y Audit identity and fail-closed major-findings policy recorded in [the 2026-09-07 accessibility adoption evidence](audits/a11y-2026-09-07.md). The generator keeps its Node.js 18-or-later support promise; the accessibility job selects its scanner's required Node.js 22 runtime explicitly.
22+
1923
## Maintenance priorities
2024

2125
Work is prioritized in this order:

‎MANIFEST.yaml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ files:
8282

8383
- path: scripts/build.js
8484
role: tooling
85-
hash: sha256:0b9445770253923bee61b49b90a883bf3db3a6511777b9da928ae26022f8382a
85+
hash: sha256:ad7861d1350c55c69294d08bbaa175ffe9b11b53139f17b8ac7ebed58bea3191
8686
note: Static site generator
8787

8888
- path: scripts/assets/favicon.svg
@@ -122,7 +122,7 @@ files:
122122

123123
- path: scripts/eval.js
124124
role: tooling
125-
hash: sha256:7f5622d2245588bf7cdf00cd2afcf651f7b9a44565b3572645b931bdfcfd0e49
125+
hash: sha256:f87c45678844465c5d84bb838664802cfa3aeaab397f3b60cddda33a6ad4a043
126126
note: Behavioral and security regression suite
127127

128128
- path: mcp-server.js
@@ -157,7 +157,7 @@ files:
157157

158158
- path: index.html
159159
role: protocol
160-
hash: sha256:8a431b58a12102a94602548fa3810ce348e10b5e2701e0a708649c1c78c595f7
160+
hash: sha256:2c13538b1d72fb4b365f54e77a41a9697fc77f71d9ab26622e55e47147f9f8db
161161
note: Canonical landing and executable quick start
162162

163163
- path: sitemap.xml
@@ -182,7 +182,7 @@ files:
182182

183183
- path: MAINTENANCE.md
184184
role: protocol
185-
hash: sha256:0d1fde34cb47dc6a25e71ff9bbfa28adb23ebf41bc99167850d919369881457d
185+
hash: sha256:51a5afe6c658692a7d1789dc695d64451ba7c4660719e43a6776b1ef03c8e4fa
186186
note: Compatibility and upgrade policy
187187

188188
- path: VERIFICATION.md

‎audits/a11y-2026-09-07.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Accessibility adoption evidence
2+
3+
Date: 2026-09-07
4+
5+
## Action and policy identity
6+
7+
The three required accessibility scans use A11y Audit v3.1.0 at immutable commit `6af2c95a56ff058cddf0c6febc71512db1d6d19b`. The public annotated tag `refs/tags/v3.1.0` resolves through tag object `1fccece25b28c99bcbbf26881edc336c586f6d5e` to that commit. This relationship was independently verified from the public Git remote before adoption.
8+
9+
The accessibility job selects Node.js 22 explicitly. The action requires Node.js 22.12.0 or later and runs axe-core 4.12.1 through Puppeteer 25.10.0. Local acceptance used Node.js 26.7.0, which satisfies that runtime floor, with the same scanner dependencies from the v3.1.0 source checkout.
10+
11+
All three scans use `fail-on: major` without a baseline. Confirmed critical or serious standards violations block. Moderate, minor, and best-practice-only findings remain advisory. Unknown violation impact, potentially major incomplete evidence, missing coverage, and scan errors cannot pass.
12+
13+
## Built surfaces and route coverage
14+
15+
The tested source was the clean v1.2.0 baseline at `51abb92806393ae4ff0e6e671e77fe383ae0f690` plus the reviewed accessibility changes described below.
16+
17+
| Surface | Build or source identity | Required routes | Result | Result SHA-256 |
18+
|---|---|---:|---|---|
19+
| Template default | `node scripts/build.js`, transient `docs/`, `https://example.com/` rewritten to the local origin | 22/22 sitemap routes | Pass | `67e34ff3d895103d4edbbfebe74f795cd1fb711779e075e8d3593ce914d8cfec` |
20+
| Canonical root landing | Hand-maintained root `index.html`, root fixture sitemap, Pages root unchanged | 1/1 reviewed route | Pass | `e1492560ce7ae26b34d57ef3f3558871b2013d6c65b1eac4e186d0ae55127f30` |
21+
| Canonical demo | `KAC_OUTPUT_DIR=demo KAC_SITE_URL=https://knowledge-as-code.com/demo/ KAC_REPO_URL=https://github.com/snapsynapse/knowledge-as-code-template node scripts/build.js` | 22/22 sitemap routes | Pass | `d8cbce799fe5ec0b90def3a6b127d6b034b3c61fe2f479ad480a5f61dafef2fc` |
22+
23+
Each final result contains zero confirmed major findings, zero nonblocking findings, zero unknown-impact violations, zero blocking or advisory incomplete candidates, and zero scan errors. The result files are local execution evidence and are identified here by digest; they are not shipped as product artifacts.
24+
25+
The initializer golden path passed after the source changes. It continues to generate the supported fork structure and derives `.github/workflows/pages.yml` without moving the canonical repository's root Pages source. The generator's Node.js 18 and 20 compatibility matrix also remains in place; Node.js 22 is specific to the accessibility job.
26+
27+
## Findings and repairs
28+
29+
The initial template and demo scans had no confirmed violations, but each was inconclusive with 380 serious incomplete candidates across all 22 routes. A body gradient prevented deterministic contrast evaluation for 296 color candidates and 84 links. The root landing had one confirmed serious link distinction violation on the `PUBLISH-MCP.md` link plus 14 serious incomplete candidates involving a roleless labeled wordmark, decorative punctuation, and gradient-backed text.
30+
31+
Repairs were made in the owning sources and regenerated into both output trees:
32+
33+
- The generated site uses the existing dark or light background token as a solid page background so text contrast can be evaluated deterministically.
34+
- Prose, list, breadcrumb, and footer links are visibly underlined.
35+
- Timeline rails and markers are real decorative elements, relationship arrows are expressed as the words “maps to,” matrix counts have descriptive accessible names, and empty matrix cells retain “Not covered” text for assistive technology.
36+
- The root wordmark has an image role, decorative separators and file arrows were removed, the confirmed link is underlined, and promotional statistics retain real text on a solid backing over the decorative gradient.
37+
- The generated skip link and back-to-top control choose black or white foreground text from each configured accent color.
38+
39+
No axe rule was disabled, no route was skipped, and no baseline was created or refreshed.
40+
41+
## Theme checks
42+
43+
Targeted axe checks covered the root landing under light and dark `prefers-color-scheme` emulation. The generated template and canonical demo index, matrix, and timeline pages were checked after applying and settling the same `light-mode` class changed by their theme button, and again in the default dark state. All 14 page-and-theme cases returned zero violations and zero incomplete candidates.
44+
45+
The root highlighted heading text measured 17.40:1 against the light page background and 16.34:1 against the light highlight; the dark equivalents were 16.62:1 and 13.69:1. The generated back-to-top glyph measured 7.29:1 in light mode and 10.48:1 in dark mode. Its control boundary measured 6.70:1 and 8.51:1 respectively.
46+
47+
This automated result covers only the routes, themes, and rules described here. It does not establish complete WCAG conformance or replace manual testing.

‎demo/404.html‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
:is(html, body).light-mode .status-badge.deprecated { background: #455a64; color: #fff; }
2929
:root { --accent: #4fc3f7; }
3030
:is(html, body).light-mode { --accent: #0055aa; }
31+
.skip-link, .back-to-top { color: #000; }
32+
:is(html, body).light-mode .skip-link, :is(html, body).light-mode .back-to-top { color: #fff; }
3133
</style>
3234
<script>
3335
(function() {
@@ -109,7 +111,7 @@ <h1 style="margin-top:2rem;">404 — Page Not Found</h1>
109111
var btn = document.createElement('button');
110112
btn.className = 'back-to-top';
111113
btn.setAttribute('aria-label', 'Back to top');
112-
btn.textContent = '\u2191';
114+
btn.innerHTML = '<span aria-hidden="true">\u2191</span>';
113115
document.body.appendChild(btn);
114116
window.addEventListener('scroll', function() { btn.classList.toggle('visible', window.scrollY > 400); });
115117
btn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });

‎demo/about.html‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
:is(html, body).light-mode .status-badge.deprecated { background: #455a64; color: #fff; }
3030
:root { --accent: #4fc3f7; }
3131
:is(html, body).light-mode { --accent: #0055aa; }
32+
.skip-link, .back-to-top { color: #000; }
33+
:is(html, body).light-mode .skip-link, :is(html, body).light-mode .back-to-top { color: #fff; }
3234
</style>
3335
<meta name="description" content="How Example Knowledge Base is sourced, verified, and maintained: 2 frameworks, 3 requirements, and 4 provisions from 2 organizations.">
3436
<meta property="og:title" content="About">
@@ -79,7 +81,7 @@ <h1><a href="index.html" onclick="passTheme(this)">Example Knowledge Base</a></h
7981
<h2 style="margin-top: 0.5rem;">About</h2>
8082
<p>A structured reference tracking example entities across categories. Tracks <strong>2 frameworks</strong>, <strong>3 requirements</strong>, <strong>4 provisions</strong>, and <strong>2 organizations</strong>.</p>
8183
<h3>Data Model</h3>
82-
<p><strong>Organization</strong> &rarr; <strong>Framework</strong> &rarr; <strong>Provision</strong> &rarr; <strong>Requirement</strong></p>
84+
<p><strong>Organization</strong> maps to <strong>Framework</strong> maps to <strong>Provision</strong> maps to <strong>Requirement</strong></p>
8385
<p>Requirements are the stable anchors. Provisions are the implementations — different frameworks implements the same requirements differently.</p>
8486
<h3>JSON API</h3>
8587
<ul>
@@ -129,7 +131,7 @@ <h3>Contributing</h3>
129131
var btn = document.createElement('button');
130132
btn.className = 'back-to-top';
131133
btn.setAttribute('aria-label', 'Back to top');
132-
btn.textContent = '\u2191';
134+
btn.innerHTML = '<span aria-hidden="true">\u2191</span>';
133135
document.body.appendChild(btn);
134136
window.addEventListener('scroll', function() { btn.classList.toggle('visible', window.scrollY > 400); });
135137
btn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });

‎demo/applies-to/federal/index.html‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
:is(html, body).light-mode .status-badge.deprecated { background: #455a64; color: #fff; }
3030
:root { --accent: #4fc3f7; }
3131
:is(html, body).light-mode { --accent: #0055aa; }
32+
.skip-link, .back-to-top { color: #000; }
33+
:is(html, body).light-mode .skip-link, :is(html, body).light-mode .back-to-top { color: #fff; }
3234
</style>
3335

3436
<meta name="description" content="1 framework that applies to Federal, with status and effective dates.">
@@ -129,7 +131,7 @@ <h1><a href="../../index.html" onclick="passTheme(this)">Example Knowledge Base<
129131
var btn = document.createElement('button');
130132
btn.className = 'back-to-top';
131133
btn.setAttribute('aria-label', 'Back to top');
132-
btn.textContent = '\u2191';
134+
btn.innerHTML = '<span aria-hidden="true">\u2191</span>';
133135
document.body.appendChild(btn);
134136
window.addEventListener('scroll', function() { btn.classList.toggle('visible', window.scrollY > 400); });
135137
btn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });

‎demo/applies-to/international/index.html‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
:is(html, body).light-mode .status-badge.deprecated { background: #455a64; color: #fff; }
3030
:root { --accent: #4fc3f7; }
3131
:is(html, body).light-mode { --accent: #0055aa; }
32+
.skip-link, .back-to-top { color: #000; }
33+
:is(html, body).light-mode .skip-link, :is(html, body).light-mode .back-to-top { color: #fff; }
3234
</style>
3335

3436
<meta name="description" content="1 framework that applies to International, with status and effective dates.">
@@ -129,7 +131,7 @@ <h1><a href="../../index.html" onclick="passTheme(this)">Example Knowledge Base<
129131
var btn = document.createElement('button');
130132
btn.className = 'back-to-top';
131133
btn.setAttribute('aria-label', 'Back to top');
132-
btn.textContent = '\u2191';
134+
btn.innerHTML = '<span aria-hidden="true">\u2191</span>';
133135
document.body.appendChild(btn);
134136
window.addEventListener('scroll', function() { btn.classList.toggle('visible', window.scrollY > 400); });
135137
btn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });

‎demo/assets/styles.css‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ html {
5454
top: 0;
5555
}
5656

57+
.sr-only {
58+
position: absolute;
59+
width: 1px;
60+
height: 1px;
61+
padding: 0;
62+
margin: -1px;
63+
overflow: hidden;
64+
clip: rect(0, 0, 0, 0);
65+
white-space: nowrap;
66+
border: 0;
67+
}
68+
5769
/* ========================================
5870
CSS VARIABLES (DARK MODE DEFAULT)
5971
======================================== */
@@ -125,7 +137,7 @@ html {
125137

126138
body {
127139
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
128-
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
140+
background: var(--bg-gradient-start);
129141
min-height: 100vh;
130142
color: var(--text-primary);
131143
padding: 20px;
@@ -137,6 +149,14 @@ body {
137149
a { color: var(--accent); text-decoration: none; }
138150
a:hover { text-decoration: underline; }
139151

152+
#main-content p a,
153+
#main-content li a,
154+
.breadcrumb a,
155+
footer p a {
156+
text-decoration: underline;
157+
text-underline-offset: 0.12em;
158+
}
159+
140160
h2 {
141161
font-size: 1.5rem;
142162
color: var(--text-heading);
@@ -702,8 +722,7 @@ th[data-sortable].sort-desc::after {
702722
position: relative;
703723
}
704724

705-
.timeline::before {
706-
content: '';
725+
.timeline-track {
707726
position: absolute;
708727
left: 20px;
709728
top: 0;
@@ -726,8 +745,7 @@ th[data-sortable].sort-desc::after {
726745
margin: 4px 0;
727746
}
728747

729-
.timeline-entry::before {
730-
content: '';
748+
.timeline-marker {
731749
position: absolute;
732750
left: 14px;
733751
top: 18px;
@@ -738,17 +756,17 @@ th[data-sortable].sort-desc::after {
738756
background: var(--bg-gradient-start);
739757
}
740758

741-
.timeline-entry.past::before {
759+
.timeline-entry.past .timeline-marker {
742760
background: var(--status-enforcing);
743761
border-color: var(--status-enforcing);
744762
}
745763

746-
.timeline-entry.future::before {
764+
.timeline-entry.future .timeline-marker {
747765
background: transparent;
748766
border-color: var(--accent);
749767
}
750768

751-
.timeline-entry.today-marker::before {
769+
.timeline-entry.today-marker .timeline-marker {
752770
background: var(--accent);
753771
border-color: var(--accent);
754772
box-shadow: 0 0 8px var(--accent);

0 commit comments

Comments
 (0)