create-principles-disciple 1.145.41 → 1.145.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/codex-adapter/package.json +1 -1
  3. package/console/dist/server/index.js +8 -0
  4. package/console/dist/server/routes/examiner-executor.d.ts +254 -0
  5. package/console/dist/server/routes/examiner-executor.js +622 -0
  6. package/console/dist/server/routes/examiner.d.ts +46 -0
  7. package/console/dist/server/routes/examiner.js +296 -0
  8. package/console/dist/ui/App.js +6 -3
  9. package/console/dist/ui/api.d.ts +138 -0
  10. package/console/dist/ui/api.js +56 -0
  11. package/console/dist/ui/components/layout/app-sidebar.d.ts +11 -0
  12. package/console/dist/ui/components/layout/app-sidebar.js +12 -2
  13. package/console/dist/ui/i18n/en.json +63 -0
  14. package/console/dist/ui/i18n/zh-CN.json +63 -0
  15. package/console/dist/ui/pages/examiner/ExaminerPage.d.ts +15 -0
  16. package/console/dist/ui/pages/examiner/ExaminerPage.js +158 -0
  17. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +2 -1
  18. package/console/dist/ui/utils/runtime-mode.d.ts +10 -0
  19. package/console/dist/ui/utils/runtime-mode.js +10 -0
  20. package/console/dist/web/assets/app.css +44 -0
  21. package/console/dist/web/assets/app.js +33 -31
  22. package/core/dist/host/host-installer.d.ts +13 -0
  23. package/core/dist/host/host-installer.d.ts.map +1 -1
  24. package/core/dist/host/host-installer.js.map +1 -1
  25. package/core/package.json +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +29 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/installer.js +1 -1
  30. package/dist/installer.js.map +1 -1
  31. package/dist/installers/workbuddy-host-installer.d.ts +39 -0
  32. package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
  33. package/dist/installers/workbuddy-host-installer.js +76 -27
  34. package/dist/installers/workbuddy-host-installer.js.map +1 -1
  35. package/package.json +2 -2
  36. package/pd-cli/dist/services/pd-config-loader.d.ts +1 -1
  37. package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -1
  38. package/pd-cli/dist/services/pd-config-loader.js +9 -0
  39. package/pd-cli/dist/services/pd-config-loader.js.map +1 -1
  40. package/pd-cli/package.json +1 -1
  41. package/plugin/openclaw.plugin.json +1 -1
  42. package/plugin/package.json +1 -1
  43. package/release-manager/dist/index.d.ts.map +1 -1
  44. package/release-manager/dist/index.js +29 -0
  45. package/release-manager/dist/index.js.map +1 -1
  46. package/release-manager/dist/installer.js +1 -1
  47. package/release-manager/dist/installer.js.map +1 -1
  48. package/release-manager/dist/installers/workbuddy-host-installer.d.ts +39 -0
  49. package/release-manager/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
  50. package/release-manager/dist/installers/workbuddy-host-installer.js +76 -27
  51. package/release-manager/dist/installers/workbuddy-host-installer.js.map +1 -1
  52. package/release-manager/package.json +2 -2
  53. package/workbuddy-adapter/package.json +1 -1
@@ -2021,6 +2021,68 @@
2021
2021
  "collapseHint": "Click to collapse diff"
2022
2022
  }
2023
2023
  },
