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
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read
Expand Down
5 changes: 3 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,10 @@ labeled detail (cutoff, requested date, counts, status history)
without exposing a DSN or raw record. Status history is detail-only
and uses lookup labels plus occurrence times; a failure event keeps
its machine `failure_code` rather than an invented caption. Failed
list rows add a next-action line (open the run, then connect the
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. The
calibrated negative result. A failed lineage row tells the operator
to retry reconstruction, not to connect TEPP. 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
6 changes: 3 additions & 3 deletions CHANGELOG.d/0.84.0-tepp-analysis-run.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0.84.0 TEPP analysis-run seed

Seed writes `analysis_run_tepp` via `tepp_client` on the shared Demo
Corp snapshot. The home list shows Failed and the next action; detail
history keeps `tepp_not_available`. Missing transport is not a fake
measurement.
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.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ All notable changes to this project are documented here. Format follows
keeps `tepp_not_available` -- never a fabricated theta. TEPP stays
a wire client, not a local psychometric engine. `make seed` skips
snapshot-count inserts once counts exist so a re-run does not hit
the freeze trigger.
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.

## [0.83.0] - 2026-08-16

Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ transport or an unused accepted envelope is Failed
(`tepp_not_available` / `tepp_result_not_persisted`). Do not invent a
theta or a local psychometric substitute. The home list caption stays
`kind · status · entity`; the machine failure code is detail-only
(ADR 0014). Open the Failed row, then connect a live TEPP transport.
(ADR 0014). Open a Failed TEPP row, then connect a live TEPP
transport. A failed lineage row retries reconstruction -- it does not
mention TEPP.
9 changes: 5 additions & 4 deletions docs/adr/0014-authorized-analysis-run-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ run on the same snapshot so the existing React home page can show both
kinds without a second application. The TEPP run is Failed /
`tepp_not_available` when the default transport is missing -- the list
keeps that machine code off the caption (this decision) and instead
tells the operator to open the run, then connect the measurement
service. The detail now shows the legal lifecycle the registry already
stored. Write/rebuild APIs, a live TEPP transport, and a fuller
Analysis Run Console remain later slices.
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
lifecycle the registry already stored. Write/rebuild APIs, a live TEPP
transport, and a fuller Analysis Run Console remain later slices.

## References

Expand Down
63 changes: 54 additions & 9 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ describe("App, authenticated", () => {
chatUnavailable?: boolean;
searchUnavailable?: boolean;
verificationEvidenceUrl?: string | null;
failedLineageRun?: boolean;
succeededTeppRun?: boolean;
}) {
const statusLabel: Record<string, string> = {
open: "Open",
Expand Down Expand Up @@ -178,8 +180,10 @@ describe("App, authenticated", () => {
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_failed",
status_label: "Failed",
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
Expand All @@ -205,10 +209,14 @@ describe("App, authenticated", () => {
},
{
status_ordinal: 3,
status_code: "analysis_status_failed",
status_label: "Failed",
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
occurred_at: "2026-01-12T12:37:00Z",
failure_code: "tepp_not_available",
...(options?.succeededTeppRun
? {}
: { failure_code: "tepp_not_available" }),
},
],
}),
Expand Down Expand Up @@ -269,8 +277,10 @@ describe("App, authenticated", () => {
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_succeeded",
status_label: "Succeeded",
status_code: options?.failedLineageRun
? "analysis_status_failed"
: "analysis_status_succeeded",
status_label: options?.failedLineageRun ? "Failed" : "Succeeded",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:30:00Z",
source_counts: [
Expand All @@ -288,8 +298,10 @@ describe("App, authenticated", () => {
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_failed",
status_label: "Failed",
status_code: options?.succeededTeppRun
? "analysis_status_succeeded"
: "analysis_status_failed",
status_label: options?.succeededTeppRun ? "Succeeded" : "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
Expand Down Expand Up @@ -1479,6 +1491,39 @@ describe("App, authenticated", () => {
expect(teppHistory).not.toHaveTextContent("Succeeded");
});

it("does not tell a failed lineage run to connect the measurement service", async () => {
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(
"Open this run to see why it failed, then retry reconstruction from a current snapshot.",
);
expect(list).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(lineageButton).not.toHaveTextContent("measurement service");
Comment thread
cursor[bot] marked this conversation as resolved.
});

it("does not tell a succeeded TEPP run to replace Failed", async () => {
stubBackend({ succeededTeppRun: true });
render(<App />);

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

it("shows the calibrated period-report mean theta on the home page", async () => {
stubBackend();
render(<App />);
Expand Down
29 changes: 20 additions & 9 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1356,14 +1356,22 @@ function analysisRunCaption(run: AnalysisRun): string {
/**
* Next action for a failed run on the home list.
*
* The machine `failure_code` stays on detail history (ADR 0014). The
* list tells the operator to open the run, then reconnect the service.
* The machine `failure_code` stays on detail history (ADR 0014). Copy
* is kind-specific so a failed lineage reconstruction is not mistaken
* for a missing TEPP transport.
*/
function analysisRunNextAction(run: AnalysisRun): string | null {
if (run.status_code === "analysis_status_failed") {
return "Open this run to see why it failed, then connect the measurement service and re-run.";
if (run.status_code !== "analysis_status_failed") {
return null;
}
switch (run.run_kind_code) {
case "analysis_run_tepp":
return "Open this run to see why it failed, then connect the measurement service and re-run.";
case "analysis_run_lineage":
return "Open this run to see why it failed, then retry reconstruction from a current snapshot.";
default:
return "Open this run to see why it failed, then retry after the blocking service is connected.";
Comment thread
cursor[bot] marked this conversation as resolved.
}
return null;
}

/**
Expand All @@ -1389,10 +1397,13 @@ function analysisRunEmptyPostsHint(run: AnalysisRun): string {
*/
function analysisRunCorpusHint(run: AnalysisRun): string | null {
if (run.run_kind_code !== "analysis_run_tepp") return null;
return (
"These posts are the cutoff corpus TEPP would measure. Connect a TEPP " +
"transport, then re-run, to replace Failed with a calibrated result."
);
if (run.status_code === "analysis_status_failed") {
return (
"These posts are the cutoff corpus TEPP would measure. Connect a TEPP " +
"transport, then re-run, to replace Failed with a calibrated result."
);
}
return "These posts are the cutoff corpus this TEPP run measured.";
}

function AnalysisRunsPanel({
Expand Down