diff --git a/backend/app/analysis_run_ingestion.py b/backend/app/analysis_run_ingestion.py
index 7335f6fb9..457675fb8 100644
--- a/backend/app/analysis_run_ingestion.py
+++ b/backend/app/analysis_run_ingestion.py
@@ -640,14 +640,16 @@ def _require_lineage_create_kind(run_kind_code: str) -> None:
if run_kind_code == _TEPP_RUN_KIND:
raise AnalysisRunCreateError(
422,
- "Connect a TEPP transport from a Failed TEPP row; this endpoint "
- "does not invent a measurement.",
+ "Open the failed measurement run. Ask an administrator to restore "
+ "measurement access before retrying; this request cannot produce "
+ "a measurement.",
)
if run_kind_code == _TOPIC_LINEAGE_RUN_KIND:
raise AnalysisRunCreateError(
422,
- "Connect a TEPP transport from a Failed topic-lineage row; this "
- "endpoint does not invent a topic model.",
+ "Open the failed topic analysis. Ask an administrator to restore "
+ "topic measurement access before retrying; this request cannot "
+ "produce a topic model.",
)
if run_kind_code == _REPORT_RUN_KIND:
raise AnalysisRunCreateError(
diff --git a/backend/app/analysis_run_start.py b/backend/app/analysis_run_start.py
index c08810078..73f73f45f 100644
--- a/backend/app/analysis_run_start.py
+++ b/backend/app/analysis_run_start.py
@@ -141,13 +141,13 @@ def start_kind_rejection(run_kind_code: str) -> AnalysisRunStartError | None:
if run_kind_code == _REPORT_KIND:
return AnalysisRunStartError(
422,
- "Rebuild the period report from the reports panel. "
- "This start path does not invent a measurement.",
+ "Open Reports and rebuild the period report. "
+ "This action cannot produce a measurement.",
)
return AnalysisRunStartError(
422,
- "Start reconstructs a Pending lineage run or submits TEPP. "
- "This start path does not invent a measurement.",
+ "Open a pending lineage or measurement run and select its available action. "
+ "An unsupported run cannot produce a measurement.",
)
diff --git a/backend/app/global_ask_queue.py b/backend/app/global_ask_queue.py
index 9bffd8502..4314ee3af 100644
--- a/backend/app/global_ask_queue.py
+++ b/backend/app/global_ask_queue.py
@@ -168,7 +168,7 @@ def _verification_next_action(status_code: str) -> str:
return {
VERIFICATION_SKIPPED: "Enable public verification to check eligible public claims.",
- VERIFICATION_UNAVAILABLE: "Configure public search and contextual-orchestrator, then retry.",
+ VERIFICATION_UNAVAILABLE: "Review the internal citations now, then retry public verification when it is available.",
VERIFICATION_NO_PUBLIC_CLAIMS: "Inspect the internal cited posts; no public claim was eligible.",
VERIFICATION_COMPLETED: "Inspect public evidence separately before any governed graph review.",
CLAIM_NOT_ENOUGH_INFORMATION: "Collect stronger authoritative evidence before accepting the claim.",
diff --git a/docs/adr/0024-rankweave-fusion-fail-closed.md b/docs/adr/0024-rankweave-fusion-fail-closed.md
index c03f73b45..4b9b839a4 100644
--- a/docs/adr/0024-rankweave-fusion-fail-closed.md
+++ b/docs/adr/0024-rankweave-fusion-fail-closed.md
@@ -32,9 +32,10 @@ tables, and does not bind the demo IdP to production Keyverse.
Samuel et al. (2025) unequal-channel weights (`temporal` 0.25,
`lexical` 0.75). The buyer sees 1-based `fused_rank` and the post
title — not a TEPP theta.
-4. After login, Rankings sits above Calendar. Unavailable copy is
- **Rankings · RankWeave not available**. An accepted hit lists the
- title; click opens that `source_post`.
+4. After login, Rankings sits above Calendar. Customer copy describes the
+ available action and result state without naming the internal ranking
+ dependency. An accepted hit lists the title; click opens that
+ `source_post`.
5. Accepted hits also disclose owned-channel evidence (ADR 0167):
1-based `channel_rank` and Cormack contribution
`weight / (η + rank)` for each channel the post actually appears
diff --git a/docs/adr/0167-rankweave-ranking-channel-evidence.md b/docs/adr/0167-rankweave-ranking-channel-evidence.md
index d7fe2e30b..7b2591aaf 100644
--- a/docs/adr/0167-rankweave-ranking-channel-evidence.md
+++ b/docs/adr/0167-rankweave-ranking-channel-evidence.md
@@ -38,9 +38,10 @@ reasons.
3. Labels: `temporal` = **Newest first**, `lexical` = **Title
overlap**. The payload never includes a fused score or a theta.
4. Rankings lists the evidence under each accepted hit as an
- accessible sibling list, not hover-only. Copy states that
- RankWeave fused newest-first and title-overlap ranks, and that
- this is not a calibrated score. Click still opens that post.
+ accessible sibling list, not hover-only. Customer copy tells the reader
+ to open a result to review its channel evidence and states that the result
+ is not a calibrated measurement; it does not name the internal ranking
+ dependency. Click still opens that post.
5. Unavailable RankWeave stays empty (`rankweave_not_available`).
Hidden posts remain omitted from every channel.
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 901b17b1a..e276b6c72 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -978,6 +978,15 @@
justify-content: flex-start;
}
+.ranking-hit > button {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--space-control-gap);
+ width: 100%;
+ text-align: left;
+}
+
.ranking-channel-evidence-copy {
margin: 0 0 0.75rem;
}
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 2dee4513d..fb4398235 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -106,6 +106,8 @@ describe("App, authenticated", () => {
succeededReportRun?: boolean;
succeededTeppRun?: boolean;
pendingTeppRun?: boolean;
+ topicLineageRun?: boolean;
+ emptyAnalysisRuns?: boolean;
pluralAffiliations?: boolean;
deferMe?: boolean;
deferPostOne?: boolean;
@@ -116,6 +118,7 @@ describe("App, authenticated", () => {
customerEntityHierarchy?: boolean;
staleSummary?: boolean;
contentAfterSummary?: boolean;
+ emptyPosts?: boolean;
organizationAliases?: boolean;
askLineageGraph?: boolean;
askImageCitation?: boolean;
@@ -376,8 +379,10 @@ describe("App, authenticated", () => {
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-tepp",
- run_kind_code: "analysis_run_tepp",
- run_kind_label: "TEPP measurement",
+ run_kind_code: options?.topicLineageRun
+ ? "analysis_run_topic_lineage"
+ : "analysis_run_tepp",
+ run_kind_label: options?.topicLineageRun ? "Topic lineage" : "TEPP measurement",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
@@ -649,7 +654,7 @@ describe("App, authenticated", () => {
JSON.stringify({
detail:
payload.run_kind_code === "analysis_run_tepp"
- ? "Connect a TEPP transport from a Failed TEPP row; this endpoint does not invent a measurement."
+ ? "Open the failed measurement run. Ask an administrator to restore measurement access before retrying; this request cannot produce a measurement."
: "Rebuild the period report from the Reports panel.",
}),
{ status: 422, headers: { "Content-Type": "application/json" } },
@@ -685,7 +690,7 @@ describe("App, authenticated", () => {
if (url.endsWith("/api/analysis-runs")) {
return Promise.resolve(
jsonResponse({
- analysis_runs: [
+ analysis_runs: options?.emptyAnalysisRuns ? [] : [
...(createdPendingLineage ? [createdPendingLineage] : []),
...(createdPendingTepp ? [createdPendingTepp] : []),
{
@@ -720,8 +725,10 @@ describe("App, authenticated", () => {
},
{
analysis_run_id: "run-demo-tepp",
- run_kind_code: "analysis_run_tepp",
- run_kind_label: "TEPP measurement",
+ run_kind_code: options?.topicLineageRun
+ ? "analysis_run_topic_lineage"
+ : "analysis_run_tepp",
+ run_kind_label: options?.topicLineageRun ? "Topic lineage" : "TEPP measurement",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
@@ -1136,6 +1143,8 @@ describe("App, authenticated", () => {
jsonResponse(
postsUrl.searchParams.get("search")
? []
+ : options?.emptyPosts
+ ? { posts: [], total_count: 0, limit: 50, offset: 0 }
: {
posts: [
{
@@ -1734,7 +1743,7 @@ describe("App, authenticated", () => {
ask_job_id: "ask-job-1",
job_status_code: "succeeded",
answer: {
- answer_text: "The cited project is supported by the stored semantic evidence.",
+ answer_text: "The cited project is supported by the linked record.",
cited_post_ids: ["post-2"],
cited_posts: [{ post_id: "post-2", post_title: "Linked post" }],
cited_post_evidence: [
@@ -2220,7 +2229,7 @@ describe("App, authenticated", () => {
await screen.findByRole("button", { name: "Search related posts for: Semantic project" }),
);
- const searchInput = await screen.findByRole("searchbox", { name: "Search semantic evidence" });
+ const searchInput = await screen.findByRole("searchbox", { name: "Search records and evidence" });
expect(searchInput).toHaveValue("Semantic project");
expect(screen.queryByRole("button", { name: "Close" })).not.toBeInTheDocument();
});
@@ -2249,7 +2258,7 @@ describe("App, authenticated", () => {
const board = await screen.findByRole("region", { name: "Board" });
expect(within(board).getByRole("search", { name: "Search and filter posts" })).toBeInTheDocument();
- expect(within(board).getByLabelText("Search semantic evidence")).toHaveAttribute("type", "search");
+ expect(within(board).getByLabelText("Search records and evidence")).toHaveAttribute("type", "search");
expect(within(board).getByRole("list", { name: "Board posts" })).toBeInTheDocument();
expect(within(board).getByText(/Posts shown:/)).toBeInTheDocument();
expect(within(board).getByLabelText("Voice of Partner")).toBeInTheDocument();
@@ -2259,13 +2268,24 @@ describe("App, authenticated", () => {
expect(fetchMock.mock.calls.some(([url]) => String(url).includes("sort=title"))).toBe(true),
);
- await userEvent.type(within(board).getByLabelText("Search semantic evidence"), "not found");
+ await userEvent.type(within(board).getByLabelText("Search records and evidence"), "not found");
await userEvent.click(within(board).getByRole("button", { name: "Search" }));
expect(within(board).getByRole("status")).toHaveTextContent("No posts match the current filters.");
await userEvent.click(within(board).getByRole("button", { name: "Reset filters" }));
expect(within(board).getByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
});
+ it("guides an empty board without exposing a developer command", async () => {
+ stubBackend({ emptyPosts: true });
+ render();
+
+ const status = await screen.findByRole("status");
+ expect(status).toHaveTextContent(
+ "No posts are available. Ask an administrator to check your access or import source records.",
+ );
+ expect(status).not.toHaveTextContent("make seed");
+ });
+
it("opens a post from a DAG node click", async () => {
stubBackend();
render();
@@ -2461,8 +2481,8 @@ describe("App, authenticated", () => {
expect(provenance).not.toHaveAttribute("open");
await userEvent.click(screen.getByText("Evidence provenance"));
expect(screen.getByText(/Ontology class:/)).toBeInTheDocument();
- expect(screen.getByText(/Extraction source: Semantic extraction/)).toBeInTheDocument();
- expect(screen.getByText(/Evidence field: Stored semantic evidence/)).toBeInTheDocument();
+ expect(screen.getByText(/Extraction source: Record content/)).toBeInTheDocument();
+ expect(screen.getByText(/Evidence field: Supporting record text/)).toBeInTheDocument();
expect(screen.queryByText("contextual_orchestrator_semantic")).not.toBeInTheDocument();
expect(screen.queryByText("https://contextualwisdomlab.github.io/LineageWeave/ontology#Project")).not.toBeInTheDocument();
expect(screen.getByText("첫 번째 이벤트")).toBeInTheDocument();
@@ -2512,13 +2532,17 @@ describe("App, authenticated", () => {
expect(keyman.compareDocumentPosition(ask) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
});
- it("labels a stale summary and retries the semantic refresh on request", async () => {
+ it("labels a stale summary and retries the summary refresh on request", async () => {
const fetchMock = stubBackend({ staleSummary: true });
render();
await userEvent.click(await screen.findByRole("button", { name: "View post: Public post" }));
await waitFor(() =>
- expect(screen.getByText("Last saved summary shown. Retry semantic refresh.")).toBeInTheDocument(),
+ expect(
+ screen.getByText(
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.",
+ ),
+ ).toBeInTheDocument(),
);
const summaryCallsBeforeRetry = fetchMock.mock.calls.filter(([input]) =>
String(input).endsWith("/api/posts/post-1/summary"),
@@ -2696,7 +2720,11 @@ describe("App, authenticated", () => {
await userEvent.click(await screen.findByRole("button", { name: /verify against web search/i }));
await waitFor(() =>
- expect(screen.getByText("Verification unavailable (search is not configured).")).toBeInTheDocument(),
+ expect(
+ screen.getByText(
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.",
+ ),
+ ).toBeInTheDocument(),
);
expect(screen.queryByText(/HTTP 503/)).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /verify against web search/i })).not.toBeInTheDocument();
@@ -3088,11 +3116,15 @@ describe("App, authenticated", () => {
);
});
- it("names RankWeave unavailability on home rankings instead of inventing a fused score", async () => {
+ it("gives a customer action when rankings are unavailable", async () => {
stubBackend();
render();
- expect(await screen.findByText("Rankings · RankWeave not available")).toBeInTheDocument();
+ expect(
+ await screen.findByText(
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.",
+ ),
+ ).toBeInTheDocument();
expect(screen.queryByText("Pricing renegotiation: revised quote sent")).not.toBeInTheDocument();
});
@@ -3196,11 +3228,11 @@ describe("App, authenticated", () => {
name: /open ranking: public post/i,
});
expect(rankingButton).toHaveTextContent("Public post");
- expect(rankingButton).toHaveTextContent("Rankings · rankweave");
+ expect(rankingButton).toHaveTextContent("Ranked result");
expect(rankingButton).toHaveTextContent("rank 1");
expect(
screen.getByText(
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
),
).toBeInTheDocument();
expect(
@@ -3249,7 +3281,7 @@ describe("App, authenticated", () => {
expect(list).toHaveTextContent("TEPP measurement · Failed · Demo Corp");
expect(list).toHaveTextContent("Period report · Succeeded · Demo Corp");
expect(list).toHaveTextContent(
- "Open this run to see why it failed, then connect the measurement service and re-run.",
+ "Ask an administrator to restore measurement access, then re-run this measurement.",
);
expect(list).toHaveTextContent("3 documents");
expect(list).not.toHaveTextContent("postgresql://");
@@ -3287,11 +3319,8 @@ describe("App, authenticated", () => {
expect(history).toHaveTextContent("Pending 2026-01-12 12:31");
expect(history).toHaveTextContent("Running 2026-01-12 12:32");
expect(history).toHaveTextContent("Succeeded 2026-01-12 12:33");
- const outbox = screen.getByRole("list", { name: "Analysis run outbox delivery" });
- expect(outbox).toHaveTextContent("Claimed 2026-01-12 12:32");
- expect(outbox).toHaveTextContent("Delivered 2026-01-12 12:33");
- expect(outbox).not.toHaveTextContent("valkey");
- expect(outbox).not.toHaveTextContent("stream");
+ expect(screen.queryByText("tepp_not_available")).not.toBeInTheDocument();
+ expect(screen.queryByRole("list", { name: "Analysis run outbox delivery" })).not.toBeInTheDocument();
expect(screen.getByRole("list", { name: "Posts known at this run cutoff" })).toBeInTheDocument();
const seededFork = screen.getByRole("list", { name: "Reconstructed lineage edges" });
expect(seededFork).toHaveTextContent(
@@ -3346,11 +3375,45 @@ describe("App, authenticated", () => {
await screen.findByRole("heading", { name: "TEPP measurement · Failed · Demo Corp" }),
).toBeInTheDocument();
const teppHistory = screen.getByRole("list", { name: "Analysis run status history" });
- expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37 · tepp_not_available");
- expect(screen.getByText(/cutoff corpus TEPP would measure/i)).toBeInTheDocument();
+ expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37");
+ expect(teppHistory).not.toHaveTextContent("tepp_not_available");
+ expect(
+ screen.getByText(
+ "These posts are the cutoff corpus TEPP would measure. Ask a workspace administrator to restore measurement access before re-running for a calibrated result.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.queryByText(/TEPP transport/i)).not.toBeInTheDocument();
expect(teppHistory).not.toHaveTextContent("Succeeded");
});
+ it("names the exact reconstruction action when no analysis runs are available", async () => {
+ stubBackend({ emptyAnalysisRuns: true });
+ render();
+
+ expect(
+ await screen.findByText(
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Request a lineage reconstruction" })).toBeInTheDocument();
+ });
+
+ it("directs a failed topic-lineage run to restore topic measurement access", async () => {
+ stubBackend({ topicLineageRun: true });
+ render();
+
+ await userEvent.click(
+ await screen.findByRole("button", {
+ name: "Open analysis run: Topic lineage · Failed · Demo Corp",
+ }),
+ );
+ expect(
+ screen.getByText(
+ "These posts are the cutoff corpus topic-lineage would thread. Ask a workspace administrator to restore topic measurement access before re-running for a topic-identity result.",
+ ),
+ ).toBeInTheDocument();
+ });
+
it("warns that a cutoff-rewritten title opens the live body, not a snapshot", async () => {
stubBackend();
render();
@@ -3399,7 +3462,7 @@ describe("App, authenticated", () => {
expect(screen.queryByRole("heading", { name: "Body this run knew" })).not.toBeInTheDocument();
});
- it("tells a running lineage run to refresh the durable outbox", async () => {
+ it("tells a running lineage run to refresh for completion", async () => {
stubBackend({ runningLineageRun: true });
render();
@@ -3407,16 +3470,16 @@ describe("App, authenticated", () => {
name: "Open analysis run: Lineage reconstruction · Running · Demo Corp",
});
expect(lineageButton).toHaveTextContent(
- "Refresh this run. Start already queued the work on the durable outbox.",
+ "Refresh this run to check whether the work has finished.",
);
await userEvent.click(lineageButton);
expect(screen.getByRole("button", { name: "Start reconstruction" })).toBeInTheDocument();
expect(
- screen.getAllByText("Refresh this run. Start already queued the work on the durable outbox."),
+ screen.getAllByText("Refresh this run to check whether the work has finished."),
).not.toHaveLength(0);
});
- it("does not tell a failed lineage run to connect the measurement service", async () => {
+ it("does not tell a failed lineage run to restore measurement access", async () => {
stubBackend({ failedLineageRun: true });
render();
@@ -3428,11 +3491,11 @@ describe("App, authenticated", () => {
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.",
+ "Retry reconstruction from a current snapshot.",
);
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.",
+ "Ask an administrator to restore measurement access, then re-run this measurement.",
);
expect(teppButton).not.toHaveTextContent("reconstruction");
});
@@ -3701,7 +3764,7 @@ describe("App, authenticated", () => {
}
});
- it("does not tell a failed period report to connect the measurement service", async () => {
+ it("does not tell a failed period report to restore measurement access", async () => {
stubBackend({ failedReportRun: true });
render();
@@ -3709,7 +3772,7 @@ describe("App, authenticated", () => {
name: "Open analysis run: Period report · Failed · Demo Corp",
});
expect(reportButton).toHaveTextContent(
- "Open this run to see why it failed, then rebuild the period report from a current snapshot.",
+ "Rebuild the period report from a current snapshot.",
);
expect(reportButton).not.toHaveTextContent("measurement service");
expect(reportButton).not.toHaveTextContent("reconstruction");
@@ -3758,7 +3821,7 @@ describe("App, authenticated", () => {
expect(
await screen.findByRole("heading", { name: "TEPP measurement · Failed · Demo Corp" }),
).toBeInTheDocument();
- expect(screen.getByText(/tepp_not_available/)).toBeInTheDocument();
+ expect(screen.queryByText(/tepp_not_available/)).not.toBeInTheDocument();
expect(screen.queryByText(/theta/i)).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Start reconstruction" })).not.toBeInTheDocument();
const startCall = fetchMock.mock.calls.find((call) =>
@@ -3778,7 +3841,7 @@ describe("App, authenticated", () => {
);
expect(
await screen.findByText(
- "Connect a TEPP transport from this Failed row. Request a lineage reconstruction does not invent a measurement.",
+ "Ask an administrator to restore measurement access, then re-run this analysis. A lineage reconstruction will not produce a measurement.",
),
).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Request a new TEPP measurement" })).not.toBeInTheDocument();
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index fbba1d9f2..3c378c80c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -157,7 +157,7 @@ function LanguageSwitcher({ accessToken }: { accessToken?: string }) {
function searchUnavailableMessage(err: unknown): string {
if (err instanceof BackendError && err.status === 503) {
- return t("Verification unavailable (search is not configured).");
+ return t("Public verification is temporarily unavailable. Review the saved evidence, then retry later.");
}
return String(err);
}
@@ -795,13 +795,13 @@ function relatedNodeCaption(node: RelatedNode): string {
const PROJECT_EXTRACTION_LABELS: Record = {
source_field_hint: "Explicit source field",
- contextual_orchestrator_semantic: "Semantic extraction",
+ contextual_orchestrator_semantic: "Record content",
};
const PROJECT_PROVENANCE_LABELS: Record = {
"source_post.source_project_code": "Source project code",
"source_post.source_project_name": "Source project name",
- "post_project_mention.evidence_text": "Stored semantic evidence",
+ "post_project_mention.evidence_text": "Supporting record text",
};
function projectExtractionLabel(method: string): string {
@@ -2145,7 +2145,7 @@ function PostDetailPopup({
) : (
- {t("Source body was not imported; summary and semantic extraction are unavailable.")}
+ {t("The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.")}
- {t("Last saved summary shown. Retry semantic refresh.")} {" "}
+ {t("The last saved summary is shown. Select Retry summary refresh to check for an update.")} {" "}
@@ -2663,10 +2663,8 @@ function analysisRunCaption(run: AnalysisRun): string {
/**
* Next action for a pending or failed run on the home list and detail.
*
- * The machine `failure_code` stays on detail history (ADR 0014). Copy
- * is pinned to registered kinds so a pending TEPP row is not mistaken
- * for reconstruction, and a failed lineage row is not mistaken for a
- * missing TEPP transport.
+ * Machine failure codes remain outside the customer view. Copy is pinned to
+ * registered kinds so each run offers the recovery action that can help.
*/
function analysisRunNextAction(run: AnalysisRun): string | null {
switch (run.status_code) {
@@ -2688,20 +2686,20 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
case "analysis_status_failed":
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.";
+ return "Ask an administrator to restore measurement access, then re-run this measurement.";
case "analysis_run_topic_lineage":
- return "Open this run to see why it failed, then connect the TEPP transport and re-run.";
+ return "Ask an administrator to restore topic measurement access, then re-run this topic analysis.";
case "analysis_run_lineage":
- return "Open this run to see why it failed, then retry reconstruction from a current snapshot.";
+ return "Retry reconstruction from a current snapshot.";
case "analysis_run_report":
- return "Open this run to see why it failed, then rebuild the period report from a current snapshot.";
+ return "Rebuild the period report from a current snapshot.";
default: {
const unexpected: never = run.run_kind_code;
return unexpected;
}
}
case "analysis_status_running":
- return "Refresh this run. Start already queued the work on the durable outbox.";
+ return "Refresh this run to check whether the work has finished.";
case "analysis_status_succeeded":
case "analysis_status_cancelled":
case null:
@@ -2755,14 +2753,15 @@ function analysisRunCorpusHint(run: AnalysisRun): string | null {
const isTopicLineage = run.run_kind_code === "analysis_run_topic_lineage";
if (run.run_kind_code !== "analysis_run_tepp" && !isTopicLineage) return null;
const service = isTopicLineage ? "topic-lineage" : "TEPP";
+ const access = isTopicLineage ? "topic measurement access" : "measurement access";
const result = isTopicLineage ? "a topic-identity result" : "a calibrated result";
const verb = isTopicLineage ? "thread" : "measure";
const verbPast = isTopicLineage ? "threaded" : "measured";
switch (run.status_code) {
case "analysis_status_failed":
return (
- `These posts are the cutoff corpus ${service} would ${verb}. Connect a TEPP ` +
- `transport, then re-run, to replace Failed with ${result}.`
+ `These posts are the cutoff corpus ${service} would ${verb}. ` +
+ `Ask a workspace administrator to restore ${access} before re-running for ${result}.`
);
case "analysis_status_succeeded":
return `These posts are the cutoff corpus this ${service} run ${verbPast}.`;
@@ -3016,12 +3015,12 @@ function AnalysisRunsPanel({
const inFlightKeyRef = useRef(null);
const entitiesReady = corporateEntities !== null && entitiesLoadError === null;
const requestLabel = requesting
- ? "Recording the run..."
+ ? t("Recording the run...")
: entitiesLoadError
- ? "Reload to choose a corporate entity"
+ ? t("Reload to choose a corporate entity")
: corporateEntities === null
- ? "Loading affiliated entities..."
- : "Request a lineage reconstruction";
+ ? t("Loading affiliated entities...")
+ : t("Request a lineage reconstruction");
useEffect(() => {
fetchAnalysisRuns(accessToken)
@@ -3139,8 +3138,7 @@ function AnalysisRunsPanel({
{(error || entitiesLoadError) &&
{error ?? entitiesLoadError}
}
{runs.length === 0 ? (
- No analysis runs visible to this account yet. Request a lineage
- reconstruction, or ask an administrator to run make seed.
+ {t("No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.")}
) : (
@@ -3203,10 +3201,10 @@ function AnalysisRunsPanel({
{analysisRunCanRequestTeppRetry(selected) && (
{selected.run_kind_code === "analysis_run_topic_lineage"
- ? "Connect a TEPP transport from this Failed row. Request a " +
- "lineage reconstruction does not invent a topic model."
- : "Connect a TEPP transport from this Failed row. Request a lineage " +
- "reconstruction does not invent a measurement."}
+ ? "Ask an administrator to restore topic measurement access, then re-run this analysis. " +
+ "A lineage reconstruction will not produce a topic result."
+ : "Ask an administrator to restore measurement access, then re-run this analysis. " +
+ "A lineage reconstruction will not produce a measurement."}
{t(
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
)}