2024
+ "examiner": {
2025
+ "loading": "Loading Examiner…",
2026
+ "title": "Examiner Verification",
2027
+ "subtitle": "Controlled A/B behavior-effect verification for a target principle (offline-first).",
2028
+ "scope": {
2029
+ "title": "Execution scope & gates"
2030
+ },
2031
+ "mode": {
2032
+ "full": "Full Library Incremental A/B (default)",
2033
+ "isolated": "Isolated Principle A/B (diagnostic)"
2034
+ },
2035
+ "create": {
2036
+ "title": "Create an offline study",
2037
+ "target": "Target principle (one per study)",
2038
+ "mode": "Experiment mode",
2039
+ "repetitions": "Repetitions per case",
2040
+ "submit": "Create study",
2041
+ "note": "Synthetic calibration cases (SYNTHETIC_OFFLINE gold). Study identities are server-minted and never overwritten."
2042
+ },
2043
+ "studies": {
2044
+ "title": "Studies",
2045
+ "empty": "No studies yet — create an offline study above."
2046
+ },
2047
+ "study": {
2048
+ "title": "Study {{studyId}}",
2049
+ "mode": "Mode",
2050
+ "intervention": "Intervention",
2051
+ "target": "Target",
2052
+ "plannedPairs": "Planned pairs",
2053
+ "run": "Execution",
2054
+ "finished": "finished",
2055
+ "running": "running",
2056
+ "notRun": "not run",
2057
+ "report": "Report",
2058
+ "preflight": "Run preflight",
2059
+ "runOffline": "Run offline"
2060
+ },
2061
+ "preflight": {
2062
+ "status": "Preflight status"
2063
+ },
2064
+ "report": {
2065
+ "title": "Report V2",
2066
+ "target": "Target",
2067
+ "endpoint": "Primary endpoint",
2068
+ "fidelity": "Treatment Fidelity",
2069
+ "contamination": "Control contamination",
2070
+ "evictions": "Capacity evictions",
2071
+ "ittNote": "planned applicable {{planned}} · determined {{determined}} · unknown sides {{unknown}} · NOT_APPLICABLE {{na}} (excluded from the denominator)",
2072
+ "noExaminerBlock": "This report predates the v0.9 contract (no examiner block).",
2073
+ "offlineNote": "Offline-stub results validate the contract pipeline only — never real-host behavior. Real host experiments require separate Owner authorization."
2074
+ },
2075
+ "created": "Study {{studyId}} created.",
2076
+ "runDone": "Offline execution finished: {{count}} units succeeded.",
2077
+ "reportGenerated": "Report generated ({{status}}).",
2078
+ "errors": {
2079
+ "unavailable": "Examiner dev tooling is unavailable in this installation.",
2080
+ "create": "Failed to create the study.",
2081
+ "preflight": "Preflight failed.",
2082
+ "run": "Offline execution failed.",
2083
+ "report": "Report generation failed."
2084
+ }
2085
+ },
2024
2086
  "debt": {
2025
2087
  "eyebrow": "Principle Debt",
2026
2088
  "title": "Principle Debt Management",
@@ -2233,6 +2295,7 @@
2233
2295
  "principles": "Principles Review",
2234
2296
  "activation": "Activation",
2235
2297
  "debt": "Principle Debt",
2298
+ "examiner": "Examiner Verification",
2236
2299
  "controlCenter": "Control Center",
2237
2300
  "reportProblem": "Feedback",
2238
2301
  "failedTasks": "Failed Tasks",
@@ -2021,6 +2021,68 @@
2021
2021
  "collapseHint": "点击折叠差异"
2022
2022
  }
2023
2023
  },
