From 3a98300613153d13226886600ffb243d962a228e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 19:31:51 +0000 Subject: [PATCH 1/2] fix(ui): drop a stale analysis-run row after its detail 404s (v2.10.3) Re-read the authorized home list after a hidden-run 404 so the button the operator just clicked does not stay mounted under a sentence that says the run is not on the list. Announce that next action with a StatusAlert (WCAG 2.2 SC 4.1.3) and keep Request as the named reconstruction control. The message still does not name the thread or the cutoff. Co-authored-by: Seongho Bae --- AGENTS.md | 7 +++ CHANGELOG.d/2.10.3-stale-hidden-run-list.md | 7 +++ CHANGELOG.md | 10 +++ CLAUDE.md | 4 ++ docs/adr/0014-authorized-analysis-run-read.md | 9 ++- docs/adr/0018-related-nodes-team-org-walk.md | 3 + .../ANALYSIS_RUN_REGISTRY_REFERENCES.md | 1 + docs/doctoring/DESIGN_TOKEN_REFERENCES.md | 7 ++- docs/storybook-inventory.md | 5 ++ frontend/package.json | 2 +- frontend/src/App.css | 4 ++ frontend/src/App.test.tsx | 61 ++++++++++++++++++- frontend/src/App.tsx | 24 +++++++- .../src/components/StatusAlert.stories.tsx | 23 +++++++ frontend/src/components/StatusAlert.test.tsx | 23 +++++++ frontend/src/components/StatusAlert.tsx | 18 ++++++ frontend/src/styles/tokens.css | 2 + lineageweave/__init__.py | 2 +- pyproject.toml | 2 +- tests/test_ingestion_transaction_contracts.py | 30 +++++++++ uv.lock | 2 +- 21 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 CHANGELOG.d/2.10.3-stale-hidden-run-list.md create mode 100644 frontend/src/components/StatusAlert.stories.tsx create mode 100644 frontend/src/components/StatusAlert.test.tsx create mode 100644 frontend/src/components/StatusAlert.tsx diff --git a/AGENTS.md b/AGENTS.md index 5fe716b87..0e8f810fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,6 +122,13 @@ R&R chips read the catalog id stored on `post_summary_role` backfill leaves a role unbound when two same-named mentions already exist on the post. +A listed analysis-run that then 404s must stay generic: do not name +the thread or the cutoff, and do not say the run is not visible +(ADR 0014 / ADR 0018). After that 404, re-read the authorized list +so the stale row does not stay clickable. Announce the next action +with `role="alert"` without moving focus. Remaining visible runs +stay clickable. Request remains the named reconstruction control. + ## CI gates `.github/workflows/tests.yml` runs the full suite on every PR to `main`. diff --git a/CHANGELOG.d/2.10.3-stale-hidden-run-list.md b/CHANGELOG.d/2.10.3-stale-hidden-run-list.md new file mode 100644 index 000000000..b1d8cda09 --- /dev/null +++ b/CHANGELOG.d/2.10.3-stale-hidden-run-list.md @@ -0,0 +1,7 @@ +# 2.10.3 Drop a stale analysis-run row after its detail 404s + +Opening a listed analysis-run that then 404s drops that stale row +from the home list after an authorized re-read, announces the next +action with a status alert, and leaves Request as the named +reconstruction control. The message still does not name the thread +or the cutoff (ADR 0014 / ADR 0018). diff --git a/CHANGELOG.md b/CHANGELOG.md index 853632a3e..f2dff8f6a 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.3] - 2026-08-17 + +### Fixed + +- Opening a listed analysis-run that then 404s drops that stale row + from the home list after an authorized re-read. The next action is + announced as a status alert: open a remaining visible run, or + request a lineage reconstruction. The message still does not name + the thread or the cutoff (ADR 0014 / ADR 0018). + ## [2.10.2] - 2026-08-17 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 0e15bc773..c9636ef64 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,3 +72,7 @@ cited source. After that next action, the popup lands the first cited evidence. Changing the week first still focuses the report period field. Mean θ stays on the period-report panel. +A listed analysis-run that then 404s stays generic: do not name the +thread or the cutoff. After that 404, re-read the authorized list so +the stale row does not stay clickable. Announce the next action with +`role="alert"` without moving focus. diff --git a/docs/adr/0014-authorized-analysis-run-read.md b/docs/adr/0014-authorized-analysis-run-read.md index b6d61dcaa..4d59ceb95 100644 --- a/docs/adr/0014-authorized-analysis-run-read.md +++ b/docs/adr/0014-authorized-analysis-run-read.md @@ -60,7 +60,14 @@ 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 live TEPP transport, and a fuller Analysis Run Console remain later -slices. +slices. A 404 on a hidden run (including a thread-group row that +still lacks an in-cutoff visible post, ADR 0018) must stay generic: +do not name the thread or the cutoff, and do not say the run is not +visible. Tell the operator to open a visible run from the home list, +or request a lineage reconstruction for a corporation they already +walk. After that 404, re-read `GET /api/analysis-runs` so the stale +list row does not stay clickable, and announce the status with +`role="alert"` (WCAG 2.2 SC 4.1.3) without moving focus. ## References diff --git a/docs/adr/0018-related-nodes-team-org-walk.md b/docs/adr/0018-related-nodes-team-org-walk.md index ae0a1c331..c7f020eb0 100644 --- a/docs/adr/0018-related-nodes-team-org-walk.md +++ b/docs/adr/0018-related-nodes-team-org-walk.md @@ -49,6 +49,9 @@ Thread-group run list visibility requires at least one ABAC-visible organization chip. - A later public post in a thread group no longer lists a January run that could not have known that post. +- A 404 on that hidden row stays generic: do not name the thread or + the cutoff. After that 404, re-read the authorized home list so the + stale row does not stay clickable (ADR 0014). ## References diff --git a/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md b/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md index 66f382504..2b963cf6d 100644 --- a/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md +++ b/docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md @@ -11,6 +11,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. List-button names include the kind-specific next-action sentence (ADR 0014; WCAG 2.2 SC 4.1.2). | +| WCAG 2.2 SC 4.1.3 Status Messages | Announce a hidden-run 404 without confirming why the row is hidden, then drop the stale list row. | `StatusAlert` (`role="alert"`) plus a post-404 `GET /api/analysis-runs` re-read (ADR 0014 / ADR 0018). | | 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. | diff --git a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md index ac73b0921..8b23b7b83 100644 --- a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md +++ b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md @@ -8,8 +8,9 @@ the Storybook inventory. | Source | Product implication | Implemented evidence | |---|---|---| -| W3C Design Tokens Format Module 1.0 | Name color, space, type, and radius once; consume those names from repeated objects. | `frontend/src/styles/tokens.css` defines `--color-*`, `--space-*`, `--size-control-min`, `--radius-chip`, `--radius-control`, `--radius-panel`, and `--font-*`. `CitationChip`, `PopupCloseButton`, `CutoffKnownBody`, and `LineageEntityPicker` read those names through `App.css`. | +| W3C Design Tokens Format Module 1.0 | Name color, space, type, and radius once; consume those names from repeated objects. | `frontend/src/styles/tokens.css` defines `--color-*`, `--space-*`, `--size-control-min`, `--radius-chip`, `--radius-control`, `--radius-panel`, and `--font-*`. `CitationChip`, `PopupCloseButton`, `CutoffKnownBody`, `LineageEntityPicker`, and `StatusAlert` read those names through `App.css`. | | Storybook for React & Vite | Catalog repeated controls so a buyer can try the next click without reading `App.tsx`. | `frontend/src/components/*.stories.tsx` and `docs/storybook-inventory.md`. | +| WCAG 2.2 SC 4.1.3 Status Messages | Announce a fail-closed status without moving focus, so the operator hears the next action. | `StatusAlert` uses `role="alert"` and `--color-status-alert`. | ## APA 7th references @@ -18,3 +19,7 @@ Design Tokens Community Group. (2025). *Design Tokens Format Module 1.0* Storybook. (2026). *Storybook for React & Vite*. https://storybook.js.org/docs/get-started/frameworks/react-vite + +World Wide Web Consortium. (2023). *Web content accessibility +guidelines (WCAG) 2.2* (W3C Recommendation). +https://www.w3.org/TR/WCAG22/ diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index a535877bc..808da12a3 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -9,6 +9,7 @@ buyer-facing control you can click before changing product CSS. | `AnalysisRun/CutoffKnownBody` | Read the cutoff-known sentence, then compare it with the live body below. | `--color-accent-border`, `--space-panel-block`, `--radius-panel`, `CutoffKnownBody` | | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | | `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` | +| `Chrome/StatusAlert` | Hear the next action after a hidden-run 404, then open a visible run or request a reconstruction. | `--color-status-alert`, `StatusAlert` | Repeated web objects must use `frontend/src/styles/tokens.css` and a module under `frontend/src/components/`. Do not add a second Node package manager; @@ -21,3 +22,7 @@ Design Tokens Community Group. (2025). *Design Tokens Format Module 1.0* Storybook. (2026). *Storybook for React & Vite*. https://storybook.js.org/docs/get-started/frameworks/react-vite + +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 9175f4cd6..709b32e5c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.10.2", + "version": "2.10.3", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.css b/frontend/src/App.css index bdda84290..0ca197583 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -27,6 +27,10 @@ color: #b91c1c; } +.status-alert { + color: var(--color-status-alert); +} + .post-list { list-style: none; padding: 0; diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index e5180b90d..b759fccb6 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -74,6 +74,7 @@ describe("App, authenticated", () => { succeededReportRun?: boolean; succeededTeppRun?: boolean; pendingTeppRun?: boolean; + hiddenAnalysisRun?: boolean; pluralAffiliations?: boolean; deferMe?: boolean; meFailed?: boolean; @@ -101,6 +102,7 @@ describe("App, authenticated", () => { let nextEventId = 1; let createdPendingLineage: Record | null = null; let createdPendingTepp: Record | null = null; + let analysisRunListCalls = 0; let releaseMe = () => {}; const meReady = options?.deferMe @@ -371,6 +373,14 @@ describe("App, authenticated", () => { ); } if (url.endsWith("/api/analysis-runs/run-demo-lineage")) { + if (options?.hiddenAnalysisRun) { + return Promise.resolve( + new Response(JSON.stringify({ detail: "Not found" }), { + status: 404, + headers: { "Content-Type": "application/json" }, + }), + ); + } return Promise.resolve( jsonResponse({ analysis_run_id: "run-demo-lineage", @@ -623,12 +633,18 @@ describe("App, authenticated", () => { return Promise.resolve(new Response(JSON.stringify(created), { status: 201 })); } if (url.endsWith("/api/analysis-runs")) { + analysisRunListCalls += 1; + const includeStaleLineageRow = !( + options?.hiddenAnalysisRun && analysisRunListCalls > 1 + ); return Promise.resolve( jsonResponse({ analysis_runs: [ ...(createdPendingLineage ? [createdPendingLineage] : []), ...(createdPendingTepp ? [createdPendingTepp] : []), - { + ...(includeStaleLineageRow + ? [ + { analysis_run_id: "run-demo-lineage", run_kind_code: "analysis_run_lineage", run_kind_label: "Lineage reconstruction", @@ -657,7 +673,9 @@ describe("App, authenticated", () => { code_revision_sha: "abcdef0123456789deadbeefcafebabe", configuration_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - }, + }, + ] + : []), { analysis_run_id: "run-demo-tepp", run_kind_code: "analysis_run_tepp", @@ -2279,6 +2297,45 @@ describe("App, authenticated", () => { expect(screen.queryByRole("heading", { name: "Body this run knew" })).not.toBeInTheDocument(); }); + it("drops a stale listed run after its detail 404s and names the next action", async () => { + stubBackend({ hiddenAnalysisRun: true }); + render(); + + await screen.findByRole("list", { name: "Analysis runs" }); + await userEvent.click( + screen.getByRole("button", { + name: "Open analysis run: Lineage reconstruction · Succeeded · Demo Corp", + }), + ); + + expect(await screen.findByRole("alert")).toHaveTextContent( + "This run is not on your list. Open a visible run from the home list, or request a lineage reconstruction for a corporation you already walk.", + ); + await waitFor(() => { + expect( + screen.queryByRole("button", { + name: "Open analysis run: Lineage reconstruction · Succeeded · Demo Corp", + }), + ).not.toBeInTheDocument(); + }); + 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(); + expect( + screen.getByRole("button", { + name: "Open analysis run: Period report · Succeeded · Demo Corp", + }), + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "Request a lineage reconstruction" }), + ).toBeInTheDocument(); + expect(screen.queryByText(/not visible/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/thread-group/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/knowledge_cutoff/i)).not.toBeInTheDocument(); + }); + it("finds a failed TEPP list button by the next-action accessible name", async () => { stubBackend(); render(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 684902521..7ddd739f5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -66,6 +66,7 @@ import { CitationChip } from "./components/CitationChip"; import { CutoffKnownBody } from "./components/CutoffKnownBody"; import { LineageEntityPicker } from "./components/LineageEntityPicker"; import { PopupCloseButton } from "./components/PopupCloseButton"; +import { StatusAlert } from "./components/StatusAlert"; import { LineageDag } from "./LineageDag"; import { PostBody } from "./PostBody"; import { subgraphForPost } from "./lineageLayout"; @@ -1819,6 +1820,17 @@ function analysisRunAccessibleName(run: AnalysisRun): string { return `Open analysis run: ${caption}. ${nextAction}`; } +/** + * Next action when detail 404s. Stay generic: do not name the thread or the cutoff. + * Naming either would confirm a hidden row (ADR 0018). + */ +function analysisRunHiddenNextAction(): string { + return ( + "This run is not on your list. Open a visible run from the home list, " + + "or request a lineage reconstruction for a corporation you already walk." + ); +} + /** * Empty-corpus copy that tells the operator what to do next. */ @@ -2172,18 +2184,24 @@ function AnalysisRunsPanel({ } catch (err) { setSelected(null); if (err instanceof BackendError && err.status === 404) { - setError("This analysis run is not visible."); + setError(analysisRunHiddenNextAction()); + try { + setRuns((await fetchAnalysisRuns(accessToken)).analysis_runs); + } catch { + // Keep the last authorized list if the re-read fails. + } return; } setError(String(err)); } } - if (error && runs === null) return

