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
4 changes: 3 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.d/2.10.1-analysis-run-accessible-next-action.md
Original file line number Diff line number Diff line change
@@ -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).
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 15 additions & 2 deletions docs/adr/0014-authorized-analysis-run-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/
5 changes: 4 additions & 1 deletion docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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/
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.10.0",
"version": "2.10.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
51 changes: 38 additions & 13 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(<App />);

const teppButton = await screen.findByRole("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.",
});
expect(teppButton).toHaveAccessibleName(
"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(
screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Succeeded · Demo Corp",
}),
).toHaveAccessibleName("Open analysis run: Lineage reconstruction · Succeeded · Demo Corp");
});

it("tells a running lineage run to refresh the durable outbox", async () => {
stubBackend({ runningLineageRun: true });
render(<App />);

const lineageButton = await screen.findByRole("button", {
name: "Open analysis run: Lineage reconstruction · Running · Demo Corp",
name: "Open analysis run: Lineage reconstruction · Running · Demo Corp. Refresh this run. Start already queued the work on the durable outbox.",
});
expect(lineageButton).toHaveTextContent(
"Refresh this run. Start already queued the work on the durable outbox.",
Expand All @@ -2296,10 +2319,10 @@ describe("App, authenticated", () => {

await screen.findByRole("list", { name: "Analysis runs" });
const lineageButton = screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Failed · Demo Corp",
name: "Open analysis run: Lineage reconstruction · Failed · Demo Corp. Open this run to see why it failed, then retry reconstruction from a current snapshot.",
});
const teppButton = 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(lineageButton).toHaveTextContent(
"Open this run to see why it failed, then retry reconstruction from a current snapshot.",
Expand Down Expand Up @@ -2584,7 +2607,7 @@ describe("App, authenticated", () => {
render(<App />);

const reportButton = await screen.findByRole("button", {
name: "Open analysis run: Period report · Failed · Demo Corp",
name: "Open analysis run: Period report · Failed · Demo Corp. Open this run to see why it failed, then rebuild the period report from a current snapshot.",
});
expect(reportButton).toHaveTextContent(
"Open this run to see why it failed, then rebuild the period report from a current snapshot.",
Expand All @@ -2608,11 +2631,13 @@ describe("App, authenticated", () => {
stubBackend({ pendingTeppRun: true });
render(<App />);

await userEvent.click(
await screen.findByRole("button", {
name: "Open analysis run: TEPP measurement · Pending · Demo Corp",
}),
);
const teppButton = await screen.findByRole("button", {
name: "Open analysis run: TEPP measurement · Pending · Demo Corp. Open this run to confirm which posts TEPP will measure. Measurement has not started yet — this is not a calibrated result.",
});
expect(teppButton).not.toHaveAccessibleName(/Reconstruction/);
expect(teppButton).not.toHaveAccessibleName(/measured/);

await userEvent.click(teppButton);
expect(
await screen.findByText("These posts are the cutoff corpus TEPP will measure once this run finishes."),
).toBeInTheDocument();
Expand All @@ -2629,7 +2654,7 @@ describe("App, authenticated", () => {

await userEvent.click(
await screen.findByRole("button", {
name: "Open analysis run: TEPP measurement · Pending · Demo Corp",
name: "Open analysis run: TEPP measurement · Pending · Demo Corp. Open this run to confirm which posts TEPP will measure. Measurement has not started yet — this is not a calibrated result.",
}),
);
await userEvent.click(screen.getByRole("button", { name: "Start TEPP measurement" }));
Expand All @@ -2651,7 +2676,7 @@ describe("App, authenticated", () => {

await userEvent.click(
await screen.findByRole("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(
Expand Down Expand Up @@ -2695,7 +2720,7 @@ describe("App, authenticated", () => {
).toBeInTheDocument();
expect(
screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Pending · Demo Corp",
name: "Open analysis run: Lineage reconstruction · Pending · Demo Corp. Open this run, then start reconstruction. Reconstruction has not started yet.",
}),
).toBeInTheDocument();
expect(
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,24 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
}
}

/**
* Accessible name for an analysis-run list button (ADR 0014).
*
* `aria-label` replaces the button contents (W3C Accessible Name and
* Description Computation 1.1). When a next action exists, the name is
* `Open analysis run: {caption}. {nextAction}` so a screen reader hears
* what to do next, not only the run title (WCAG 2.2 SC 4.1.2). Succeeded
* and cancelled rows keep the caption alone.
*/
function analysisRunAccessibleName(run: AnalysisRun): string {
const caption = analysisRunCaption(run);
const nextAction = analysisRunNextAction(run);
if (nextAction === null) {
return `Open analysis run: ${caption}`;
}
return `Open analysis run: ${caption}. ${nextAction}`;
}

/**
* Empty-corpus copy that tells the operator what to do next.
*/
Expand Down Expand Up @@ -2208,7 +2226,7 @@ function AnalysisRunsPanel({
<li key={run.analysis_run_id} className="ticket-list-item">
<button
className="post-list-item"
aria-label={`Open analysis run: ${caption}`}
aria-label={analysisRunAccessibleName(run)}
onClick={() => void handleOpen(run.analysis_run_id)}
>
<span className="ticket-title">{caption}</span>
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
"sentence_excerpts",
]

__version__ = "2.10.0"
__version__ = "2.10.1"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "2.10.0"
version = "2.10.1"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.