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 @@ -489,7 +489,9 @@ its machine `failure_code` rather than an invented caption. Failed
TEPP list rows add a next-action line (open the run, then connect the
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. The
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. The
payload is lookup labels plus non-negative aggregate counts -- never
source SQL, a DSN, a raw record, or a provider body. After `make seed`,
Demo Analyst and Demo Admin see "Lineage reconstruction · Succeeded ·
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.d/0.84.0-tepp-analysis-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ Seed writes `analysis_run_tepp` via `tepp_client` on the shared Demo
Corp snapshot. The home list shows Failed and a kind-specific next
action; detail history keeps `tepp_not_available`. Missing transport
is not a fake measurement. A failed lineage row does not mention TEPP.
A failed period-report row rebuilds the report. A pending TEPP row
does not claim a calibrated measurement.
2 changes: 2 additions & 0 deletions CHANGELOG.d/0.86.0-related-nodes-team-org-walk.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Related-node walks include team and organization mention edges. Click an
R&R team to open sibling posts. Thread-group run lists honor knowledge_cutoff.
Failed period-report rows rebuild the report; a pending TEPP corpus
does not claim a calibrated measurement.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ All notable changes to this project are documented here. Format follows
- Thread-group analysis-run *lists* now require an in-cutoff visible
post. A later public post in that thread group no longer surfaces a
January run the account was not allowed to know.
- Failed period-report rows tell the operator to rebuild the report.
Next-action copy is pinned to the registered run kinds. A pending
TEPP corpus does not claim a calibrated measurement.

## [0.85.0] - 2026-08-16

Expand Down Expand Up @@ -62,7 +65,10 @@ All notable changes to this project are documented here. Format follows
snapshot-count inserts once counts exist so a re-run does not hit
the freeze trigger. A failed lineage row tells the operator to retry
reconstruction; only a failed TEPP row mentions the measurement
service. Stacked PRs now run the same GitHub Checks as PRs to main.
service. A failed period-report row tells the operator to rebuild
the report from a current snapshot. A pending TEPP row does not
claim a calibrated measurement. Stacked PRs now run the same
GitHub Checks as PRs to main.

## [0.83.0] - 2026-08-16

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ theta or a local psychometric substitute. The home list caption stays
`kind · status · entity`; the machine failure code is detail-only
(ADR 0014). Open a Failed TEPP row, then connect a live TEPP
transport. A failed lineage row retries reconstruction -- it does not
mention TEPP.
mention TEPP. A failed period-report row rebuilds the report. A
pending TEPP row does not claim a calibrated measurement.
Digest prefixes stay audible; hover a prefix to read the full digest.
Opening a cutoff title shows the live post -- compare it with the
cutoff before treating the body as reconstructed evidence (ADR 0016).
Expand Down
5 changes: 4 additions & 1 deletion docs/adr/0014-authorized-analysis-run-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ kinds without a second application. The TEPP run is Failed /
keeps that machine code off the caption (this decision) and instead
tells the operator to open the TEPP run, then connect the measurement
service. A failed lineage row tells the operator to retry
reconstruction, not to connect TEPP. The detail now shows the legal
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. The detail now shows the legal
lifecycle the registry already stored. `POST /api/analysis-runs` now
records a Pending run on an authorized cutoff capture (ADR 0017).
Reconstruction, a live TEPP transport, and a fuller Analysis Run
Expand Down
191 changes: 159 additions & 32 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
searchUnavailable?: boolean;
verificationEvidenceUrl?: string | null;
failedLineageRun?: boolean;
failedReportRun?: boolean;
succeededTeppRun?: boolean;
pendingTeppRun?: boolean;
}) {
const statusLabel: Record<string, string> = {
open: "Open",
Expand Down Expand Up @@ -171,54 +173,110 @@
jsonResponse({ post_id: "post-1", has_commitment: true, ticket }),
);
}
if (url.endsWith("/api/analysis-runs/run-demo-tepp")) {
if (url.endsWith("/api/analysis-runs/run-demo-report")) {
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-tepp",
run_kind_code: "analysis_run_tepp",
run_kind_label: "TEPP measurement",
analysis_run_id: "run-demo-report",
run_kind_code: "analysis_run_report",
run_kind_label: "Period report",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
status_code: "analysis_status_failed",
status_label: "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
requested_at: "2026-01-12T12:38:00Z",
source_counts: [
{
count_type_code: "analysis_count_document",
count_type_label: "Documents",
count_value: 3,
},
],
visible_posts: [{ post_id: "post-1", post_title: "Public post" }],
visible_posts: [],
status_history: [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:35:00Z",
occurred_at: "2026-01-12T12:39:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_running",
status_label: "Running",
occurred_at: "2026-01-12T12:36:00Z",
status_code: "analysis_status_failed",
status_label: "Failed",
occurred_at: "2026-01-12T12:40:00Z",
failure_code: "period_report_rebuild_failed",
},
],
}),
);
}
if (url.endsWith("/api/analysis-runs/run-demo-tepp")) {
const teppStatus = options?.succeededTeppRun
? "analysis_status_succeeded"
: options?.pendingTeppRun
? "analysis_status_pending"
: "analysis_status_failed";
const teppLabel = options?.succeededTeppRun
? "Succeeded"
: options?.pendingTeppRun
? "Pending"
: "Failed";
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-tepp",
run_kind_code: "analysis_run_tepp",
run_kind_label: "TEPP measurement",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: teppStatus,
status_label: teppLabel,
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
{
status_ordinal: 3,
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
occurred_at: "2026-01-12T12:37:00Z",
...(options?.succeededTeppRun
? {}
: { failure_code: "tepp_not_available" }),
count_type_code: "analysis_count_document",
count_type_label: "Documents",
count_value: 3,
},
],
visible_posts: [{ post_id: "post-1", post_title: "Public post" }],
status_history: options?.pendingTeppRun
? [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:35:00Z",
},
]
: [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:35:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_running",
status_label: "Running",
occurred_at: "2026-01-12T12:36:00Z",
},
{
status_ordinal: 3,
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
occurred_at: "2026-01-12T12:37:00Z",
...(options?.succeededTeppRun
? {}
: { failure_code: "tepp_not_available" }),
},
],
}),
);
}
Expand Down Expand Up @@ -331,8 +389,14 @@
scope_entity_name: "Demo Corp",
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
: options?.pendingTeppRun
? "analysis_status_pending"
: "analysis_status_failed",
status_label: options?.succeededTeppRun
? "Succeeded"
: options?.pendingTeppRun
? "Pending"
: "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
Expand All @@ -343,6 +407,29 @@
},
],
},
...(options?.failedReportRun
? [
{
analysis_run_id: "run-demo-report",
run_kind_code: "analysis_run_report" as const,
run_kind_label: "Period report",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_failed" as const,
status_label: "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:38:00Z",
source_counts: [
{
count_type_code: "analysis_count_document",
count_type_label: "Documents",
count_value: 3,
},
],
},
]
: []),
],
}),
);
Expand Down Expand Up @@ -1615,18 +1702,58 @@
stubBackend({ failedLineageRun: true });
render(<App />);