2024
+ "examiner": {
2025
+ "loading": "正在加载 Examiner…",
2026
+ "title": "Examiner 验证",
2027
+ "subtitle": "对目标原则进行受控 A/B 行为效果验证(离线优先)。",
2028
+ "scope": {
2029
+ "title": "执行范围与门禁"
2030
+ },
2031
+ "mode": {
2032
+ "full": "完整原则库增量 A/B(默认)",
2033
+ "isolated": "独立原则 A/B(诊断模式)"
2034
+ },
2035
+ "create": {
2036
+ "title": "创建离线 Study",
2037
+ "target": "目标原则(每次实验一条)",
2038
+ "mode": "实验模式",
2039
+ "repetitions": "每题重复次数",
2040
+ "submit": "创建 Study",
2041
+ "note": "使用合成校准题(SYNTHETIC_OFFLINE Gold)。Study 身份由服务端铸造,创建后不可覆盖。"
2042
+ },
2043
+ "studies": {
2044
+ "title": "研究列表",
2045
+ "empty": "还没有 Study。从上方创建一个离线 Study。"
2046
+ },
2047
+ "study": {
2048
+ "title": "Study {{studyId}}",
2049
+ "mode": "模式",
2050
+ "intervention": "干预类型",
2051
+ "target": "目标原则",
2052
+ "plannedPairs": "计划配对",
2053
+ "run": "执行",
2054
+ "finished": "已完成",
2055
+ "running": "进行中",
2056
+ "notRun": "未执行",
2057
+ "report": "报告",
2058
+ "preflight": "运行预检",
2059
+ "runOffline": "离线执行"
2060
+ },
2061
+ "preflight": {
2062
+ "status": "预检状态"
2063
+ },
2064
+ "report": {
2065
+ "title": "Report V2",
2066
+ "target": "目标",
2067
+ "endpoint": "主要终点",
2068
+ "fidelity": "Treatment Fidelity(注入忠实度)",
2069
+ "contamination": "Control 污染",
2070
+ "evictions": "容量挤出",
2071
+ "ittNote": "计划适用配对 {{planned}} · 完全确定 {{determined}} · 未知侧 {{unknown}} · 不适用 {{na}}(不进入分母)",
2072
+ "noExaminerBlock": "该报告早于 v0.9 契约(无 examiner 区块)。",
2073
+ "offlineNote": "离线 Stub 结果仅验证契约管线,绝不代表真实宿主行为。真实宿主实验需要拥有者单独授权。"
2074
+ },
2075
+ "created": "Study {{studyId}} 已创建。",
2076
+ "runDone": "离线执行完成:{{count}} 个单元成功。",
2077
+ "reportGenerated": "报告已生成({{status}})。",
2078
+ "errors": {
2079
+ "unavailable": "Examiner 开发工具在此安装中不可用。",
2080
+ "create": "创建 Study 失败。",
2081
+ "preflight": "预检失败。",
2082
+ "run": "离线执行失败。",
2083
+ "report": "报告生成失败。"
2084
+ }
2085
+ },
2024
2086
  "debt": {
2025
2087
  "eyebrow": "原则债务",
2026
2088
  "title": "原则债务管理",
@@ -2233,6 +2295,7 @@
2233
2295
  "principles": "原则审查",
2234
2296
  "activation": "生效情况",
2235
2297
  "debt": "原则债务",
2298
+ "examiner": "Examiner 验证",
2236
2299
  "controlCenter": "控制中心",
2237
2300
  "reportProblem": "产品反馈",
2238
2301
  "failedTasks": "失败任务",
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ /**
3
+ * SPEC-PD-EXAMINER-001 v0.9 §8 — Examiner product entry (offline-first).
4
+ *
5
+ * The Owner journey: pick a target principle from the workspace ledger →
6
+ * choose a mode (full-library incremental default / isolated diagnostic) →
7
+ * create the study (server-minted identity) → run the fail-closed preflight →
8
+ * run the OFFLINE-STUB execution → read the Report V2 (ITT + fidelity).
9
+ *
10
+ * Honesty contract: every result is labelled offline-stub / synthetic gold —
11
+ * the console NEVER presents these as real-host behavior; real runs require
12
+ * separate Owner authorization (the gates panel says exactly what is and is
13
+ * not ready).
14
+ */
15
+ export declare function ExaminerPage(): React.ReactElement;
@@ -0,0 +1,158 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback } from "react";
3
+ import { useSearchParams } from "react-router-dom";
4
+ import { useTranslation } from "react-i18next";
5
+ import { toast } from "sonner";
6
+ import { PageShell } from "../../components/layout/page-shell.js";
7
+ import { PageLoading } from "../../components/layout/page-loading.js";
8
+ import { SectionTitle } from "../../components/layout/section-title.js";
9
+ import { Button } from "../../components/ui/button.js";
10
+ import { truncateByCodePoints } from "../../utils/reading-layout.js";
11
+ import { FlaskConical, ShieldCheck, Play, FileText, Loader2 } from "lucide-react";
12
+ import { fetchExaminerOverview, fetchExaminerPrinciples, createExaminerStudy, fetchExaminerStudyStatus, runExaminerPreflight, runExaminerStudy, generateExaminerReport, fetchExaminerReport, } from "../../api.js";
13
+ /**
14
+ * SPEC-PD-EXAMINER-001 v0.9 §8 — Examiner product entry (offline-first).
15
+ *
16
+ * The Owner journey: pick a target principle from the workspace ledger →
17
+ * choose a mode (full-library incremental default / isolated diagnostic) →
18
+ * create the study (server-minted identity) → run the fail-closed preflight →
19
+ * run the OFFLINE-STUB execution → read the Report V2 (ITT + fidelity).
20
+ *
21
+ * Honesty contract: every result is labelled offline-stub / synthetic gold —
22
+ * the console NEVER presents these as real-host behavior; real runs require
23
+ * separate Owner authorization (the gates panel says exactly what is and is
24
+ * not ready).
25
+ */
26
+ export function ExaminerPage() {
27
+ const { t } = useTranslation();
28
+ // §8.1: prefill the target from the principle-detail entry link
29
+ // (?principle=<id>); only an EXACT id match is honored.
30
+ const [searchParams] = useSearchParams();
31
+ const prefillPrinciple = searchParams.get("principle");
32
+ const [overview, setOverview] = useState(null);
33
+ const [principles, setPrinciples] = useState([]);
34
+ const [targetPrincipleId, setTargetPrincipleId] = useState("");
35
+ const [experimentMode, setExperimentMode] = useState("full_library_incremental");
36
+ const [repetitions, setRepetitions] = useState(2);
37
+ const [selectedStudy, setSelectedStudy] = useState(null);
38
+ const [status, setStatus] = useState(null);
39
+ const [preflight, setPreflight] = useState(null);
40
+ const [report, setReport] = useState(null);
41
+ const [busy, setBusy] = useState(null);
42
+ const [error, setError] = useState(null);
43
+ const refreshStatus = useCallback(async (studyId) => {
44
+ const statusResponse = await fetchExaminerStudyStatus(studyId);
45
+ if (!statusResponse.success) {
46
+ setError(statusResponse.error ?? "status unavailable");
47
+ return;
48
+ }
49
+ setStatus(statusResponse.data);
50
+ }, []);
51
+ const selectStudy = useCallback(async (studyId) => {
52
+ setSelectedStudy(studyId);
53
+ setStatus(null);
54
+ setPreflight(null);
55
+ setReport(null);
56
+ await refreshStatus(studyId);
57
+ const reportResponse = await fetchExaminerReport(studyId);
58
+ if (reportResponse.success)
59
+ setReport(reportResponse.data);
60
+ }, [refreshStatus]);
61
+ useEffect(() => {
62
+ let cancelled = false;
63
+ (async () => {
64
+ const overviewResponse = await fetchExaminerOverview();
65
+ if (cancelled)
66
+ return;
67
+ if (!overviewResponse.success) {
68
+ setError(overviewResponse.error ?? t("pages.examiner.errors.unavailable"));
69
+ return;
70
+ }
71
+ setOverview(overviewResponse.data);
72
+ const principlesResponse = await fetchExaminerPrinciples();
73
+ if (!cancelled && principlesResponse.success) {
74
+ const active = principlesResponse.data.principles.filter((p) => p.status === "active");
75
+ setPrinciples(active);
76
+ if (prefillPrinciple !== null) {
77
+ const prefilled = active.find((p) => p.id === prefillPrinciple);
78
+ if (prefilled !== undefined)
79
+ setTargetPrincipleId(prefilled.id);
80
+ else if (active[0] !== undefined)
81
+ setTargetPrincipleId(active[0].id);
82
+ }
83
+ else if (active[0] !== undefined) {
84
+ setTargetPrincipleId(active[0].id);
85
+ }
86
+ }
87
+ })();
88
+ return () => {
89
+ cancelled = true;
90
+ };
91
+ }, [t, prefillPrinciple]);
92
+ const handleCreate = async () => {
93
+ if (!targetPrincipleId)
94
+ return;
95
+ setBusy("create");
96
+ setError(null);
97
+ const response = await createExaminerStudy({ targetPrincipleId, experimentMode, repetitionsPerCase: repetitions });
98
+ setBusy(null);
99
+ if (!response.success) {
100
+ toast.error(response.error ?? t("pages.examiner.errors.create"));
101
+ return;
102
+ }
103
+ toast.success(t("pages.examiner.created", { studyId: response.data.studyId }));
104
+ const overviewResponse = await fetchExaminerOverview();
105
+ if (overviewResponse.success)
106
+ setOverview(overviewResponse.data);
107
+ await selectStudy(response.data.studyId);
108
+ };
109
+ const handlePreflight = async () => {
110
+ if (!selectedStudy)
111
+ return;
112
+ setBusy("preflight");
113
+ const response = await runExaminerPreflight(selectedStudy);
114
+ setBusy(null);
115
+ if (!response.success) {
116
+ toast.error(response.error ?? t("pages.examiner.errors.preflight"));
117
+ return;
118
+ }
119
+ setPreflight(response.data);
120
+ };
121
+ const handleRun = async () => {
122
+ if (!selectedStudy)
123
+ return;
124
+ setBusy("run");
125
+ const response = await runExaminerStudy(selectedStudy);
126
+ setBusy(null);
127
+ if (!response.success) {
128
+ toast.error(response.error ?? t("pages.examiner.errors.run"));
129
+ return;
130
+ }
131
+ toast.success(t("pages.examiner.runDone", { count: response.data.units.filter((u) => u.state === "SUCCEEDED").length }));
132
+ await refreshStatus(selectedStudy);
133
+ };
134
+ const handleReport = async () => {
135
+ if (!selectedStudy)
136
+ return;
137
+ setBusy("report");
138
+ const generated = await generateExaminerReport(selectedStudy);
139
+ setBusy(null);
140
+ if (!generated.success) {
141
+ toast.error(generated.error ?? t("pages.examiner.errors.report"));
142
+ return;
143
+ }
144
+ const fetched = await fetchExaminerReport(selectedStudy);
145
+ if (fetched.success)
146
+ setReport(fetched.data);
147
+ await refreshStatus(selectedStudy);
148
+ toast.success(t("pages.examiner.reportGenerated", { status: generated.data.status }));
149
+ };
150
+ if (!overview && !error)
151
+ return _jsx(PageLoading, { label: t("pages.examiner.loading") });
152
+ return (_jsxs(PageShell, { children: [_jsxs("header", { className: "mb-6", children: [_jsx("h1", { className: "text-[22px] font-semibold text-ink", "data-testid": "examiner-page-title", children: t("pages.examiner.title") }), _jsx("p", { className: "mt-1 text-[14px] text-ink-3", children: t("pages.examiner.subtitle") })] }), error && (_jsx("div", { className: "rounded-lg border border-destructive/40 bg-destructive/10 p-4 text-sm text-destructive", "data-testid": "examiner-error", children: error })), overview && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rounded-lg border bg-card p-4 text-sm", "data-testid": "examiner-scope", children: [_jsxs("div", { className: "flex items-center gap-2 font-medium", children: [_jsx(ShieldCheck, { className: "size-4" }), t("pages.examiner.scope.title")] }), _jsx("p", { className: "mt-1 text-muted-foreground", children: overview.executionScope }), _jsx("div", { className: "mt-3 grid gap-1 text-xs text-muted-foreground", children: Object.entries(overview.gates).map(([gate, state]) => (_jsxs("div", { "data-testid": `examiner-gate-${gate}`, children: [_jsx("span", { className: "font-mono", children: gate }), ": ", state] }, gate))) })] }), _jsx(SectionTitle, { children: t("pages.examiner.create.title") }), _jsxs("div", { className: "grid gap-3 rounded-lg border bg-card p-4", "data-testid": "examiner-create", children: [_jsxs("label", { className: "text-sm", children: [_jsx("span", { className: "mb-1 block font-medium", children: t("pages.examiner.create.target") }), _jsx("select", { className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: targetPrincipleId, onChange: (e) => setTargetPrincipleId(e.target.value), "data-testid": "examiner-target-select", children: principles.map((p) => (_jsxs("option", { value: p.id, children: [p.id, " \u2014 ", truncateByCodePoints(p.text, 60)] }, p.id))) })] }), _jsxs("label", { className: "text-sm", children: [_jsx("span", { className: "mb-1 block font-medium", children: t("pages.examiner.create.mode") }), _jsxs("select", { className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: experimentMode, onChange: (e) => setExperimentMode(e.target.value), "data-testid": "examiner-mode-select", children: [_jsx("option", { value: "full_library_incremental", children: t("pages.examiner.mode.full") }), _jsx("option", { value: "isolated_principle", children: t("pages.examiner.mode.isolated") })] })] }), _jsxs("label", { className: "text-sm", children: [_jsx("span", { className: "mb-1 block font-medium", children: t("pages.examiner.create.repetitions") }), _jsx("select", { className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: repetitions, onChange: (e) => setRepetitions(Number(e.target.value)), children: [1, 2, 3, 4, 5].map((n) => (_jsx("option", { value: n, children: n }, n))) })] }), _jsxs(Button, { onClick: handleCreate, disabled: !targetPrincipleId || busy === "create", "data-testid": "examiner-create-button", children: [busy === "create" ? _jsx(Loader2, { className: "size-4 animate-spin" }) : _jsx(FlaskConical, { className: "size-4" }), t("pages.examiner.create.submit")] }), _jsx("p", { className: "text-xs text-muted-foreground", children: t("pages.examiner.create.note") })] }), _jsx(SectionTitle, { children: t("pages.examiner.studies.title") }), _jsxs("div", { className: "grid gap-2", "data-testid": "examiner-study-list", children: [overview.studies.length === 0 && (_jsx("p", { className: "text-sm text-muted-foreground", children: t("pages.examiner.studies.empty") })), overview.studies.map((studyId) => (_jsx("button", { className: `rounded-md border px-3 py-2 text-left font-mono text-xs ${selectedStudy === studyId ? "border-primary bg-primary/10" : "bg-card hover:bg-accent"}`, onClick: () => selectStudy(studyId), "data-testid": `examiner-study-${studyId}`, children: studyId }, studyId)))] }), selectedStudy && status && (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { children: t("pages.examiner.study.title", { studyId: selectedStudy }) }), _jsxs("div", { className: "grid gap-3 rounded-lg border bg-card p-4 text-sm", "data-testid": "examiner-study-detail", children: [_jsxs("div", { className: "grid grid-cols-2 gap-2 text-xs", children: [_jsxs("div", { children: [t("pages.examiner.study.mode"), ": ", _jsx("span", { className: "font-mono", children: status.experimentMode })] }), _jsxs("div", { children: [t("pages.examiner.study.intervention"), ": ", _jsx("span", { className: "font-mono", children: status.interventionType })] }), _jsxs("div", { children: [t("pages.examiner.study.target"), ": ", _jsx("span", { className: "font-mono", children: status.targetPrincipleId })] }), _jsxs("div", { children: [t("pages.examiner.study.plannedPairs"), ": ", status.plannedPairs] }), _jsxs("div", { children: [t("pages.examiner.study.run"), ": ", status.runStarted ? (status.runFinished ? t("pages.examiner.study.finished") : t("pages.examiner.study.running")) : t("pages.examiner.study.notRun")] }), _jsxs("div", { children: [t("pages.examiner.study.report"), ": ", _jsx("span", { className: "font-mono", children: status.reportStatus ?? "—" })] })] }), Object.keys(status.unitStates).length > 0 && (_jsx("div", { className: "text-xs text-muted-foreground", "data-testid": "examiner-unit-states", children: Object.entries(status.unitStates).map(([state, count]) => `${state}: ${count}`).join(" · ") })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: handlePreflight, disabled: busy === "preflight", "data-testid": "examiner-preflight-button", children: [busy === "preflight" ? _jsx(Loader2, { className: "size-4 animate-spin" }) : _jsx(ShieldCheck, { className: "size-4" }), t("pages.examiner.study.preflight")] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleRun, disabled: busy === "run", "data-testid": "examiner-run-button", children: [busy === "run" ? _jsx(Loader2, { className: "size-4 animate-spin" }) : _jsx(Play, { className: "size-4" }), t("pages.examiner.study.runOffline")] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleReport, disabled: busy === "report", "data-testid": "examiner-report-button", children: [busy === "report" ? _jsx(Loader2, { className: "size-4 animate-spin" }) : _jsx(FileText, { className: "size-4" }), t("pages.examiner.study.report")] })] }), preflight && (_jsxs("div", { className: "rounded-md border p-3 text-xs", "data-testid": "examiner-preflight-result", children: [_jsxs("div", { className: "font-medium", children: [t("pages.examiner.preflight.status"), ": ", preflight.status] }), preflight.blockers.length > 0 && (_jsx("div", { className: "mt-2 text-destructive", children: preflight.blockers.map((b) => (_jsxs("div", { children: ["\u26D4 ", b.id, ": ", b.detail] }, b.id))) })), preflight.warnings.length > 0 && (_jsx("div", { className: "mt-2 text-amber-600 dark:text-amber-400", children: preflight.warnings.map((w) => (_jsxs("div", { children: ["\u26A0 ", w.id, ": ", w.detail] }, w.id))) }))] }))] })] })), report && report.report && (_jsxs(_Fragment, { children: [_jsx(SectionTitle, { children: t("pages.examiner.report.title") }), _jsxs("div", { className: "grid gap-3 rounded-lg border bg-card p-4 text-sm", "data-testid": "examiner-report", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "rounded bg-muted px-2 py-0.5 font-mono text-xs", children: report.report.status }), report.report.examiner && (_jsx("span", { className: "rounded bg-muted px-2 py-0.5 font-mono text-xs", children: report.report.examiner.experimentMode }))] }), report.report.examiner ? (_jsxs("div", { className: "grid gap-3", children: [_jsxs("div", { className: "text-xs text-muted-foreground", children: [t("pages.examiner.report.target"), ": ", _jsx("span", { className: "font-mono", children: report.report.examiner.targetPrincipleId }), " \u00B7 ", t("pages.examiner.report.endpoint"), ": ", _jsx("span", { className: "font-mono", children: report.report.examiner.primaryEndpoint?.id })] }), _jsxs("div", { className: "rounded-md border p-3", "data-testid": "examiner-report-itt", children: [_jsx("div", { className: "font-medium", children: "ITT" }), report.report.examiner.itt.pointEstimate.status === "ESTIMABLE" ? (_jsx("div", { className: "text-2xl font-bold", children: report.report.examiner.itt.pointEstimate.value })) : (_jsxs("div", { className: "font-mono text-sm", children: ["[", report.report.examiner.itt.lowerBound.value ?? "n/a", ", ", report.report.examiner.itt.upperBound.value ?? "n/a", "]"] })), _jsx("div", { className: "text-xs text-muted-foreground", children: t("pages.examiner.report.ittNote", {
153
+ planned: report.report.examiner.itt.plannedApplicablePairs,
154
+ determined: report.report.examiner.itt.determinedPairs,
155
+ unknown: report.report.examiner.itt.unknownPairs,
156
+ na: report.report.examiner.applicability.notApplicablePairs,
157
+ }) })] }), _jsxs("div", { className: "rounded-md border p-3", "data-testid": "examiner-report-fidelity", children: [_jsx("div", { className: "font-medium", children: t("pages.examiner.report.fidelity") }), _jsxs("div", { className: "text-xs", children: ["CONTEXT_PRESENT+ ", report.report.examiner.treatmentFidelity.contextPresent, "/", report.report.examiner.treatmentFidelity.plannedTreatmentUnits, " \u00B7 RUNTIME_LOADED ", report.report.examiner.treatmentFidelity.runtimeLoaded, " \u00B7 ", t("pages.examiner.report.contamination"), " ", report.report.examiner.treatmentFidelity.controlContamination.units, " \u00B7 ", t("pages.examiner.report.evictions"), " ", report.report.examiner.treatmentFidelity.capacityEvictions.units] })] })] })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: t("pages.examiner.report.noExaminerBlock") })), _jsx("p", { className: "text-xs text-muted-foreground", children: t("pages.examiner.report.offlineNote") })] })] }))] }))] }));
158
+ }
@@ -14,6 +14,7 @@ import { formatDate } from "../../utils/format-date.js";
14
14
  import { localizeAggregatedBlocker } from "../../utils/blocker-localization.js";
