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
@@ -0,0 +1,296 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { sendSuccess, sendError, sendNotFound } from '../utils/response.js';
4
+ import { PrinciplesConsoleModel } from '../models/PrinciplesConsoleModel.js';
5
+ import { loadLabModules, loadHostCapability, preflightCheck, offlineStudyPass, reportMaterialize, listStudyNames, resolveStudyAccess, resolveStudyRoots, studyStatusPayload, studyReportPayload, materializeConsoleStudy, verifyConsoleOwnership, writableRootProblem, isJsonObject, ExaminerPreflightBlockedError, } from './examiner-executor.js';
6
+ /* eslint-disable @typescript-eslint/max-params -- node:http handler signature (req, res, workspaceDir, subPath) matches every other route */
7
+ const here = path.dirname(fileURLToPath(import.meta.url));
8
+ const repoRoot = path.resolve(here, '..', '..', '..', '..', '..');
9
+ const labRoot = path.join(repoRoot, 'scripts', 'dev', 'principle-learning-lab');
10
+ const PRINCIPLE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
11
+ const STUDY_ROUTE_PATTERN = /^\/studies\/([A-Za-z0-9-]+)(\/[^/]*)?$/;
12
+ /**
13
+ * P2-1: the Examiner is a DEV-ONLY capability. The gate is SERVER-SIDE and
14
+ * cannot be set by a client — it reads only the running module's own URL and
15
+ * the operator's environment: a source run (tsx/vitest, import.meta.url ends
16
+ * with .ts) or an explicit `PD_EXAMINER_DEV=1`. `NODE_ENV=production` is an
17
+ * UNCONDITIONAL hard gate: not even the DEV override can enable the surface
18
+ * there, so a production build (`dist/server.js`) answers 404 — never 503.
19
+ */
20
+ export function isExaminerDevRuntime(moduleUrl, env = process.env) {
21
+ if (env.NODE_ENV === 'production')
22
+ return false;
23
+ if (env.PD_EXAMINER_DEV === '1')
24
+ return true;
25
+ return moduleUrl.endsWith('.ts');
26
+ }
27
+ function examinerDevEnabled() {
28
+ return isExaminerDevRuntime(import.meta.url);
29
+ }
30
+ /** The runner's lock-time re-verification refusal (dynamic module: match the
31
+ * contract name, not an instanceof across import boundaries). */
32
+ function isFrozenStudyDriftError(error) {
33
+ return error instanceof Error && error.name === 'FrozenStudyDriftError';
34
+ }
35
+ /**
36
+ * Drift details are CONTRACT output (check names + case ids), but they travel
37
+ * into an HTTP payload: scrub anything path-shaped and bound the size so a
38
+ * refusal never leaks filesystem internals or parser text.
39
+ */
40
+ function driftProblems(details) {
41
+ if (typeof details !== 'string')
42
+ return [];
43
+ return details
44
+ .split(';')
45
+ .map((part) => part.trim())
46
+ .filter(Boolean)
47
+ .map((part) => part
48
+ .replace(/[A-Za-z]:[\\/][^\s;]*/g, '<path>')
49
+ .replace(/(^|[\s(=])\/[^\s;()]+/g, '$1<path>')
50
+ .slice(0, 240))
51
+ .slice(0, 20);
52
+ }
53
+ /**
54
+ * Responses carry stable reason codes, never raw exception text: a thrown
55
+ * message can embed absolute paths and other internals that are not
56
+ * Owner-facing information. The full error stays in the server log.
57
+ */
58
+ function logRefusal(scope, error) {
59
+ console.error(`[pd-console] examiner ${scope}:`, error);
60
+ }
61
+ let labModules = null;
62
+ let hostCapability = null;
63
+ let labUnavailableReason = null;
64
+ async function loadLab() {
65
+ if (hostCapability)
66
+ return hostCapability;
67
+ try {
68
+ labModules = await loadLabModules(labRoot);
69
+ hostCapability = await loadHostCapability(labRoot);
70
+ return hostCapability;
71
+ }
72
+ catch (error) {
73
+ logRefusal('lab unavailable', error);
74
+ labUnavailableReason = 'lab tooling could not be loaded (details in the console server log)';
75
+ labModules = null;
76
+ hostCapability = null;
77
+ return null;
78
+ }
79
+ }
80
+ function readBody(req) {
81
+ return new Promise((resolve) => {
82
+ let raw = '';
83
+ req.on('data', (chunk) => {
84
+ raw += String(chunk);
85
+ });
86
+ req.on('end', () => {
87
+ if (!raw) {
88
+ resolve({});
89
+ return;
90
+ }
91
+ try {
92
+ const parsed = JSON.parse(raw);
93
+ resolve(isJsonObject(parsed) ? parsed : {});
94
+ }
95
+ catch {
96
+ resolve({});
97
+ }
98
+ });
99
+ });
100
+ }
101
+ export async function handleExaminerRoute(req, res, workspaceDir, subPath) {
102
+ if (!examinerDevEnabled()) {
103
+ sendError(res, 404, 'examiner_not_enabled', 'The Examiner research surface is a local-dev capability and is not enabled in this runtime');
104
+ return;
105
+ }
106
+ const lab = await loadLab();
107
+ if (!lab || !labModules) {
108
+ sendError(res, 503, 'examiner_dev_tooling_unavailable', `Examiner dev tooling is not available in this installation (the principle-learning-lab lives in the repository's scripts/dev area): ${labUnavailableReason ?? 'unknown reason'}`);
109
+ return;
110
+ }
111
+ const method = req.method ?? 'GET';
112
+ const roots = resolveStudyRoots(labRoot);
113
+ const context = { labRoot, roots };
114
+ const rootProblem = writableRootProblem(roots);
115
+ // GET /overview
116
+ if (subPath === '/overview' && method === 'GET') {
117
+ const writable = listStudyNames(roots.writable);
118
+ const historical = listStudyNames(roots.historical);
119
+ sendSuccess(res, {
120
+ spec: 'SPEC-PD-EXAMINER-001 v0.9',
121
+ executionScope: 'offline-stub only — real model calls and real host runs are NOT authorized this round',
122
+ gates: {
123
+ gate1_challenge_trust: 'BLOCKED — independent human Gold review has not been performed for any Examiner suite (code tests can never substitute)',
124
+ gate2_behavior_evaluation: 'NOT_READY — requires Gate 1 + real-host authorization',
125
+ gate3_llm_authoring: 'NOT_STARTED',
126
+ gate4_product_integration: 'NOT_STARTED (local-dev console integration; the production UI/API is disabled)',
127
+ },
128
+ hostInjectionCapability: lab,
129
+ fullConclusionMinimumLevel: 'CONTEXT_PRESENT',
130
+ writableStudiesRoot: roots.writable,
131
+ historicalStudiesRoot: roots.historical,
132
+ rootProblem,
133
+ studies: [...writable, ...historical.filter((name) => !writable.includes(name))],
134
+ readOnlyStudies: [
135
+ ...historical,
136
+ ...writable.filter((name) => !verifyConsoleOwnership(roots.writable, path.join(roots.writable, name)).ok),
137
+ ],
138
+ });
139
+ return;
140
+ }
141
+ // GET /principles — real workspace principles as study inputs (read-only)
142
+ if (subPath === '/principles' && method === 'GET') {
143
+ const model = new PrinciplesConsoleModel(workspaceDir);
144
+ const list = await model.listPrinciples();
145
+ const principles = (list.principles ?? []).map((p) => ({ id: p.id, version: 'ledger', text: p.text, status: p.status, ruleCount: p.ruleCount ?? 0 }));
146
+ sendSuccess(res, { principles, note: 'study inputs are read from the workspace ledger (read-only); eligibility filtering happens inside the Examiner contract' });
147
+ return;
148
+ }
149
+ // POST /studies — create an offline examiner study (identity server-minted)
150
+ if (subPath === '/studies' && method === 'POST') {
151
+ if (rootProblem) {
152
+ sendError(res, 409, 'writable_root_not_isolated', rootProblem);
153
+ return;
154
+ }
155
+ const body = await readBody(req);
156
+ const requestedTarget = typeof body.targetPrincipleId === 'string' ? body.targetPrincipleId.trim() : '';
157
+ const requestedMode = typeof body.experimentMode === 'string' ? body.experimentMode : '';
158
+ const repetitionsRaw = Number(body.repetitionsPerCase);
159
+ const repetitionsPerCase = Number.isInteger(repetitionsRaw) && repetitionsRaw >= 1 && repetitionsRaw <= 5 ? repetitionsRaw : 2;
160
+ if (!PRINCIPLE_ID_PATTERN.test(requestedTarget)) {
161
+ sendError(res, 400, 'invalid_target_principle', 'targetPrincipleId required (charset-validated principle id; one target Principle per study)');
162
+ return;
163
+ }
164
+ const model = new PrinciplesConsoleModel(workspaceDir);
165
+ const list = await model.listPrinciples();
166
+ const ledgerPrinciples = (list.principles ?? []).map((p) => ({ id: p.id, version: 'ledger', text: p.text, status: p.status, ruleCount: p.ruleCount ?? 0 }));
167
+ // The request only SELECTS: the target travels onward as an INDEX into
168
+ // the server's own ledger list (exact-match required) and the mode as a
169
+ // BOOLEAN; the study identity/directory/seed are minted server-side. No
170
+ // request string crosses into the research pipeline.
171
+ const targetIndex = ledgerPrinciples.findIndex((p) => p.id === requestedTarget);
172
+ if (targetIndex < 0) {
173
+ sendError(res, 404, 'target_principle_not_found', 'the target principle was not found in the workspace ledger');
174
+ return;
175
+ }
176
+ const isolatedMode = requestedMode === 'isolated_principle';
177
+ try {
178
+ const created = await materializeConsoleStudy(labModules, labRoot, roots, {
179
+ isolatedMode,
180
+ targetIndex,
181
+ ledgerPrinciples,
182
+ repetitionsPerCase,
183
+ });
184
+ sendSuccess(res, {
185
+ studyId: created.studyId,
186
+ targetPrincipleId: created.targetPrincipleId,
187
+ experimentMode: isolatedMode ? 'isolated_principle' : 'full_library_incremental',
188
+ plannedPairs: created.plannedPairs,
189
+ manifestHash: created.manifestHash,
190
+ note: 'offline study created with SYNTHETIC gold — run preflight, then the offline-stub execution; results demonstrate the contract pipeline, never real host behavior',
191
+ });
192
+ }
193
+ catch (error) {
194
+ logRefusal('study creation refused', error);
195
+ sendError(res, 422, 'study_creation_refused', 'the study was refused before any file was written (details in the console server log)');
196
+ }
197
+ return;
198
+ }
199
+ // /studies/:studyId[/...]
200
+ const studyMatch = STUDY_ROUTE_PATTERN.exec(subPath);
201
+ if (studyMatch && studyMatch[1]) {
202
+ const rest = studyMatch[2] ?? '';
203
+ const access = resolveStudyAccess(roots, studyMatch[1]);
204
+ if (!access) {
205
+ sendError(res, 404, 'study_not_found', 'study not found');
206
+ return;
207
+ }
208
+ const studyDir = access.dir;
209
+ // GET status
210
+ if (rest === '' && method === 'GET') {
211
+ sendSuccess(res, { ...studyStatusPayload(studyDir), readOnly: access.readOnly, access: access.reason });
212
+ return;
213
+ }
214
+ // POST /preflight — the fail-closed gate over the frozen study facts
215
+ if (rest === '/preflight' && method === 'POST') {
216
+ const preflight = preflightCheck(labModules, studyDir, context);
217
+ sendSuccess(res, { studyId: access.studyId, ok: preflight.ok, status: preflight.status, blockers: preflight.blockers, warnings: preflight.warnings, checks: preflight.checks });
218
+ return;
219
+ }
220
+ // POST /run — offline-stub only; real modes are refused at THIS boundary.
221
+ // P1-1/P1-4: the study must be server-owned and writable, and the SERVER
222
+ // preflight must pass BEFORE the runner is entered. A blocker returns 409
223
+ // with no RUN_STARTED event, no reservation and no actor creation.
224
+ if (rest === '/run' && method === 'POST') {
225
+ const body = await readBody(req);
226
+ // Real execution needs an Owner-approved run-authorization.json plus the
227
+ // Stage R gate — this console route NEVER transports a caller-supplied
228
+ // mode into the runner; only the offline-stub default is reachable.
229
+ if (body.mode !== undefined && body.mode !== 'offline-stub') {
230
+ sendError(res, 403, 'real_execution_not_authorized', 'non-offline execution requires explicit Owner authorization (run-authorization.json + Stage R gate) — NOT GRANTED this round');
231
+ return;
232
+ }
233
+ if (access.readOnly) {
234
+ sendError(res, 403, 'study_read_only', `this study cannot be executed from the console: ${access.reason}`);
235
+ return;
236
+ }
237
+ try {
238
+ const summary = await offlineStudyPass(labModules, studyDir, context);
239
+ sendSuccess(res, { studyId: access.studyId, units: summary.units.map((unit) => ({ unit: unit.unit, state: unit.state })) });
240
+ }
241
+ catch (error) {
242
+ // P1-4: both gates answer the same 409 shape. The server preflight
243
+ // refuses BEFORE RUN_STARTED; the runner re-verifies UNDER the study
244
+ // lock and refuses on drift discovered after that first check. Either
245
+ // way: no RUN_STARTED, no reservation, no actor, no attempt file.
246
+ if (error instanceof ExaminerPreflightBlockedError) {
247
+ sendError(res, 409, 'preflight_blocked', 'offline preflight blocked; no run started, no unit reserved, no actor created', {
248
+ blockers: error.blockers,
249
+ });
250
+ return;
251
+ }
252
+ if (isFrozenStudyDriftError(error)) {
253
+ sendError(res, 409, 'preflight_blocked', 'the frozen study drifted after preflight; no run started, no unit reserved, no actor created', {
254
+ blockers: [{ id: 'frozen_study_drift', problems: driftProblems(error.details) }],
255
+ });
256
+ return;
257
+ }
258
+ logRefusal('offline run failed', error);
259
+ sendError(res, 500, 'offline_run_failed', 'the offline run failed (details in the console server log)');
260
+ }
261
+ return;
262
+ }
263
+ // GET /report — read-only (historical studies stay readable)
264
+ if (rest === '/report' && method === 'GET') {
265
+ const payload = studyReportPayload(labModules, studyDir);
266
+ if (!payload) {
267
+ sendError(res, 404, 'report_not_generated', 'no Report V2 yet — run the offline execution first, then generate the report (the console never auto-reruns or rewrites history)');
268
+ return;
269
+ }
270
+ sendSuccess(res, { ...payload, readOnly: access.readOnly });
271
+ return;
272
+ }
273
+ // POST /report — generate (idempotent; refuses to overwrite a READY report)
274
+ if (rest === '/report' && method === 'POST') {
275
+ if (access.readOnly) {
276
+ sendError(res, 403, 'study_read_only', `this study cannot be written from the console: ${access.reason}`);
277
+ return;
278
+ }
279
+ try {
280
+ const result = reportMaterialize(labModules, studyDir, new Date().toISOString(), context, () => {
281
+ const check = verifyConsoleOwnership(roots.writable, studyDir);
282
+ return check.ok ? { ok: true } : { ok: false, reason: check.reason };
283
+ });
284
+ sendSuccess(res, { studyId: access.studyId, status: result.status, written: result.written });
285
+ }
286
+ catch (error) {
287
+ logRefusal('report write refused', error);
288
+ sendError(res, 409, 'report_write_refused', 'the report was refused and no report file was written (details in the console server log)');
289
+ }
290
+ return;
291
+ }
292
+ sendNotFound(res, `Route /api/v1/examiner/studies/:studyId${rest} not found`);
293
+ return;
294
+ }
295
+ sendNotFound(res, `Route /api/v1/examiner${subPath} not found`);
296
+ }
@@ -19,6 +19,7 @@ import { CorePrinciplesPage } from "./pages/principles/CorePrinciplesPage.js";
19
19
  import { PrincipleDetailPage } from "./pages/principles/PrincipleDetailPage.js";
20
20
  import { ActivationPage } from "./pages/activation/ActivationPage.js";
21
21
  import { DebtPage } from "./pages/debt/DebtPage.js";
22
+ import { ExaminerPage } from "./pages/examiner/ExaminerPage.js";
22
23
  import { ControlCenterPage } from "./pages/control-center/ControlCenterPage.js";
23
24
  import { SettingsPage } from "./pages/settings/SettingsPage.js";
24
25
  import { UpdatePage } from "./pages/settings/UpdatePage.js";
@@ -28,8 +29,10 @@ import { IntentPage } from "./pages/intent/IntentPage.js";
28
29
  import { DesignSystemPage } from "./pages/design-system/DesignSystemPage.js";
29
30
  import { WelcomePage } from "./pages/welcome/WelcomePage.js";
30
31
  import { getOnboardingState } from "./utils/onboarding-state.js";
31
- // Vite replaces import.meta.env.DEV with a boolean literal at build time.
32
- // The cast avoids TS2339 without needing a generated .d.ts file.
32
+ // Vite replaces import.meta.env.DEV with a boolean literal at build time, so a
33
+ // production bundle resolves this to false and dead-code elimination drops the
34
+ // DEV-only routes from the shipped UI. `utils/runtime-mode.ts` exports the same
35
+ // value for components rendered outside this module; both read the same define.
33
36
  const IS_DEV = import.meta.env.DEV;
34
37
  /**
35
38
  * SPEC-PD-UIUX-001 R4: the skip control must NOT touch the URL hash — the app
@@ -162,7 +165,7 @@ function AuthRoutes() {
162
165
  }, []);
163
166
  return (_jsxs(Routes, { children: [_jsx(Route, { path: "/splash", element: _jsx(SplashScreen, { onComplete: () => {
164
167
  setShowSplash(false);
165
- } }) }), _jsx(Route, { path: "/login", element: _jsx(LoginForm, { onAuthSuccess: handleAuthSuccess }) }), _jsx(Route, { path: "/*", element: authed === null ? (_jsx("div", { className: "min-h-screen bg-paper flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-8 h-8 border-2 border-gov border-t-transparent rounded-full animate-spin" }), _jsx("span", { className: "text-[12px] font-mono tracking-wider text-ink-3 uppercase", children: "Verifying Session..." })] }) })) : authed ? (_jsx(NotificationProvider, { children: _jsx(AuthenticatedShell, { featureFlags: featureFlags ?? undefined, children: _jsx(ErrorBoundary, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: "/focus", replace: true }) }), _jsx(Route, { path: "/welcome", element: _jsx(WelcomePage, { workspaceId: currentWorkspaceId }) }), _jsx(Route, { path: "/focus", element: _jsx(FocusPage, { featureFlags: featureFlags ?? undefined }) }), _jsx(Route, { path: "/pain", element: _jsx(PainPage, {}) }), _jsx(Route, { path: "/principles", element: _jsx(PrinciplesPage, {}) }), _jsx(Route, { path: "/principles/core", element: _jsx(CorePrinciplesPage, {}) }), _jsx(Route, { path: "/principles/:id", element: _jsx(PrincipleDetailPage, {}) }), _jsx(Route, { path: "/activation", element: _jsx(ActivationPage, {}) }), _jsx(Route, { path: "/debt", element: _jsx(DebtPage, {}) }), _jsx(Route, { path: "/control-center", element: _jsx(ControlCenterPage, {}) }), _jsx(Route, { path: "/settings", element: _jsx(SettingsPage, {}) }), _jsx(Route, { path: "/update", element: _jsx(UpdatePage, {}) }), _jsx(Route, { path: "/report-problem", element: _jsx(ReportProblemPage, {}) }), _jsx(Route, { path: "/failed-tasks", element: _jsx(FailedTasksPage, {}) }), _jsx(Route, { path: "/intent", element: _jsx(IntentPage, {}) }), IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(DesignSystemPage, {}) })), !IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(Navigate, { to: "/focus", replace: true }) }))] }) }) }) })) : (_jsx(Navigate, { to: "/login", replace: true })) })] }));
168
+ } }) }), _jsx(Route, { path: "/login", element: _jsx(LoginForm, { onAuthSuccess: handleAuthSuccess }) }), _jsx(Route, { path: "/*", element: authed === null ? (_jsx("div", { className: "min-h-screen bg-paper flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-8 h-8 border-2 border-gov border-t-transparent rounded-full animate-spin" }), _jsx("span", { className: "text-[12px] font-mono tracking-wider text-ink-3 uppercase", children: "Verifying Session..." })] }) })) : authed ? (_jsx(NotificationProvider, { children: _jsx(AuthenticatedShell, { featureFlags: featureFlags ?? undefined, children: _jsx(ErrorBoundary, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: "/focus", replace: true }) }), _jsx(Route, { path: "/welcome", element: _jsx(WelcomePage, { workspaceId: currentWorkspaceId }) }), _jsx(Route, { path: "/focus", element: _jsx(FocusPage, { featureFlags: featureFlags ?? undefined }) }), _jsx(Route, { path: "/pain", element: _jsx(PainPage, {}) }), _jsx(Route, { path: "/principles", element: _jsx(PrinciplesPage, {}) }), _jsx(Route, { path: "/principles/core", element: _jsx(CorePrinciplesPage, {}) }), _jsx(Route, { path: "/principles/:id", element: _jsx(PrincipleDetailPage, {}) }), _jsx(Route, { path: "/activation", element: _jsx(ActivationPage, {}) }), _jsx(Route, { path: "/debt", element: _jsx(DebtPage, {}) }), IS_DEV ? (_jsx(Route, { path: "/examiner", element: _jsx(ExaminerPage, {}) })) : (_jsx(Route, { path: "/examiner", element: _jsx(Navigate, { to: "/focus", replace: true }) })), _jsx(Route, { path: "/control-center", element: _jsx(ControlCenterPage, {}) }), _jsx(Route, { path: "/settings", element: _jsx(SettingsPage, {}) }), _jsx(Route, { path: "/update", element: _jsx(UpdatePage, {}) }), _jsx(Route, { path: "/report-problem", element: _jsx(ReportProblemPage, {}) }), _jsx(Route, { path: "/failed-tasks", element: _jsx(FailedTasksPage, {}) }), _jsx(Route, { path: "/intent", element: _jsx(IntentPage, {}) }), IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(DesignSystemPage, {}) })), !IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(Navigate, { to: "/focus", replace: true }) }))] }) }) }) })) : (_jsx(Navigate, { to: "/login", replace: true })) })] }));
166
169
  }
167
170
  export function App() {
168
171
  return (_jsx(ThemeProvider, { children: _jsxs(I18nextProvider, { i18n: i18n, children: [_jsx(HashRouter, { children: _jsx(AuthRoutes, {}) }), _jsx(Toaster, {})] }) }));
@@ -468,3 +468,141 @@ export interface PrincipleDetailRule {
468
468
  export interface PrincipleDetailData {
469
469
  principle: PrincipleDetail;
470
470
  }
471
+ export interface ExaminerOverviewData {
472
+ spec: string;
473
+ executionScope: string;
474
+ gates: Record<string, string>;
475
+ hostInjectionCapability: Record<string, string>;
476
+ fullConclusionMinimumLevel: string;
477
+ studiesRoot: string;
478
+ studies: string[];
479
+ }
480
+ export interface ExaminerPrincipleItem {
481
+ id: string;
482
+ version: string;
483
+ text: string;
484
+ status: string;
485
+ }
486
+ export interface ExaminerStudyStatusData {
487
+ studyId: string;
488
+ experimentMode: string;
489
+ interventionType: string;
490
+ targetPrincipleId: string | null;
491
+ plannedPairs: number | null;
492
+ runStarted: boolean;
493
+ runFinished: boolean;
494
+ unitStates: Record<string, number>;
495
+ reportStatus: string | null;
496
+ }
497
+ export interface ExaminerPreflightCheck {
498
+ id: string;
499
+ severity: string;
500
+ ok: boolean;
501
+ detail: string;
502
+ }
503
+ export interface ExaminerPreflightData {
504
+ studyId: string;
505
+ ok: boolean;
506
+ status: string;
507
+ blockers: ExaminerPreflightCheck[];
508
+ warnings: ExaminerPreflightCheck[];
509
+ checks: ExaminerPreflightCheck[];
510
+ }
511
+ export interface ExaminerReportData {
512
+ studyId: string;
513
+ report: {
514
+ status: string;
515
+ scope?: {
516
+ executionMode?: string;
517
+ controlArm?: string;
518
+ treatmentArm?: string;
519
+ };
520
+ examiner?: {
521
+ experimentMode: string;
522
+ interventionType: string;
523
+ targetPrincipleId: string | null;
524
+ primaryEndpoint: {
525
+ id: string;
526
+ } | null;
527
+ applicability: {
528
+ applicablePairs: number;
529
+ notApplicablePairs: number;
530
+ };
531
+ itt: {
532
+ plannedApplicablePairs: number;
533
+ determinedPairs: number;
534
+ unknownPairs: number;
535
+ cells: Record<string, number>;
536
+ pointEstimate: {
537
+ status: string;
538
+ value?: number;
539
+ numerator?: number;
540
+ denominator?: number;
541
+ };
542
+ lowerBound: {
543
+ status: string;
544
+ value?: number;
545
+ };
546
+ upperBound: {
547
+ status: string;
548
+ value?: number;
549
+ };
550
+ };
551
+ treatmentFidelity: {
552
+ plannedTreatmentUnits: number;
553
+ contextPresent: number;
554
+ runtimeLoaded: number;
555
+ actualInjectionRate: {
556
+ status: string;
557
+ value?: number;
558
+ };
559
+ controlContamination: {
560
+ units: number;
561
+ ofControlChains: number;
562
+ };
563
+ capacityEvictions: {
564
+ units: number;
565
+ };
566
+ };
567
+ randomization: {
568
+ strategy: string;
569
+ verified: boolean;
570
+ } | null;
571
+ } | null;
572
+ dimensions?: Record<string, unknown>;
573
+ limitations?: string[];
574
+ };
575
+ verification: {
576
+ checks: {
577
+ ok: boolean;
578
+ }[];
579
+ };
580
+ note: string;
581
+ }
582
+ export declare function fetchExaminerOverview(): Promise<ApiResponse<ExaminerOverviewData>>;
583
+ export declare function fetchExaminerPrinciples(): Promise<ApiResponse<{
584
+ principles: ExaminerPrincipleItem[];
585
+ }>>;
586
+ export declare function createExaminerStudy(input: {
587
+ targetPrincipleId: string;
588
+ experimentMode: string;
589
+ repetitionsPerCase: number;
590
+ }): Promise<ApiResponse<{
591
+ studyId: string;
592
+ experimentMode: string;
593
+ plannedPairs: number | null;
594
+ note: string;
595
+ }>>;
596
+ export declare function fetchExaminerStudyStatus(studyId: string): Promise<ApiResponse<ExaminerStudyStatusData>>;
597
+ export declare function runExaminerPreflight(studyId: string): Promise<ApiResponse<ExaminerPreflightData>>;
598
+ export declare function runExaminerStudy(studyId: string): Promise<ApiResponse<{
599
+ units: {
600
+ unit: unknown;
601
+ state: string;
602
+ }[];
603
+ }>>;
604
+ export declare function generateExaminerReport(studyId: string): Promise<ApiResponse<{
605
+ status: string;
606
+ written: boolean;
607
+ }>>;
608
+ export declare function fetchExaminerReport(studyId: string): Promise<ApiResponse<ExaminerReportData>>;
@@ -1027,3 +1027,59 @@ export { request,
1027
1027
  getToken, setToken, clearToken, checkAuth, approveApproval, rejectApproval, editApproval, fetchPrinciples, fetchCorePrinciples, fetchPrincipleDetail, fetchPrincipleGovernance, fetchOwnerDecisionView, fetchOwnerDecisionInbox, fetchOwnerDecisionLibrary, fetchPrincipleTrajectory, fetchPrincipleReceipts, fetchReceiptCounts, fetchEvidenceAudit, archivePrinciple, unarchivePrinciple, createFeedbackReport, listFeedbackReports, getFeedbackReport, deleteFeedbackReport,
1028
1028
  // Feedback submit ladder (Slice 3, spec §8)
1029
1029
  fetchFeedbackChannels, submitFeedbackReport, markFeedbackReportSent, fetchConfigSummary, fetchConfigCatalog, updateAgentBinding, updateDefaultRuntime, createRuntimeProfile, updateRuntimeProfile, deleteRuntimeProfile, fetchAgentReadiness, patchFeatureFlag, fetchOutputLanguage, updateOutputLanguage, fetchWorkspaces, addWorkspace, removeWorkspace, syncWorkspace, fetchGovernanceQueue, fetchGovernanceExperience, recoverFailedTask, fetchFailedTaskDetail, fetchOwnerDecisions, resolveOwnerDecision, fetchApprovalsGrouped, fetchAllActivations, disableActivation, proposePromptRevision, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, fetchLifecycleMetrics, fetchUpdateStatus, fetchUpdateHistory, applyFullUpdate, fetchUpdateRecovery, fetchUpdateTransaction, fetchEvidenceChain, fetchIntentSummary, fetchIntentContent, createIntentTemplate, saveIntentContent, fetchIntentVersions, recordIntentDecision, listIntentDecisionsByPainId, listIntentDecisionsByTaskId, fetchIntentDecisionSummary, dispatchFollowUp, fetchDesignGateSessions, fetchDesignGateDeclaration, createDesignGateDeclaration, updateDesignGateDecision, revokeDesignGateDeclaration, };
1030
+ export async function fetchExaminerOverview() {
1031
+ return request('/api/v1/examiner/overview', undefined, (v) => {
1032
+ if (v !== null && typeof v === 'object' && Object.hasOwn(v, 'studies'))
1033
+ return v;
1034
+ return null;
1035
+ });
1036
+ }
1037
+ export async function fetchExaminerPrinciples() {
1038
+ return request('/api/v1/examiner/principles', undefined, (v) => {
1039
+ if (v && typeof v === 'object' && Array.isArray(v.principles))
1040
+ return v;
1041
+ return null;
1042
+ });
1043
+ }
1044
+ export async function createExaminerStudy(input) {
1045
+ return request('/api/v1/examiner/studies', { method: 'POST', body: JSON.stringify(input) }, (v) => {
1046
+ if (v && typeof v.studyId === 'string')
1047
+ return v;
1048
+ return null;
1049
+ });
1050
+ }
1051
+ export async function fetchExaminerStudyStatus(studyId) {
1052
+ return request(`/api/v1/examiner/studies/${encodeURIComponent(studyId)}`, undefined, (v) => {
1053
+ if (v && typeof v.studyId === 'string')
1054
+ return v;
1055
+ return null;
1056
+ });
1057
+ }
1058
+ export async function runExaminerPreflight(studyId) {
1059
+ return request(`/api/v1/examiner/studies/${encodeURIComponent(studyId)}/preflight`, { method: 'POST' }, (v) => {
1060
+ if (v && typeof v.ok === 'boolean')
1061
+ return v;
1062
+ return null;
1063
+ });
1064
+ }
1065
+ export async function runExaminerStudy(studyId) {
1066
+ return request(`/api/v1/examiner/studies/${encodeURIComponent(studyId)}/run`, { method: 'POST', body: JSON.stringify({ mode: 'offline-stub' }) }, (v) => {
1067
+ if (v && Array.isArray(v.units))
1068
+ return v;
1069
+ return null;
1070
+ });
1071
+ }
1072
+ export async function generateExaminerReport(studyId) {
1073
+ return request(`/api/v1/examiner/studies/${encodeURIComponent(studyId)}/report`, { method: 'POST' }, (v) => {
1074
+ if (v && typeof v.status === 'string')
1075
+ return v;
1076
+ return null;
1077
+ });
1078
+ }
1079
+ export async function fetchExaminerReport(studyId) {
1080
+ return request(`/api/v1/examiner/studies/${encodeURIComponent(studyId)}/report`, undefined, (v) => {
1081
+ if (v && v.report)
1082
+ return v;
1083
+ return null;
1084
+ });
1085
+ }
@@ -1,4 +1,15 @@
1
1
  import * as React from "react";
2
+ /**
3
+ * P2-1: the Examiner is a DEV-only research surface. The navigation entry is
4
+ * derived from the build-time mode, so a production bundle never advertises a
5
+ * capability the server refuses.
6
+ */
7
+ export declare function visibleMainNavItems(isDev: boolean): {
8
+ id: string;
9
+ labelKey: string;
10
+ href: string;
11
+ icon: React.ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
12
+ }[];
2
13
  export declare function AppSidebar({ featureFlags }: {
3
14
  featureFlags?: Record<string, {
4
15
  enabled: boolean;
@@ -1,12 +1,13 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { Link, useLocation, useNavigate } from "react-router-dom";
4
- import { Sun, Moon, Focus, AlertTriangle, AlertCircle, BookOpen, Zap, Archive, Compass, Settings, MessageSquare, RefreshCw, LogOut, Languages, Menu, } from "lucide-react";
4
+ import { Sun, Moon, Focus, AlertTriangle, AlertCircle, BookOpen, Zap, Archive, Compass, FlaskConical, Settings, MessageSquare, RefreshCw, LogOut, Languages, Menu, } from "lucide-react";
5
5
  import { useTheme } from "../theme-provider.js";
6
6
  import { useTranslation } from "react-i18next";
7
7
  import { useNotifications } from "../notifications/useNotifications.js";
8
8
  import { clearToken } from "../../api.js";
9
9
  import { cn } from "../../../lib/utils.js";
10
+ import { IS_DEV } from "../../utils/runtime-mode.js";
10
11
  import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "../ui/sheet.js";
11
12
  function ThresholdMark({ className }) {
12
13
  return (_jsxs("svg", { viewBox: "0 0 28 28", className: className, fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: [_jsx("path", { d: "M6 4V24M22 4V24M2 14H26", strokeLinecap: "square" }), _jsx("circle", { cx: "14", cy: "14", r: "2.5", fill: "currentColor", stroke: "none" })] }));
@@ -17,8 +18,17 @@ const mainNavItems = [
17
18
  { id: "principles", labelKey: "components.sidebar.principles", href: "/principles", icon: BookOpen },
18
19
  { id: "activation", labelKey: "components.sidebar.activation", href: "/activation", icon: Zap },
19
20
  { id: "debt", labelKey: "components.sidebar.debt", href: "/debt", icon: Archive },
21
+ { id: "examiner", labelKey: "components.sidebar.examiner", href: "/examiner", icon: FlaskConical },
20
22
  { id: "intent", labelKey: "components.sidebar.intent", href: "/intent", icon: Compass },
21
23
  ];
24
+ /**
25
+ * P2-1: the Examiner is a DEV-only research surface. The navigation entry is
26
+ * derived from the build-time mode, so a production bundle never advertises a
27
+ * capability the server refuses.
28
+ */
29
+ export function visibleMainNavItems(isDev) {
30
+ return isDev ? mainNavItems : mainNavItems.filter((item) => item.id !== "examiner");
31
+ }
22
32
  const toolNavItems = [
23
33
  { id: "control-center", labelKey: "components.sidebar.controlCenter", href: "/control-center", icon: Settings },
24
34
  { id: "report-problem", labelKey: "components.sidebar.reportProblem", href: "/report-problem", icon: MessageSquare },
@@ -51,7 +61,7 @@ function SidebarContent({ featureFlags, onNavigate, touchTargets, }) {
51
61
  i18n.changeLanguage(nextLang);
52
62
  localStorage.setItem("pd-language", nextLang);
53
63
  };
54
- return (_jsxs(_Fragment, { children: [_jsxs("nav", { className: "flex-1 py-2 overflow-y-auto", "aria-label": t("components.sidebar.navigation", { defaultValue: "主导航" }), children: [_jsx("div", { className: "px-2", children: mainNavItems.map((item) => {
64
+ return (_jsxs(_Fragment, { children: [_jsxs("nav", { className: "flex-1 py-2 overflow-y-auto", "aria-label": t("components.sidebar.navigation", { defaultValue: "主导航" }), children: [_jsx("div", { className: "px-2", children: visibleMainNavItems(IS_DEV).map((item) => {
55
65
  const Icon = item.icon;
56
66
  const active = isActive(item.href);
57
67
  return (_jsxs(Link, { to: item.href, "data-testid": `nav-${item.id}`, "aria-current": active ? "page" : undefined, onClick: onNavigate, className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[13px] transition-all duration-150", touchTargets ? "min-h-[44px] py-2" : "py-2", active