Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9f841cc
fix(ui): hide ranking engine name from customer copy
Aug 26, 2026
fbc0987
fix(ui): retain ranking measurement disclaimer
Aug 26, 2026
9efe21d
fix(ui): hide fused ranking implementation label
Aug 26, 2026
b0e4d2c
fix(ui): replace internal ontology labels
Aug 26, 2026
4e41bcd
test(ui): assert complete ranking guidance
Aug 26, 2026
be34c33
fix(ui): describe source record details accurately
Aug 26, 2026
cc1b779
fix(ui): align ranking guidance with available actions
Aug 26, 2026
5c246a5
style(ui): align related-concepts heading
Aug 26, 2026
b6c8c46
docs(ui): align ranking copy with product boundary
Aug 26, 2026
cc2b718
fix(ui): remove internal ontology copy
Aug 26, 2026
95f8fee
Merge remote-tracking branch 'origin/main' into fix/customer-facing-r…
Aug 26, 2026
6a9a3de
fix(ui): preserve product capability labels
Aug 26, 2026
eafa9e0
fix(i18n): remove retired source-state copy
Aug 26, 2026
cd2cfb8
fix(i18n): align ranked-record labels
Aug 26, 2026
ff4d9ea
fix(ui): provide durable rankings next action
Aug 26, 2026
6013821
fix(ui): replace implementation copy with user actions
Aug 26, 2026
bebab3d
fix(ui): keep recovery guidance product-facing
Aug 26, 2026
efe864e
fix(ui): keep recovery guidance product-facing
Aug 26, 2026
53091d5
fix(ui): align analysis next actions
Aug 26, 2026
876129e
fix(i18n): align Vietnamese reconstruction action
Aug 26, 2026
b05e310
fix(ui): keep analysis guidance customer-facing
Aug 26, 2026
d7f3e60
fix(ui): give failed runs actionable recovery copy
Aug 26, 2026
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
10 changes: 6 additions & 4 deletions backend/app/analysis_run_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)
Comment thread
seonghobae marked this conversation as resolved.
if run_kind_code == _REPORT_RUN_KIND:
raise AnalysisRunCreateError(
Expand Down
8 changes: 4 additions & 4 deletions backend/app/analysis_run_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
)


Expand Down
2 changes: 1 addition & 1 deletion backend/app/global_ask_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Comment thread
seonghobae marked this conversation as resolved.
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.",
Expand Down
7 changes: 4 additions & 3 deletions docs/adr/0024-rankweave-fusion-fail-closed.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions docs/adr/0167-rankweave-ranking-channel-evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
9 changes: 9 additions & 0 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
137 changes: 100 additions & 37 deletions frontend/src/App.test.tsx

Large diffs are not rendered by default.