{error}

; + if (error && runs === null) return {error}; if (runs === null) return

Loading analysis runs...

; const corpusHint = selected ? analysisRunCorpusHint(selected) : null; const selectedNextAction = selected ? analysisRunNextAction(selected) : null; + const statusMessage = error ?? entitiesLoadError; return (
@@ -2208,7 +2226,7 @@ function AnalysisRunsPanel({ {requestLabel} - {(error || entitiesLoadError) &&

{error ?? entitiesLoadError}

} + {statusMessage ? {statusMessage} : null} {runs.length === 0 ? (

No analysis runs visible to this account yet. Request a lineage diff --git a/frontend/src/components/StatusAlert.stories.tsx b/frontend/src/components/StatusAlert.stories.tsx new file mode 100644 index 000000000..947aeedff --- /dev/null +++ b/frontend/src/components/StatusAlert.stories.tsx @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { StatusAlert } from "./StatusAlert"; + +const meta = { + title: "Chrome/StatusAlert", + component: StatusAlert, + args: { + children: + "This run is not on your list. Open a visible run from the home list, or request a lineage reconstruction for a corporation you already walk.", + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const HiddenAnalysisRun: Story = {}; + +export const ListLoadFailure: Story = { + args: { + children: "BackendError: 503 Service Unavailable", + }, +}; diff --git a/frontend/src/components/StatusAlert.test.tsx b/frontend/src/components/StatusAlert.test.tsx new file mode 100644 index 000000000..ebca0552c --- /dev/null +++ b/frontend/src/components/StatusAlert.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { StatusAlert } from "./StatusAlert"; + +describe("StatusAlert", () => { + it("announces the next action as an alert without moving focus", () => { + render( + + This run is not on your list. Open a visible run from the home list, or request a lineage reconstruction for a corporation you already walk. + , + ); + + const alert = screen.getByRole("alert"); + expect(alert).toHaveTextContent( + "This run is not on your list. Open a visible run from the home list, or request a lineage reconstruction for a corporation you already walk.", + ); + expect(alert.tagName).toBe("P"); + expect(document.activeElement).not.toBe(alert); + expect(screen.queryByText(/not visible/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/thread-group/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/knowledge_cutoff/i)).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/StatusAlert.tsx b/frontend/src/components/StatusAlert.tsx new file mode 100644 index 000000000..f8710b881 --- /dev/null +++ b/frontend/src/components/StatusAlert.tsx @@ -0,0 +1,18 @@ +export type StatusAlertProps = { + children: string; +}; + +/** + * Announces a fail-closed status so the operator hears the next action. + * + * Uses `role="alert"` (WCAG 2.2 SC 4.1.3) without moving focus. + * Next action: read the sentence, then use the control it names + * (open a visible run, or request a lineage reconstruction). + */ +export function StatusAlert({ children }: StatusAlertProps) { + return ( +

+ {children} +

+ ); +} diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css index fc92405f7..7722cb16a 100644 --- a/frontend/src/styles/tokens.css +++ b/frontend/src/styles/tokens.css @@ -8,6 +8,7 @@ --color-accent-background: rgba(170, 59, 255, 0.1); --color-accent-border: rgba(170, 59, 255, 0.5); --color-chip-border: #3335; + --color-status-alert: #b91c1c; --space-chip-inline: 0.6rem; --space-chip-block: 0.1rem; --space-chip-gap: 0.3rem; @@ -34,5 +35,6 @@ --color-accent-background: rgba(192, 132, 252, 0.15); --color-accent-border: rgba(192, 132, 252, 0.5); --color-chip-border: #9ca3af; + --color-status-alert: #f87171; } } diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index 8fda891a2..aa53eb9eb 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -55,4 +55,4 @@ "sentence_excerpts", ] -__version__ = "2.10.2" +__version__ = "2.10.3" diff --git a/pyproject.toml b/pyproject.toml index b04c3ca26..5cb375082 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.10.2" +version = "2.10.3" 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" } diff --git a/tests/test_ingestion_transaction_contracts.py b/tests/test_ingestion_transaction_contracts.py index 5a4e284b3..55a94ab4d 100644 --- a/tests/test_ingestion_transaction_contracts.py +++ b/tests/test_ingestion_transaction_contracts.py @@ -642,6 +642,36 @@ async def persist_edges(conn, post_id) -> list[Any]: assert mention_inserts == [] +def test_hidden_run_copy_stays_generic_and_drops_the_stale_row() -> None: + """ADR 0014/0018: a 404 must not confirm why a row is hidden.""" + + app = ( + Path(__file__).resolve().parents[1] / "frontend" / "src" / "App.tsx" + ).read_text(encoding="utf-8") + alert = ( + Path(__file__).resolve().parents[1] + / "frontend" + / "src" + / "components" + / "StatusAlert.tsx" + ).read_text(encoding="utf-8") + agents = ( + Path(__file__).resolve().parents[1] / "AGENTS.md" + ).read_text(encoding="utf-8") + assert "This analysis run is not visible." not in app + assert "This run is not on your list. Open a visible run from the home list," in app + assert ( + "or request a lineage reconstruction for a corporation you already walk." + in app + ) + assert "do not name the thread or the cutoff" in app + assert "setRuns((await fetchAnalysisRuns(accessToken)).analysis_runs)" in app + assert 'role="alert"' in alert + assert "{error}" in app + assert "re-read the authorized list" in agents + assert "do not name the thread or the cutoff" in agents + + def test_role_catalog_identity_is_stored_on_the_role_row() -> None: """ADR 0019: fetch must not reconstruct organization identity by name.""" root = Path(__file__).resolve().parents[1] diff --git a/uv.lock b/uv.lock index 127875bd5..025598df7 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "2.10.2" +version = "2.10.3" source = { virtual = "." } dependencies = [ { name = "certifi" }, From debf7756970d3068e3520e1fb53c6d1cfb1cadef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 19:35:39 +0000 Subject: [PATCH 2/2] fix(ui): keep the hidden-run leak lock on one AGENTS.md line The source lock looks for the contiguous phrase "do not name the thread or the cutoff" in AGENTS.md. Wrapping that phrase across two comment lines failed Full test suite on #244. Co-authored-by: Seongho Bae --- AGENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0e8f810fe..c97691319 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -122,12 +122,12 @@ R&R chips read the catalog id stored on `post_summary_role` backfill leaves a role unbound when two same-named mentions already exist on the post. -A listed analysis-run that then 404s must stay generic: do not name -the thread or the cutoff, and do not say the run is not visible -(ADR 0014 / ADR 0018). After that 404, re-read the authorized list -so the stale row does not stay clickable. Announce the next action -with `role="alert"` without moving focus. Remaining visible runs -stay clickable. Request remains the named reconstruction control. +A listed analysis-run that then 404s must stay generic: do not name the thread or the cutoff, +and do not say the run is not visible (ADR 0014 / ADR 0018). After +that 404, re-read the authorized list so the stale row does not stay +clickable. Announce the next action with `role="alert"` without +moving focus. Remaining visible runs stay clickable. Request remains +the named reconstruction control. ## CI gates