15
15
  import { groupByReasonCode, formatGroupedReasonLine } from "../../utils/attention-presentation.js";
16
16
  import { splitReadingTitleBody, truncateByCodePoints } from "../../utils/reading-layout.js";
17
+ import { IS_DEV } from "../../utils/runtime-mode.js";
17
18
  // ── Runtime validation (H section) ──────────────────────────────────────────
18
19
  function isString(v) {
19
20
  return typeof v === "string";
@@ -479,7 +480,7 @@ export function PrincipleDetailPage() {
479
480
  const learnedPreview = learnedText === undefined ? undefined : truncateByCodePoints(learnedText, 240);
480
481
  const learnedTruncated = learnedPreview !== learnedText;
481
482
  const headlineLayout = splitReadingTitleBody(learnedText ?? principle.text);
482
- return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate(returnTo), className: "mb-4 -ml-2", children: ["\u2190 ", t("principles.detail.backToList")] }), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.conclusion") }), _jsx("h1", { className: "text-[22px] font-semibold text-ink leading-snug mb-3", "data-testid": "principle-detail-title", children: headlineLayout.title }), headlineLayout.body !== null && !learnedKnown && (_jsx("div", { className: "mb-3 text-[15px] leading-relaxed text-ink whitespace-pre-wrap break-words", "data-testid": "principle-detail-body", children: headlineLayout.body })), ownerDecision !== null && ownerDecision.learnedPrinciple.status !== 'known' && (_jsx("p", { className: "text-amber text-[13px] leading-relaxed mb-3", "data-testid": "owner-decision-technical-note", children: t("principles.detail.ownerDecision.technicalOriginalNote", {
483
+ return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between", children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate(returnTo), className: "-ml-2", children: ["\u2190 ", t("principles.detail.backToList")] }), IS_DEV && (_jsx(Link, { to: `/examiner?principle=${encodeURIComponent(String(id ?? ''))}`, className: "text-[13px] text-ink-3 underline decoration-dotted underline-offset-4 hover:text-ink", "data-testid": "principle-detail-examiner-link", children: t("principles.detail.examinerEntry", { defaultValue: "验证此原则的行为效果 (Examiner)" }) }))] }), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.conclusion") }), _jsx("h1", { className: "text-[22px] font-semibold text-ink leading-snug mb-3", "data-testid": "principle-detail-title", children: headlineLayout.title }), headlineLayout.body !== null && !learnedKnown && (_jsx("div", { className: "mb-3 text-[15px] leading-relaxed text-ink whitespace-pre-wrap break-words", "data-testid": "principle-detail-body", children: headlineLayout.body })), ownerDecision !== null && ownerDecision.learnedPrinciple.status !== 'known' && (_jsx("p", { className: "text-amber text-[13px] leading-relaxed mb-3", "data-testid": "owner-decision-technical-note", children: t("principles.detail.ownerDecision.technicalOriginalNote", {
483
484
  defaultValue: "以上为原始技术建议文本;可读的行为准则暂缺(保留原文以保证可追溯)。",
484
485
  }) })), _jsx("p", { className: "text-ink-3 text-[14px] leading-relaxed mb-3", children: t("principles.detail.policyNote") }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-ink-4 text-[13px]", children: t("principles.detail.modifyWordingNote") }), _jsx(Button, { variant: "ghost", size: "sm", className: "ml-auto", "data-testid": "principle-feedback", onClick: () => {
485
486
  const params = new URLSearchParams({ source: "principle_page", principleId: id ?? "" });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build-time runtime mode.
3
+ *
4
+ * Vite replaces `import.meta.env.DEV` with a boolean literal at build time, so
5
+ * a production bundle resolves this to `false` and dead-code elimination can
6
+ * drop the DEV-only surfaces from the shipped UI. This is a trust boundary in
7
+ * the CLIENT only — the server independently refuses the Examiner API outside
8
+ * a dev runtime (see `examinerDevEnabled` in the examiner route).
9
+ */
10
+ export declare const IS_DEV: boolean;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build-time runtime mode.
3
+ *
4
+ * Vite replaces `import.meta.env.DEV` with a boolean literal at build time, so
5
+ * a production bundle resolves this to `false` and dead-code elimination can
6
+ * drop the DEV-only surfaces from the shipped UI. This is a trust boundary in
7
+ * the CLIENT only — the server independently refuses the Examiner API outside
8
+ * a dev runtime (see `examinerDevEnabled` in the examiner route).
9
+ */
10
+ export const IS_DEV = import.meta.env.DEV;
@@ -41,6 +41,8 @@
41
41
  --text-base--line-height: calc(1.5 / 1);
42
42
  --text-lg: 1.125rem;
43
43
  --text-lg--line-height: calc(1.75 / 1.125);
44
+ --text-2xl: 1.5rem;
45
+ --text-2xl--line-height: calc(2 / 1.5);
44
46
  --font-weight-normal: 400;
45
47
  --font-weight-medium: 500;
46
48
  --font-weight-semibold: 600;
@@ -79,6 +81,7 @@
79
81
  --color-danger: #8b3a3a;
80
82
  --color-background: var(--color-paper);
81
83
  --color-foreground: var(--color-ink);
84
+ --color-card: var(--color-surface);
82
85
  --color-popover: var(--color-panel);
83
86
  --color-popover-foreground: var(--color-ink);
84
87
  --color-primary: var(--color-gov);
@@ -566,6 +569,10 @@
566
569
  .table {
567
570
  display: table;
568
571
  }
572
+ .size-4 {
573
+ width: calc(var(--spacing) * 4);
574
+ height: calc(var(--spacing) * 4);
575
+ }
569
576
  .h-1 {
570
577
  height: var(--spacing);
571
578
  }
@@ -1185,6 +1192,9 @@
1185
1192
  .rounded-full {
1186
1193
  border-radius: calc(infinity * 1px);
1187
1194
  }
1195
+ .rounded-lg {
1196
+ border-radius: var(--radius-lg);
1197
+ }
1188
1198
  .rounded-md {
1189
1199
  border-radius: var(--radius-md);
1190
1200
  }
@@ -1341,6 +1351,12 @@
1341
1351
  border-color: color-mix(in oklab, var(--color-danger) 50%, transparent);
1342
1352
  }
