diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index a876f076d..22bf0c910 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -511,7 +511,9 @@ measurement service) so `tepp_not_available` is not mistaken for a
calibrated negative result. A failed lineage row tells the operator
to retry reconstruction, not to connect TEPP. A failed period-report
row tells the operator to rebuild the report. A pending TEPP row
-does not claim a calibrated measurement. A pending lineage row
+does not claim a calibrated measurement and does not say
+reconstruction. The list button accessible name includes the
+next-action sentence; detail repeats it (ADR 0014). A pending lineage row
says reconstruction has not started yet; open it and start
reconstruction. The
payload is lookup labels plus non-negative aggregate counts -- never
diff --git a/CHANGELOG.d/2.10.1-analysis-run-accessible-next-action.md b/CHANGELOG.d/2.10.1-analysis-run-accessible-next-action.md
new file mode 100644
index 000000000..3a0e27a02
--- /dev/null
+++ b/CHANGELOG.d/2.10.1-analysis-run-accessible-next-action.md
@@ -0,0 +1,5 @@
+# 2.10.1 Include next-action in analysis-run accessible names
+
+List button names include the kind-specific next-action sentence.
+Open a Failed TEPP row and hear connect the measurement service
+(ADR 0014).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ff4d3d66..85e50fc69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,16 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [2.10.1] - 2026-08-17
+
+### Fixed
+
+- Analysis-run list buttons now include the kind-specific next-action
+ sentence in the accessible name (WCAG 2.2 SC 4.1.2). Open a Failed
+ TEPP row: a screen reader hears connect the measurement service, not
+ only the run title. `aria-label` replaces button contents (ADR 0014).
+ No TEPP theta is invented.
+
## [2.10.0] - 2026-08-17
### Added
diff --git a/CLAUDE.md b/CLAUDE.md
index 1bcf50763..0e15bc773 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -27,8 +27,10 @@ theta or a local psychometric substitute. The home list caption stays
(ADR 0014). Open a Failed TEPP row, then connect a live TEPP
transport. A failed lineage row retries reconstruction -- it does not
mention TEPP. A failed period-report row rebuilds the report. A
-pending TEPP row does not claim a calibrated measurement. A pending
-lineage row says reconstruction has not started yet.
+pending TEPP row does not claim a calibrated measurement and does
+not say reconstruction. The list button name includes the
+next-action sentence. A pending lineage row says reconstruction has
+not started yet.
Digest prefixes stay audible; hover a prefix to read the full digest.
Opening a cutoff title shows the live post. Titles marked updated
after cutoff were rewritten after the run; the opened body names
diff --git a/docs/adr/0014-authorized-analysis-run-read.md b/docs/adr/0014-authorized-analysis-run-read.md
index 10e99d37d..b6d61dcaa 100644
--- a/docs/adr/0014-authorized-analysis-run-read.md
+++ b/docs/adr/0014-authorized-analysis-run-read.md
@@ -49,8 +49,13 @@ service. A failed lineage row tells the operator to retry
reconstruction, not to connect TEPP. A failed period-report row
tells the operator to rebuild the report from a current snapshot.
A pending or running TEPP row must not claim a calibrated
-measurement. A pending lineage row says reconstruction has not
-started yet. The detail now shows the legal
+measurement and must not say reconstruction. The list
+button accessible name is `Open analysis run: {caption}. {nextAction}`
+when a next action exists (WCAG 2.2 SC 4.1.2); otherwise the caption
+alone. `aria-label` replaces button contents (W3C Accessible Name and
+Description Computation 1.1), so the next-action sentence must live
+in that name. Detail repeats that sentence. A pending
+lineage row says reconstruction has not started yet. The detail now shows the legal
lifecycle the registry already stored. `POST /api/analysis-runs` now
records a Pending lineage run on an authorized cutoff capture
(ADR 0017). TEPP and period-report kinds are 422. Reconstruction, a
@@ -67,3 +72,11 @@ Educational Research Association.
Lebo, T., Sahoo, S., & McGuinness, D. (Eds.). (2013). *PROV-O: The PROV
ontology* (W3C Recommendation). World Wide Web Consortium.
https://www.w3.org/TR/2013/REC-prov-o-20130430/
+
+World Wide Web Consortium. (2018). *Accessible name and description
+computation 1.1* (W3C Recommendation).
+https://www.w3.org/TR/accname-1.1/
+
+World Wide Web Consortium. (2023). *Web content accessibility
+guidelines (WCAG) 2.2* (W3C Recommendation).
+https://www.w3.org/TR/WCAG22/
diff --git a/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md b/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
index 99dfff47b..66f382504 100644
--- a/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
+++ b/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
@@ -10,7 +10,7 @@
|---|---|---|
| W3C PROV-DM and PROV-O | Preserve identifiable entities, activities, agents, generation/use, and derivation without flattening provenance into display-only edges. | `analysis_source_snapshot`, `analysis_run`, authenticated requester, append-only status events, immutable digests; later product bindings continue to use the separate `provenance_*` layer from ADR 0011. |
| W3C Time Ontology in OWL | Keep temporal concepts explicit and avoid collapsing distinct clocks. | Evidence availability and snapshot capture remain on `analysis_source_snapshot`; analysis knowledge cutoff and request time remain on `analysis_run`; status occurrence and database record time remain distinct. `GET /api/analysis-runs/{id}` visible posts apply `created_at <= knowledge_cutoff` (ADR 0016). Detail compares live `updated_at` with that cutoff and marks titles rewritten after the run. Seed records the built period report as a later Succeeded run on that same snapshot (ADR 0024) without copying a theta onto the registry row. The cutoff-known body is read from `source_post_revision` on the opened post, not from the run payload (ADR 0025). |
-| W3C Accessible Name and Description Computation 1.1 | Do not let `aria-label` replace visible text the operator must hear. | Analysis-run digest prefixes live in a labeled group; the prefixes remain the accessible contents and the full digest is on `title` for hover verification. |
+| W3C Accessible Name and Description Computation 1.1 | Do not let `aria-label` replace visible text the operator must hear. | Analysis-run digest prefixes live in a labeled group; the prefixes remain the accessible contents and the full digest is on `title` for hover verification. List-button names include the kind-specific next-action sentence (ADR 0014; WCAG 2.2 SC 4.1.2). |
| ISO 8601-1:2019 | Use unambiguous timestamp representation and timezone-aware persistence. | PostgreSQL `timestamptz` for availability, capture, cutoff, request, occurrence, and record clocks; tests use explicit `Z` offsets. |
| PostgreSQL 18 constraints and trigger contracts | Put integrity close to durable truth and use constraints for row shape while triggers enforce cross-row state and serialization. | Digest/check constraints, category allowlists, account-scoped uniqueness, shape constraints, immutable-row triggers, shared snapshot-row locking, and serialized status transitions. |
| NIST SP 800-92 | Treat audit records as bounded, protected operational evidence rather than unstructured application logging. | Append-only status events, machine failure codes, actor identity, occurrence/record clocks, fail-closed rollback, `invoking_session_role` on each retention event, and exclusion of raw source/provider payloads. |
@@ -125,3 +125,6 @@ computation 1.1* (W3C Recommendation). https://www.w3.org/TR/accname-1.1/
World Wide Web Consortium. (2022). *Time ontology in OWL* (W3C Recommendation).
https://www.w3.org/TR/owl-time/
+
+World Wide Web Consortium. (2023). *Web content accessibility guidelines
+(WCAG) 2.2* (W3C Recommendation). https://www.w3.org/TR/WCAG22/
diff --git a/frontend/package.json b/frontend/package.json
index 58489b343..49b74b3e0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.10.0",
+ "version": "2.10.1",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 0e35f6832..e5180b90d 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -2137,6 +2137,12 @@ describe("App, authenticated", () => {
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
);
+ expect(
+ screen.getByRole("button", {
+ name: "Open analysis run: TEPP measurement · Failed · Demo Corp. Open this run to see why it failed, then connect the measurement service and re-run.",
+ }),
+ ).toBeInTheDocument();
+
await userEvent.click(
screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Succeeded · Demo Corp",
@@ -2213,7 +2219,7 @@ describe("App, authenticated", () => {
await userEvent.click(
screen.getByRole("button", {
- name: "Open analysis run: TEPP measurement · Failed · Demo Corp",
+ name: "Open analysis run: TEPP measurement · Failed · Demo Corp. Open this run to see why it failed, then connect the measurement service and re-run.",
}),
);
expect(
@@ -2273,12 +2279,29 @@ describe("App, authenticated", () => {
expect(screen.queryByRole("heading", { name: "Body this run knew" })).not.toBeInTheDocument();
});
+ it("finds a failed TEPP list button by the next-action accessible name", async () => {
+ stubBackend();
+ render(