Skip to content

Commit d90bf0c

Browse files
xiaojiou176tester
andauthored
fix: tighten pm first-run and current-run truth closure (#194)
Co-authored-by: tester <test@example.com>
1 parent a34e3eb commit d90bf0c

13 files changed

Lines changed: 197 additions & 7 deletions

File tree

apps/dashboard/app/pm/components/PMIntakeLeftSidebar.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export default function PMIntakeLeftSidebar(props: Props) {
4848
onFocusInput,
4949
} = props;
5050
const activeSession = sessionHistory.find((session) => session.pm_session_id === intakeId);
51+
const showBlockingHistoryError = Boolean(sessionHistoryError) && (Boolean(intakeId) || sessionHistory.length > 0);
52+
const showFirstRunHistoryNotice = Boolean(sessionHistoryError) && !showBlockingHistoryError;
5153
const activeSessionLabel = intakeId
5254
? `${locale === "zh-CN" ? "当前会话" : "Current session"}: ${
5355
activeSession
@@ -119,7 +121,14 @@ export default function PMIntakeLeftSidebar(props: Props) {
119121
{activeSessionLabel}
120122
</p>
121123

122-
{sessionHistoryError && <p className="alert alert-danger" role="alert">{sessionHistoryError}</p>}
124+
{showBlockingHistoryError ? <p className="alert alert-danger" role="alert">{sessionHistoryError}</p> : null}
125+
{showFirstRunHistoryNotice ? (
126+
<p className="alert alert-warning" role="status">
127+
{locale === "zh-CN"
128+
? "会话历史暂时不可用。你仍然可以直接发出第一条请求,系统会创建正式会话。"
129+
: "Session history is temporarily unavailable. You can still start the first request and create the formal session."}
130+
</p>
131+
) : null}
123132
{newConversationError && (
124133
<p className="alert alert-danger" role="alert" data-testid="pm-new-conversation-error">
125134
{newConversationError}

apps/dashboard/app/workflows/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export default async function WorkflowsPage() {
239239
<span className="muted">{workflowListPageCopy.emptyTitle}</span>
240240
<span className="mono muted">{workflowListPageCopy.emptyHint}</span>
241241
<Button asChild variant="default">
242-
<Link href="/pm">打开 PM 入口</Link>
242+
<Link href="/pm">{locale === "zh-CN" ? "打开 PM 入口" : "Open PM intake"}</Link>
243243
</Button>
244244
</div>
245245
</Card>

apps/dashboard/tests/home_page.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,14 @@ describe("dashboard home run-summary clarity", () => {
567567
expect(String(zhMetadata.description)).toContain("证明与回放桌面");
568568
});
569569

570+
it("renders the English runs desk with English-first copy", async () => {
571+
render(await RunsPage({ searchParams: Promise.resolve({}) }));
572+
573+
expect(screen.getByRole("heading", { name: "Proof & Replay" })).toBeInTheDocument();
574+
expect(screen.queryByRole("heading", { name: "证明与回放" })).not.toBeInTheDocument();
575+
expect(screen.getByText("This desk should answer one question first: which run deserves proof right now.")).toBeInTheDocument();
576+
});
577+
570578
it("shows degraded risk summary when runs data is unavailable", async () => {
571579
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
572580
mockFetchRuns.mockRejectedValueOnce(new Error("runs down"));

apps/dashboard/tests/pm_page_chat.test.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,10 @@ describe("pm page chat-driven flow", () => {
769769
mockFetchPmSessions.mockRejectedValue(new Error("network down"));
770770
render(<PMIntakePage />);
771771

772-
expect(await screen.findByRole("alert")).toHaveTextContent("Failed to load session history: network error, please try again.");
772+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
773+
expect(
774+
await screen.findByText("Session history is temporarily unavailable. You can still start the first request and create the formal session.")
775+
).toBeInTheDocument();
773776
});
774777

775778
it("cancels in-flight chat request and keeps input draft", async () => {

apps/dashboard/tests/pm_page_stage_flow.suite.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,52 @@ describe("pm intake component branches", () => {
372372
expect(screen.getByText("Loading session history")).toBeInTheDocument();
373373
});
374374

375+
it("keeps first-run session history failures as a non-blocking status note", () => {
376+
render(
377+
<PMIntakeLeftSidebar
378+
{...buildLeftSidebarProps({
379+
sessionHistoryError: "Failed to load session history: network error, please try again.",
380+
sessionHistory: [],
381+
intakeId: "",
382+
})}
383+
/>,
384+
);
385+
386+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
387+
expect(
388+
screen.getByText("Session history is temporarily unavailable. You can still start the first request and create the formal session.")
389+
).toBeInTheDocument();
390+
expect(screen.getByText("No previous sessions yet. Send the first request to start.")).toBeInTheDocument();
391+
});
392+
393+
it("keeps session-history failures blocking once a real session exists", () => {
394+
render(
395+
<PMIntakeLeftSidebar
396+
{...buildLeftSidebarProps({
397+
intakeId: "pm-history-1",
398+
sessionHistoryError: "Failed to load session history: network error, please try again.",
399+
sessionHistory: [
400+
{
401+
pm_session_id: "pm-history-1",
402+
status: "active",
403+
run_count: 1,
404+
running_runs: 1,
405+
failed_runs: 0,
406+
success_runs: 0,
407+
blocked_runs: 0,
408+
latest_run_id: "run-1",
409+
current_role: "PM",
410+
current_step: "plan",
411+
updated_at: "2026-03-01T10:00:00.000Z",
412+
},
413+
],
414+
})}
415+
/>,
416+
);
417+
418+
expect(screen.getByRole("alert")).toHaveTextContent("Failed to load session history: network error, please try again.");
419+
});
420+
375421
it("covers center panel keyboard link actions and inline chain expand action", () => {
376422
const onLayoutModeChange = vi.fn();
377423
const onHoveredChainRoleChange = vi.fn();

apps/dashboard/tests/workflows_queue_page.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,16 @@ describe("workflows queue page", () => {
138138
expect(screen.getByRole("link", { name: / PM |Open PM intake/ })).toHaveAttribute("href", "/pm");
139139
expect(screen.getByText(/No workflow cases yet|/)).toBeInTheDocument();
140140
});
141+
142+
it("keeps the English empty-state CTA in English", async () => {
143+
vi.mocked(fetchWorkflows).mockResolvedValue([] as never);
144+
vi.mocked(fetchQueue).mockResolvedValue([] as never);
145+
vi.mocked(mutationExecutionCapability).mockReturnValue({ executable: false, operatorRole: null } as never);
146+
147+
const view = await WorkflowsPage();
148+
render(view);
149+
150+
expect(screen.getByRole("link", { name: "Open PM intake" })).toHaveAttribute("href", "/pm");
151+
expect(screen.queryByRole("link", { name: "打开 PM 入口" })).not.toBeInTheDocument();
152+
});
141153
});

apps/orchestrator/tests/test_ci_current_run_report_contracts.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import json
4+
import os
45
import subprocess
56
import sys
67
from datetime import datetime, timezone
@@ -246,6 +247,33 @@ def test_current_run_consistency_downgrades_stale_local_advisory_to_advisory(tmp
246247
assert "source_sha_mismatch" in payload["authority_reasons"]
247248

248249

250+
def test_ci_control_plane_doctor_can_emit_current_run_source_metadata(tmp_path: Path) -> None:
251+
out_dir = tmp_path / "doctor"
252+
env = {
253+
"OPENVIBECODING_CI_CONTROL_PLANE_DOCTOR_OUT_DIR": str(out_dir),
254+
"OPENVIBECODING_DOCTOR_REQUIRE_DOCKER": "0",
255+
"OPENVIBECODING_DOCTOR_REQUIRE_SUDO": "0",
256+
"RUNNER_TEMP": str(tmp_path / "runner-temp"),
257+
"OPENVIBECODING_CI_SOURCE_RUN_ID": "local-run",
258+
"OPENVIBECODING_CI_SOURCE_ROUTE": "local_full_ci",
259+
"OPENVIBECODING_CI_SOURCE_EVENT": "local",
260+
}
261+
proc = subprocess.run(
262+
["bash", str(REPO_ROOT / "scripts" / "ci_control_plane_doctor.sh")],
263+
cwd=REPO_ROOT,
264+
text=True,
265+
capture_output=True,
266+
env={**os.environ, **env},
267+
check=False,
268+
)
269+
270+
assert proc.returncode == 0, proc.stderr or proc.stdout
271+
payload = json.loads((out_dir / "report.json").read_text(encoding="utf-8"))
272+
assert payload["source_run_id"] == "local-run"
273+
assert payload["source_route"] == "local_full_ci"
274+
assert payload["source_event"] == "local"
275+
276+
249277
def test_artifact_index_marks_head_mismatch_non_authoritative(tmp_path: Path) -> None:
250278
route_report = tmp_path / "trusted_pr.json"
251279
route_report.write_text("{}", encoding="utf-8")

configs/env.registry.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,45 @@
16921692
"scripts/lib/ci_main_impl.sh"
16931693
]
16941694
},
1695+
{
1696+
"name": "OPENVIBECODING_CI_SOURCE_EVENT",
1697+
"scope": "ci",
1698+
"secret": false,
1699+
"required": false,
1700+
"default": null,
1701+
"owner": "platform",
1702+
"description": "Current CI source event propagated into control-plane doctor and current-run provenance receipts so authoritative local/full-ci evidence can carry the triggering event class.",
1703+
"consumers": [
1704+
"scripts/ci_control_plane_doctor.sh",
1705+
"scripts/lib/ci_step126_helpers.sh"
1706+
]
1707+
},
1708+
{
1709+
"name": "OPENVIBECODING_CI_SOURCE_ROUTE",
1710+
"scope": "ci",
1711+
"secret": false,
1712+
"required": false,
1713+
"default": null,
1714+
"owner": "platform",
1715+
"description": "Current CI source route identifier propagated into control-plane doctor and current-run provenance receipts so authoritative evidence keeps the originating route contract.",
1716+
"consumers": [
1717+
"scripts/ci_control_plane_doctor.sh",
1718+
"scripts/lib/ci_step126_helpers.sh"
1719+
]
1720+
},
1721+
{
1722+
"name": "OPENVIBECODING_CI_SOURCE_RUN_ID",
1723+
"scope": "ci",
1724+
"secret": false,
1725+
"required": false,
1726+
"default": null,
1727+
"owner": "platform",
1728+
"description": "Current CI source run identifier propagated into control-plane doctor and current-run provenance receipts so authoritative evidence keeps the originating run lineage.",
1729+
"consumers": [
1730+
"scripts/ci_control_plane_doctor.sh",
1731+
"scripts/lib/ci_step126_helpers.sh"
1732+
]
1733+
},
16951734
{
16961735
"name": "OPENVIBECODING_CI_RUNNER_CLASS",
16971736
"scope": "ci",

packages/frontend-shared/uiCopy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ const UI_COPY = {
581581
queueSummary: (count, slaState) => `queue: ${count} / SLA ${slaState}`,
582582
},
583583
runsPage: {
584-
title: "证明与回放",
584+
title: "Proof & Replay",
585585
subtitle: "Use this spine to inspect run evidence, compare posture, and replay decisions. Failed-run triage is only one lane inside the broader proof desk.",
586586
countsBadge: (runCount) => `${runCount} runs`,
587587
warningTitle: "Proof & Replay is currently running with partial truth.",

packages/frontend-shared/uiCopy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1676,7 +1676,7 @@ const UI_COPY: Record<UiLocale, UiCopy> = {
16761676
queueSummary: (count: number, slaState: string) => `queue: ${count} / SLA ${slaState}`,
16771677
},
16781678
runsPage: {
1679-
title: "证明与回放",
1679+
title: "Proof & Replay",
16801680
subtitle:
16811681
"Use this spine to inspect run evidence, compare posture, and replay decisions. Failed-run triage is only one lane inside the broader proof desk.",
16821682
countsBadge: (runCount: number) => `${runCount} runs`,

0 commit comments

Comments
 (0)