1343
1353
  }
1354
+ .border-destructive\/40 {
1355
+ border-color: color-mix(in srgb, #8b3a3a 40%, transparent);
1356
+ @supports (color: color-mix(in lab, red, red)) {
1357
+ border-color: color-mix(in oklab, var(--color-destructive) 40%, transparent);
1358
+ }
1359
+ }
1344
1360
  .border-destructive\/50 {
1345
1361
  border-color: color-mix(in srgb, #8b3a3a 50%, transparent);
1346
1362
  @supports (color: color-mix(in lab, red, red)) {
@@ -1449,6 +1465,9 @@
1449
1465
  .border-paper {
1450
1466
  border-color: var(--color-paper);
1451
1467
  }
1468
+ .border-primary {
1469
+ border-color: var(--color-primary);
1470
+ }
1452
1471
  .border-red-200 {
1453
1472
  border-color: var(--color-red-200);
1454
1473
  }
@@ -1533,6 +1552,9 @@
1533
1552
  .bg-border {
1534
1553
  background-color: var(--color-border);
1535
1554
  }
1555
+ .bg-card {
1556
+ background-color: var(--color-card);
1557
+ }
1536
1558
  .bg-danger {
1537
1559
  background-color: var(--color-danger);
1538
1560
  }
@@ -1551,6 +1573,12 @@
1551
1573
  .bg-destructive {
1552
1574
  background-color: var(--color-destructive);
1553
1575
  }
1576
+ .bg-destructive\/10 {
1577
+ background-color: color-mix(in srgb, #8b3a3a 10%, transparent);
1578
+ @supports (color: color-mix(in lab, red, red)) {
1579
+ background-color: color-mix(in oklab, var(--color-destructive) 10%, transparent);
1580
+ }
1581
+ }
1554
1582
  .bg-gov {
1555
1583
  background-color: var(--color-gov);
1556
1584
  }
@@ -1659,6 +1687,12 @@
1659
1687
  .bg-primary {
1660
1688
  background-color: var(--color-primary);
1661
1689
  }
1690
+ .bg-primary\/10 {
1691
+ background-color: color-mix(in srgb, #1e3a5f 10%, transparent);
1692
+ @supports (color: color-mix(in lab, red, red)) {
1693
+ background-color: color-mix(in oklab, var(--color-primary) 10%, transparent);
1694
+ }
1695
+ }
1662
1696
  .bg-primary\/70 {
1663
1697
  background-color: color-mix(in srgb, #1e3a5f 70%, transparent);
1664
1698
  @supports (color: color-mix(in lab, red, red)) {
@@ -1968,6 +2002,10 @@
1968
2002
  .font-mono {
1969
2003
  font-family: var(--font-mono);
1970
2004
  }
2005
+ .text-2xl {
2006
+ font-size: var(--text-2xl);
2007
+ line-height: var(--tw-leading, var(--text-2xl--line-height));
2008
+ }
1971
2009
  .text-base {
1972
2010
  font-size: var(--text-base);
1973
2011
  line-height: var(--tw-leading, var(--text-base--line-height));
@@ -2269,6 +2307,9 @@
2269
2307
  .underline {
2270
2308
  text-decoration-line: underline;
2271
2309
  }
2310
+ .decoration-dotted {
2311
+ text-decoration-style: dotted;
2312
+ }
2272
2313
  .underline-offset-2 {
2273
2314
  text-underline-offset: 2px;
2274
2315
  }
@@ -2570,6 +2611,9 @@
2570
2611
  .hover\:border-line-2:hover {
2571
2612
  border-color: var(--color-line-2);
2572
2613
  }
2614
+ .hover\:bg-accent:hover {
2615
+ background-color: var(--color-accent);
2616
+ }
2573
2617
  .hover\:bg-danger\/5:hover {
2574
2618
  background-color: color-mix(in srgb, #8b3a3a 5%, transparent);
2575
2619
  }