const list = await screen.findByRole("list", { name: "Analysis runs" });
expect(list).toHaveTextContent("Lineage reconstruction · Failed · Demo Corp");
expect(list).toHaveTextContent(
await screen.findByRole("list", { name: "Analysis runs" });
const lineageButton = screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Failed · Demo Corp",
});
const teppButton = screen.getByRole("button", {
name: "Open analysis run: TEPP measurement · Failed · Demo Corp",
});
expect(lineageButton).toHaveTextContent(
"Open this run to see why it failed, then retry reconstruction from a current snapshot.",
);
expect(list).toHaveTextContent(
expect(lineageButton).not.toHaveTextContent("measurement service");
expect(teppButton).toHaveTextContent(
"Open this run to see why it failed, then connect the measurement service and re-run.",
);
const lineageButton = screen.getByRole("button", {
name: "Open analysis run: Lineage reconstruction · Failed · Demo Corp",
expect(teppButton).not.toHaveTextContent("reconstruction");
});

it("does not tell a failed period report to connect the measurement service", async () => {
stubBackend({ failedReportRun: true });
render(<App />);

const reportButton = await screen.findByRole("button", {
name: "Open analysis run: Period report · Failed · Demo Corp",
});
expect(lineageButton).not.toHaveTextContent("measurement service");
expect(reportButton).toHaveTextContent(
"Open this run to see why it failed, then rebuild the period report from a current snapshot.",
);
expect(reportButton).not.toHaveTextContent("measurement service");
expect(reportButton).not.toHaveTextContent("reconstruction");

await userEvent.click(reportButton);
expect(
await screen.findByText(
"No posts were available at this cutoff for the period report. Open a later run, or ask an administrator to capture a newer snapshot.",
),
).toBeInTheDocument();
});

it("does not tell a pending TEPP run that it already measured", async () => {
stubBackend({ pendingTeppRun: true });
render(<App />);

await userEvent.click(
await screen.findByRole("button", {
name: "Open analysis run: TEPP measurement · Pending · Demo Corp",
}),
);
expect(
await screen.findByText("These posts are the cutoff corpus TEPP will measure once this run finishes."),
).toBeInTheDocument();
expect(screen.queryByText(/replace Failed/i)).not.toBeInTheDocument();
expect(screen.queryByText(/this TEPP run measured/i)).not.toBeInTheDocument();
});

it("does not tell a succeeded TEPP run to replace Failed", async () => {
Expand Down Expand Up @@ -1654,7 +1781,7 @@
expect(
await screen.findByRole("heading", { name: "Lineage reconstruction · Pending · Demo Corp" }),
).toBeInTheDocument();
expect(screen.getByText(/has not started yet/)).toBeInTheDocument();

Check failure on line 1784 in frontend/src/App.test.tsx

View workflow job for this annotation

GitHub Actions / Frontend lint, test, build

src/App.test.tsx > App, authenticated > records a pending lineage run and opens the authorized detail

TestingLibraryElementError: Unable to find an element with the text: /has not started yet/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <main> <header class="app-header" > <h1> LineageWeave </h1> <div> <span> demo.analyst </span> <button> Log out </button> </div> </header> <section class="popup-section lineage-home" > <h2> Calendar </h2> <ul class="ticket-list" > <li class="ticket-list-item" > <button aria-label="Open commitment for: Public post" class="post-list-item" > <span class="ticket-title" > Send Northridge Grid the revised quote </span> <span class="post-badge" > Public post </span> <span class="post-badge" > Open </span> <span class="post-badge" > due 2026-01-12 </span> </button> </li> <li class="ticket-list-item" > <button aria-label="Open commitment for: Specification revision requested" class="post-list-item" > <span class="ticket-title" > Send Westfield Power the revised specification </span> <span class="post-badge" > Specification revision requested </span> <span class="post-badge" > Open </span> <span class="post-badge" > due 2026-01-14 </span> </button> </li> </ul> </section> <section class="popup-section lineage-home" > <div class="lineage-home-header" > <h2> Analysis runs </h2> <button aria-label="Request a lineage reconstruction" class="keyman-select" > Request a lineage reconstruction </button> </div> <ul aria-label="Analysis runs" class="ticket-list" > <li class="ticket-list-item" > <button aria-label="Open analysis run: Lineage reconstruction · Succeeded · Demo Corp" class="post-list-item" > <span class="ticket-title" > Lineage reconstruction · Succeeded · Demo Corp </span> <span class="post-badge" > 3 documents </span> </button> </li> <li class="ticket-list-item" > <button aria-label="Open analysis run: TEPP measurement · Failed · Demo Corp" class="post-list-item" > <span class="ticket-title" > TEPP measurement · Failed · Demo Corp </span> <span class="post-badge" > 3 documents </span> <span class="post-meta" > Open this run to see why it failed, then connect the measurement service and re-
const postCall = fetchMock.mock.calls.find(
(call) => String(call[0]).endsWith("/api/analysis-runs") && call[1]?.method === "POST",
);
Expand Down
Loading
Loading