+ {locale === "zh-CN"
+ ? "会话历史暂时不可用。你仍然可以直接发出第一条请求,系统会创建正式会话。"
+ : "Session history is temporarily unavailable. You can still start the first request and create the formal session."}
+
+ ) : null}
{newConversationError && (
{newConversationError}
diff --git a/apps/dashboard/app/workflows/page.tsx b/apps/dashboard/app/workflows/page.tsx
index 1a1d171..230024d 100644
--- a/apps/dashboard/app/workflows/page.tsx
+++ b/apps/dashboard/app/workflows/page.tsx
@@ -239,7 +239,7 @@ export default async function WorkflowsPage() {
{workflowListPageCopy.emptyTitle}{workflowListPageCopy.emptyHint}
diff --git a/apps/dashboard/tests/home_page.test.tsx b/apps/dashboard/tests/home_page.test.tsx
index 5bf8c9e..9e62690 100644
--- a/apps/dashboard/tests/home_page.test.tsx
+++ b/apps/dashboard/tests/home_page.test.tsx
@@ -567,6 +567,14 @@ describe("dashboard home run-summary clarity", () => {
expect(String(zhMetadata.description)).toContain("证明与回放桌面");
});
+ it("renders the English runs desk with English-first copy", async () => {
+ render(await RunsPage({ searchParams: Promise.resolve({}) }));
+
+ expect(screen.getByRole("heading", { name: "Proof & Replay" })).toBeInTheDocument();
+ expect(screen.queryByRole("heading", { name: "证明与回放" })).not.toBeInTheDocument();
+ expect(screen.getByText("This desk should answer one question first: which run deserves proof right now.")).toBeInTheDocument();
+ });
+
it("shows degraded risk summary when runs data is unavailable", async () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
mockFetchRuns.mockRejectedValueOnce(new Error("runs down"));
diff --git a/apps/dashboard/tests/pm_page_chat.test.tsx b/apps/dashboard/tests/pm_page_chat.test.tsx
index afc4baf..8a5a3a6 100644
--- a/apps/dashboard/tests/pm_page_chat.test.tsx
+++ b/apps/dashboard/tests/pm_page_chat.test.tsx
@@ -769,7 +769,10 @@ describe("pm page chat-driven flow", () => {
mockFetchPmSessions.mockRejectedValue(new Error("network down"));
render();
- expect(await screen.findByRole("alert")).toHaveTextContent("Failed to load session history: network error, please try again.");
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
+ expect(
+ await screen.findByText("Session history is temporarily unavailable. You can still start the first request and create the formal session.")
+ ).toBeInTheDocument();
});
it("cancels in-flight chat request and keeps input draft", async () => {
diff --git a/apps/dashboard/tests/pm_page_stage_flow.suite.tsx b/apps/dashboard/tests/pm_page_stage_flow.suite.tsx
index e7d1d53..6ab17a4 100644
--- a/apps/dashboard/tests/pm_page_stage_flow.suite.tsx
+++ b/apps/dashboard/tests/pm_page_stage_flow.suite.tsx
@@ -372,6 +372,52 @@ describe("pm intake component branches", () => {
expect(screen.getByText("Loading session history")).toBeInTheDocument();
});
+ it("keeps first-run session history failures as a non-blocking status note", () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
+ expect(
+ screen.getByText("Session history is temporarily unavailable. You can still start the first request and create the formal session.")
+ ).toBeInTheDocument();
+ expect(screen.getByText("No previous sessions yet. Send the first request to start.")).toBeInTheDocument();
+ });
+
+ it("keeps session-history failures blocking once a real session exists", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByRole("alert")).toHaveTextContent("Failed to load session history: network error, please try again.");
+ });
+
it("covers center panel keyboard link actions and inline chain expand action", () => {
const onLayoutModeChange = vi.fn();
const onHoveredChainRoleChange = vi.fn();
diff --git a/apps/dashboard/tests/workflows_queue_page.test.tsx b/apps/dashboard/tests/workflows_queue_page.test.tsx
index 030c3b4..fb91f83 100644
--- a/apps/dashboard/tests/workflows_queue_page.test.tsx
+++ b/apps/dashboard/tests/workflows_queue_page.test.tsx
@@ -138,4 +138,16 @@ describe("workflows queue page", () => {
expect(screen.getByRole("link", { name: /打开 PM 入口|Open PM intake/ })).toHaveAttribute("href", "/pm");
expect(screen.getByText(/No workflow cases yet|当前还没有工作流案例/)).toBeInTheDocument();
});
+
+ it("keeps the English empty-state CTA in English", async () => {
+ vi.mocked(fetchWorkflows).mockResolvedValue([] as never);
+ vi.mocked(fetchQueue).mockResolvedValue([] as never);
+ vi.mocked(mutationExecutionCapability).mockReturnValue({ executable: false, operatorRole: null } as never);
+
+ const view = await WorkflowsPage();
+ render(view);
+
+ expect(screen.getByRole("link", { name: "Open PM intake" })).toHaveAttribute("href", "/pm");
+ expect(screen.queryByRole("link", { name: "打开 PM 入口" })).not.toBeInTheDocument();
+ });
});
diff --git a/apps/orchestrator/tests/test_ci_current_run_report_contracts.py b/apps/orchestrator/tests/test_ci_current_run_report_contracts.py
index 7f5f563..28f4849 100644
--- a/apps/orchestrator/tests/test_ci_current_run_report_contracts.py
+++ b/apps/orchestrator/tests/test_ci_current_run_report_contracts.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
+import os
import subprocess
import sys
from datetime import datetime, timezone
@@ -246,6 +247,33 @@ def test_current_run_consistency_downgrades_stale_local_advisory_to_advisory(tmp
assert "source_sha_mismatch" in payload["authority_reasons"]
+def test_ci_control_plane_doctor_can_emit_current_run_source_metadata(tmp_path: Path) -> None:
+ out_dir = tmp_path / "doctor"
+ env = {
+ "OPENVIBECODING_CI_CONTROL_PLANE_DOCTOR_OUT_DIR": str(out_dir),
+ "OPENVIBECODING_DOCTOR_REQUIRE_DOCKER": "0",
+ "OPENVIBECODING_DOCTOR_REQUIRE_SUDO": "0",
+ "RUNNER_TEMP": str(tmp_path / "runner-temp"),
+ "OPENVIBECODING_CI_SOURCE_RUN_ID": "local-run",
+ "OPENVIBECODING_CI_SOURCE_ROUTE": "local_full_ci",
+ "OPENVIBECODING_CI_SOURCE_EVENT": "local",
+ }
+ proc = subprocess.run(
+ ["bash", str(REPO_ROOT / "scripts" / "ci_control_plane_doctor.sh")],
+ cwd=REPO_ROOT,
+ text=True,
+ capture_output=True,
+ env={**os.environ, **env},
+ check=False,
+ )
+
+ assert proc.returncode == 0, proc.stderr or proc.stdout
+ payload = json.loads((out_dir / "report.json").read_text(encoding="utf-8"))
+ assert payload["source_run_id"] == "local-run"
+ assert payload["source_route"] == "local_full_ci"
+ assert payload["source_event"] == "local"
+
+
def test_artifact_index_marks_head_mismatch_non_authoritative(tmp_path: Path) -> None:
route_report = tmp_path / "trusted_pr.json"
route_report.write_text("{}", encoding="utf-8")
diff --git a/configs/env.registry.json b/configs/env.registry.json
index 6cde37a..f92c431 100644
--- a/configs/env.registry.json
+++ b/configs/env.registry.json
@@ -1692,6 +1692,45 @@
"scripts/lib/ci_main_impl.sh"
]
},
+ {
+ "name": "OPENVIBECODING_CI_SOURCE_EVENT",
+ "scope": "ci",
+ "secret": false,
+ "required": false,
+ "default": null,
+ "owner": "platform",
+ "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.",
+ "consumers": [
+ "scripts/ci_control_plane_doctor.sh",
+ "scripts/lib/ci_step126_helpers.sh"
+ ]
+ },
+ {
+ "name": "OPENVIBECODING_CI_SOURCE_ROUTE",
+ "scope": "ci",
+ "secret": false,
+ "required": false,
+ "default": null,
+ "owner": "platform",
+ "description": "Current CI source route identifier propagated into control-plane doctor and current-run provenance receipts so authoritative evidence keeps the originating route contract.",
+ "consumers": [
+ "scripts/ci_control_plane_doctor.sh",
+ "scripts/lib/ci_step126_helpers.sh"
+ ]
+ },
+ {
+ "name": "OPENVIBECODING_CI_SOURCE_RUN_ID",
+ "scope": "ci",
+ "secret": false,
+ "required": false,
+ "default": null,
+ "owner": "platform",
+ "description": "Current CI source run identifier propagated into control-plane doctor and current-run provenance receipts so authoritative evidence keeps the originating run lineage.",
+ "consumers": [
+ "scripts/ci_control_plane_doctor.sh",
+ "scripts/lib/ci_step126_helpers.sh"
+ ]
+ },
{
"name": "OPENVIBECODING_CI_RUNNER_CLASS",
"scope": "ci",
diff --git a/packages/frontend-shared/uiCopy.js b/packages/frontend-shared/uiCopy.js
index f120a36..57d192d 100644
--- a/packages/frontend-shared/uiCopy.js
+++ b/packages/frontend-shared/uiCopy.js
@@ -581,7 +581,7 @@ const UI_COPY = {
queueSummary: (count, slaState) => `queue: ${count} / SLA ${slaState}`,
},
runsPage: {
- title: "证明与回放",
+ title: "Proof & Replay",
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.",
countsBadge: (runCount) => `${runCount} runs`,
warningTitle: "Proof & Replay is currently running with partial truth.",
diff --git a/packages/frontend-shared/uiCopy.ts b/packages/frontend-shared/uiCopy.ts
index 47e2955..efa0cd1 100644
--- a/packages/frontend-shared/uiCopy.ts
+++ b/packages/frontend-shared/uiCopy.ts
@@ -1676,7 +1676,7 @@ const UI_COPY: Record = {
queueSummary: (count: number, slaState: string) => `queue: ${count} / SLA ${slaState}`,
},
runsPage: {
- title: "证明与回放",
+ title: "Proof & Replay",
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.",
countsBadge: (runCount: number) => `${runCount} runs`,
diff --git a/scripts/ci_control_plane_doctor.sh b/scripts/ci_control_plane_doctor.sh
index 827f77a..ca2a08c 100644
--- a/scripts/ci_control_plane_doctor.sh
+++ b/scripts/ci_control_plane_doctor.sh
@@ -46,8 +46,11 @@ if check_cmd curl; then curl_ok=1; fi
if [[ -n "${RUNNER_TEMP:-}" ]]; then runner_temp_ok=1; fi
allowlist_json='["OPENVIBECODING_DOC_GATE_MODE","OPENVIBECODING_DOC_GATE_BASE_SHA","OPENVIBECODING_DOC_GATE_HEAD_SHA","OPENVIBECODING_EXTERNAL_WEB_PROBE_PROVIDER_API_MODE","OPENVIBECODING_CI_LIVE_PREFLIGHT_PROVIDER_API_MODE","OPENVIBECODING_CI_EXTERNAL_WEB_PROBE_PROVIDER_API_MODE"]'
+SOURCE_RUN_ID="${OPENVIBECODING_CI_SOURCE_RUN_ID:-}"
+SOURCE_ROUTE="${OPENVIBECODING_CI_SOURCE_ROUTE:-}"
+SOURCE_EVENT="${OPENVIBECODING_CI_SOURCE_EVENT:-}"
-python3 - "$REPORT_JSON" "$REPORT_MD" "$docker_ok" "$sudo_ok" "$jq_ok" "$curl_ok" "$runner_temp_ok" "$tool_cache_ok" "$allowlist_json" <<'PY'
+python3 - "$REPORT_JSON" "$REPORT_MD" "$docker_ok" "$sudo_ok" "$jq_ok" "$curl_ok" "$runner_temp_ok" "$tool_cache_ok" "$allowlist_json" "$SOURCE_RUN_ID" "$SOURCE_ROUTE" "$SOURCE_EVENT" <<'PY'
import json
import sys
from datetime import datetime, timezone
@@ -63,6 +66,9 @@ from pathlib import Path
runner_temp_ok,
tool_cache_ok,
allowlist_json,
+ source_run_id,
+ source_route,
+ source_event,
) = sys.argv[1:]
payload = {
@@ -77,6 +83,9 @@ payload = {
"tool_cache_contract": tool_cache_ok == "1",
},
"strict_ci_openvibecoding_allowlist": json.loads(allowlist_json),
+ "source_run_id": source_run_id,
+ "source_route": source_route,
+ "source_event": source_event,
}
Path(report_json).write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
Path(report_md).write_text(
@@ -90,6 +99,9 @@ Path(report_md).write_text(
f"- curl: **{payload['checks']['curl']}**",
f"- runner_temp: **{payload['checks']['runner_temp']}**",
f"- tool_cache_contract: **{payload['checks']['tool_cache_contract']}**",
+ f"- source_run_id: `{payload['source_run_id']}`",
+ f"- source_route: `{payload['source_route']}`",
+ f"- source_event: `{payload['source_event']}`",
f"- strict_ci_openvibecoding_allowlist: `{', '.join(payload['strict_ci_openvibecoding_allowlist'])}`",
"",
]
diff --git a/scripts/lib/ci_step126_helpers.sh b/scripts/lib/ci_step126_helpers.sh
index bbadf5c..3b984a2 100644
--- a/scripts/lib/ci_step126_helpers.sh
+++ b/scripts/lib/ci_step126_helpers.sh
@@ -23,6 +23,13 @@ run_ci_step126_current_run_fanin() {
--github-ref "${GITHUB_REF:-local}" \
--github-event-name "${GITHUB_EVENT_NAME:-local}" \
--job-observed "release-evidence"
+ local_runner_temp="${RUNNER_TEMP:-${CI_REPORT_ROOT}/runner_temp}"
+ mkdir -p "${local_runner_temp}"
+ OPENVIBECODING_CI_SOURCE_RUN_ID="${GITHUB_RUN_ID:-local-run}" \
+ OPENVIBECODING_CI_SOURCE_ROUTE="${CI_ROUTE_ID}" \
+ OPENVIBECODING_CI_SOURCE_EVENT="${GITHUB_EVENT_NAME:-local}" \
+ RUNNER_TEMP="${local_runner_temp}" \
+ bash scripts/ci_control_plane_doctor.sh
declare -a CI_SOURCE_MANIFEST_ARGS=(
--output "${CI_SOURCE_MANIFEST_PATH}"
--route-id "${CI_ROUTE_ID}"
diff --git a/scripts/truth_triage.sh b/scripts/truth_triage.sh
index 163e7b2..532136f 100644
--- a/scripts/truth_triage.sh
+++ b/scripts/truth_triage.sh
@@ -7,6 +7,7 @@ cd "$ROOT_DIR"
CURRENT_RUN_ROOT=".runtime-cache/openvibecoding/reports/ci/current_run"
CURRENT_RUN_ROUTE_REPORT=".runtime-cache/openvibecoding/reports/ci/routes/local-advisory.json"
CURRENT_RUN_SOURCE_MANIFEST="${CURRENT_RUN_ROOT}/source_manifest.json"
+CURRENT_RUN_CONSISTENCY_JSON="${CURRENT_RUN_ROOT}/consistency.json"
cleanup_forbidden_python_residue() {
local found=0
@@ -56,6 +57,27 @@ build_local_advisory_current_run_manifest() {
--route-report "$CURRENT_RUN_ROUTE_REPORT"
}
+preserve_authoritative_current_run_manifest() {
+ python3 - <<'PY'
+import json
+from pathlib import Path
+
+manifest = Path(".runtime-cache/openvibecoding/reports/ci/current_run/source_manifest.json")
+consistency = Path(".runtime-cache/openvibecoding/reports/ci/current_run/consistency.json")
+if not manifest.is_file() or not consistency.is_file():
+ raise SystemExit(1)
+
+manifest_payload = json.loads(manifest.read_text(encoding="utf-8"))
+consistency_payload = json.loads(consistency.read_text(encoding="utf-8"))
+is_authoritative = bool(consistency_payload.get("authoritative_current_truth"))
+source_route = str(manifest_payload.get("source_route") or "").strip()
+
+if is_authoritative and source_route and source_route != "local-advisory":
+ raise SystemExit(0)
+raise SystemExit(1)
+PY
+}
+
echo "🧭 [truth-triage] start"
echo
echo "== normalize transient residue =="
@@ -71,7 +93,11 @@ bash scripts/run_governance_py.sh scripts/check_upstream_same_run_cohesion.py
echo
echo "== current-run truth =="
-build_local_advisory_current_run_manifest
+if preserve_authoritative_current_run_manifest; then
+ echo "ℹ️ [truth-triage] preserving existing authoritative current-run manifest"
+else
+ build_local_advisory_current_run_manifest
+fi
bash scripts/run_governance_py.sh scripts/check_ci_current_run_sources.py --source-manifest "$CURRENT_RUN_SOURCE_MANIFEST"
echo