98 changes: 44 additions & 54 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -795,13 +795,13 @@ function relatedNodeCaption(node: RelatedNode): string {

const PROJECT_EXTRACTION_LABELS: Record<string, string> = {
source_field_hint: "Explicit source field",
contextual_orchestrator_semantic: "Semantic extraction",
contextual_orchestrator_semantic: "Record content",
};

const PROJECT_PROVENANCE_LABELS: Record<string, string> = {
"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 {
Expand Down Expand Up @@ -2145,7 +2145,7 @@ function PostDetailPopup({
<PostBody body={post.post_body} imageContent={imageContent} structureUnits={structureUnits} />
) : (
<p className="popup-placeholder" role="status">
{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.")}
</p>
)}
</section>
Expand All @@ -2167,8 +2167,8 @@ function PostDetailPopup({
post.source_project_name ||
post.source_system_code ||
post.source_record_key) && (
<section className="popup-section" aria-label={t("Original source state")}>
<h3>{t("Original source state")}</h3>
<section className="popup-section" aria-label={t("Source record details")}>
<h3>{t("Source record details")}</h3>
<dl>
{post.source_stage_code ? (
<>
Expand Down Expand Up @@ -2279,15 +2279,15 @@ function PostDetailPopup({
</>
) : null}
</dl>
<p className="post-meta">{t("Raw source codes are shown; no state label was inferred.")}</p>
<p className="post-meta">{t("Use these details to compare this record in its original system.")}</p>
</section>
)}

<FiveW1H slots={fiveW1H?.slots ?? null} />

{post.project_evidence && post.project_evidence.length > 0 ? (
<section className="popup-section" aria-label={t("Projects / semantic evidence")}>
<h3>{t("Projects / semantic evidence")}</h3>
<section className="popup-section" aria-label={t("Projects and supporting evidence")}>
<h3>{t("Projects and supporting evidence")}</h3>
<ul>
{post.project_evidence.map((project) => (
<li key={`${project.resolution_status}:${project.project_key}`}>
Expand Down Expand Up @@ -2329,7 +2329,7 @@ function PostDetailPopup({
<>
{summary.summary_status === "stale" ? (
<p className="post-meta" role="status">
{t("Last saved summary shown. Retry semantic refresh.")} {" "}
{t("The last saved summary is shown. Select Retry summary refresh to check for an update.")} {" "}
<button type="button" onClick={() => setSummaryRetry((value) => value + 1)}>
{t("Retry summary refresh")}
</button>
Expand Down Expand Up @@ -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) {
Expand All @@ -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:
Expand Down Expand Up @@ -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}.`;
Expand Down Expand Up @@ -3016,12 +3015,12 @@ function AnalysisRunsPanel({
const inFlightKeyRef = useRef<string | null>(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)
Expand Down Expand Up @@ -3139,8 +3138,7 @@ function AnalysisRunsPanel({
{(error || entitiesLoadError) && <p className="error">{error ?? entitiesLoadError}</p>}
{runs.length === 0 ? (
<p className="popup-placeholder">
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.")}
</p>
) : (
<ul className="ticket-list" aria-label="Analysis runs">
Expand Down Expand Up @@ -3203,10 +3201,10 @@ function AnalysisRunsPanel({
{analysisRunCanRequestTeppRetry(selected) && (
<p className="post-meta">
{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."}
</p>
)}
{analysisRunReportPeriod(selected) && onSelectReportPeriod && (
Expand Down Expand Up @@ -3277,16 +3275,6 @@ function AnalysisRunsPanel({
{selected.status_history.map((event) => (
<li key={event.status_ordinal}>
{event.status_label} {event.occurred_at.slice(0, 16).replace("T", " ")}
{event.failure_code ? ` · ${event.failure_code}` : ""}
Comment thread
seonghobae marked this conversation as resolved.
</li>
))}
</ol>
)}
{selected.outbox_deliveries && selected.outbox_deliveries.length > 0 && (
<ol aria-label="Analysis run outbox delivery">
{selected.outbox_deliveries.map((event) => (
<li key={event.delivery_ordinal}>
{event.delivery_status_label} {event.occurred_at.slice(0, 16).replace("T", " ")}
</li>
))}
</ol>
Expand Down Expand Up @@ -3362,27 +3350,29 @@ function RankingsPanel({
{ranking && (
<span className="post-badge">
{ranking.status === "accepted"
? "rankweave"
: `rankweave · ${ranking.status_reason ?? "unavailable"}`}
? t("Ranked result")
: t("Rankings unavailable")}
</span>
Comment thread
seonghobae marked this conversation as resolved.
)}
</div>
{error && <p className="error">{error}</p>}
{ranking === null && !error && <p role="status">{t("Loading rankings...")}</p>}
{ranking && ranking.status === "unavailable" && (
<p className="popup-placeholder">{t("Rankings · RankWeave not available")}</p>
<p className="popup-placeholder">
{t("Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.")}
</p>
)}
{ranking && ranking.status === "accepted" && ranking.rankings.length === 0 && (
<p className="popup-placeholder">{t("No fused rankings from RankWeave.")}</p>
<p className="popup-placeholder">{t("No ranked records are available. Check again after more records are added.")}</p>
)}
{ranking && ranking.rankings.length > 0 && (
<>
<p className="ranking-channel-evidence-copy">
{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.",
)}
Comment thread
seonghobae marked this conversation as resolved.
</p>
<ul className="ticket-list" aria-label={t("Fused rankings")}>
<ul className="ticket-list" aria-label={t("Ranked records")}>
{ranking.rankings.map((hit) => (
<li key={hit.post_id} className="ticket-list-item ranking-hit">
<button
Expand All @@ -3391,7 +3381,7 @@ function RankingsPanel({
onClick={() => onSelectPost(hit.post_id)}
>
<span className="ticket-title">{hit.post_title}</span>
<span className="post-badge">{t("Rankings · rankweave")}</span>
<span className="post-badge">{t("Ranked result")}</span>
<span className="post-badge">{tf("rank {rank}", { rank: String(hit.fused_rank) })}</span>
</button>
{(hit.channel_evidence ?? []).length > 0 ? (
Expand Down Expand Up @@ -4151,17 +4141,17 @@ function PostList({
}}
>
<label>
{t("Search semantic evidence")}
{t("Search records and evidence")}
<input
type="search"
value={searchInput}
onChange={(event) => setSearchInput(event.target.value)}
placeholder={t("Search semantic evidence")}
aria-label={t("Search semantic evidence")}
placeholder={t("Search records and evidence")}
aria-label={t("Search records and evidence")}
/>
</label>
<button type="submit">{t("Search")}</button>
<p className="board-search-help post-meta">{t("Search includes post text and semantic evidence.")}</p>
<p className="board-search-help post-meta">{t("Enter words from a record or its supporting evidence, then select Search.")}</p>
<fieldset className="board-voc-type-filter">
<legend>{t("Filter by VOC type")}</legend>
{vocTypeOptions.map((option) => (
Expand Down Expand Up @@ -4218,7 +4208,7 @@ function PostList({
<p className="board-empty" role="status">
{hasBoardFilters
? t("No posts match the current filters.")
: t("No posts visible to this account yet -- try `make seed`.")}
: t("No posts are available. Ask an administrator to check your access or import source records.")}
</p>
) : filteredPosts.length === 0 ? (
<p className="board-empty" role="status">
Expand Down Expand Up @@ -4683,7 +4673,7 @@ function CustomerMasterPanel({
<section className="customer-keymen" aria-labelledby="observed-customer-evidence-heading">
<h3 id="observed-customer-evidence-heading">{t("Observed customer evidence")}</h3>
<p className="workspace-destination-intro">
{t("Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.")}
{t("These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.")}
</p>
{master.source_customer_hints.length > HINT_RENDER_LIMIT && (
<p className="post-meta">
Expand Down
Loading
Loading