create-principles-disciple 1.143.0 → 1.143.1

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 (60) hide show
  1. package/console/dist/server/index.js +19 -0
  2. package/console/dist/server/models/OwnerDecisionViewModel.d.ts +65 -0
  3. package/console/dist/server/models/OwnerDecisionViewModel.js +686 -0
  4. package/console/dist/server/routes/owner-decision.d.ts +40 -0
  5. package/console/dist/server/routes/owner-decision.js +88 -0
  6. package/console/dist/server/routes/principles.d.ts +3 -0
  7. package/console/dist/server/routes/principles.js +3 -0
  8. package/console/dist/ui/api.d.ts +7 -3
  9. package/console/dist/ui/api.js +13 -2
  10. package/console/dist/ui/i18n/en.json +70 -4
  11. package/console/dist/ui/i18n/zh-CN.json +70 -4
  12. package/console/dist/ui/pages/focus/FocusPage.js +25 -2
  13. package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +24 -0
  14. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +168 -210
  15. package/console/dist/ui/pages/principles/PrinciplesPage.js +25 -10
  16. package/console/dist/ui/utils/enum-labels.d.ts +1 -1
  17. package/console/dist/ui/utils/enum-labels.js +8 -0
  18. package/console/dist/ui/utils/validators.d.ts +99 -1
  19. package/console/dist/ui/utils/validators.js +115 -0
  20. package/console/dist/web/assets/app.css +3 -0
  21. package/console/dist/web/assets/app.js +590 -349
  22. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js +25 -0
  23. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js.map +1 -1
  24. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +16 -0
  25. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
  26. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +22 -0
  27. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
  28. package/core/dist/runtime-v2/index.d.ts +7 -0
  29. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  30. package/core/dist/runtime-v2/index.js +4 -0
  31. package/core/dist/runtime-v2/index.js.map +1 -1
  32. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts +2 -0
  33. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts.map +1 -0
  34. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js +559 -0
  35. package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js.map +1 -0
  36. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts +7868 -0
  37. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js +367 -0
  39. package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js.map +1 -0
  40. package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts +415 -0
  41. package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts.map +1 -0
  42. package/core/dist/runtime-v2/owner-decision/owner-decision-view.js +856 -0
  43. package/core/dist/runtime-v2/owner-decision/owner-decision-view.js.map +1 -0
  44. package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts +72 -0
  45. package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts.map +1 -0
  46. package/core/dist/runtime-v2/owner-decision/semantic-selector.js +176 -0
  47. package/core/dist/runtime-v2/owner-decision/semantic-selector.js.map +1 -0
  48. package/package.json +1 -1
  49. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
  50. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  51. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +47 -7
  52. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  53. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +84 -0
  54. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  55. package/pd-cli/dist/services/rulehost-pipeline-runner.js +215 -38
  56. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  57. package/plugin/dist/bundle.js +526 -526
  58. package/plugin/dist/governance-audit.js +205 -205
  59. package/plugin/dist/rulehost-evidence.js +246 -246
  60. package/release-manager/package.json +1 -1
@@ -7,7 +7,7 @@ import { PageShell } from "../../components/layout/page-shell.js";
7
7
  import { PageLoading } from "../../components/layout/page-loading.js";
8
8
  import { SectionTitle } from "../../components/layout/section-title.js";
9
9
  import { Button } from "../../components/ui/button.js";
10
- import { fetchPrincipleDetail, fetchApprovalsGrouped, fetchLifecycleMetrics, fetchPrincipleTrajectory, fetchPrincipleGovernance, fetchPrincipleReceipts, approveApproval, rejectApproval, editApproval, } from "../../api.js";
10
+ import { fetchPrincipleDetail, fetchApprovalsGrouped, fetchLifecycleMetrics, fetchPrincipleTrajectory, fetchPrincipleGovernance, fetchPrincipleReceipts, fetchOwnerDecisionView, approveApproval, rejectApproval, editApproval, disableActivation, } from "../../api.js";
11
11
  import { ReceiptCoverageDisclosure, getReceiptSourceStatusLabelKey } from "../../components/receipts/ReceiptCoverageDisclosure.js";
12
12
  import { formatDate } from "../../utils/format-date.js";
13
13
  // ── Runtime validation (H section) ──────────────────────────────────────────
@@ -22,17 +22,11 @@ function safeStringArray(v) {
22
22
  return [];
23
23
  return v.filter(isString);
24
24
  }
25
- function safeString(v, fallback = "") {
26
- return typeof v === "string" ? v : fallback;
27
- }
28
25
  /**
29
26
  * Validate and normalize a PrincipleDetail from untrusted network data.
30
27
  * Returns a normalized object with safe defaults for all fields the page
31
28
  * actually accesses. Never returns null for individual fields — only for
32
29
  * completely unparseable top-level structures.
33
- *
34
- * Fields the page uses: id, text, status, triggerPattern, action,
35
- * derivedFromPainIds (length + map), rules (length).
36
30
  */
37
31
  function validatePrincipleDetail(data) {
38
32
  if (!isRecord(data))
@@ -50,8 +44,8 @@ function validatePrincipleDetail(data) {
50
44
  // Normalize fields the page accesses with safe defaults
51
45
  const normalized = {
52
46
  ...raw,
53
- triggerPattern: safeString(raw.triggerPattern),
54
- action: safeString(raw.action),
47
+ triggerPattern: typeof raw.triggerPattern === "string" ? raw.triggerPattern : "",
48
+ action: typeof raw.action === "string" ? raw.action : "",
55
49
  derivedFromPainIds: safeStringArray(raw.derivedFromPainIds),
56
50
  rules: Array.isArray(raw.rules) ? raw.rules : [],
57
51
  };
@@ -146,6 +140,58 @@ export function deriveGovernanceControlBlock(input) {
146
140
  }
147
141
  return { source: 'i18n', reasonKey: GOVERNANCE_BLOCK_I18N_KEYS.noDecisionReason };
148
142
  }
143
+ export function narrativeTexts(field) {
144
+ if (field.status === 'known' && Array.isArray(field.value)) {
145
+ const texts = [];
146
+ for (const item of field.value) {
147
+ if (isRecord(item) && isString(item.text))
148
+ texts.push(item.text);
149
+ }
150
+ return { status: 'known', texts };
151
+ }
152
+ const reason = isRecord(field.reason) && isString(field.reason.ownerText) ? field.reason.ownerText : '';
153
+ return { status: 'unknown', reason };
154
+ }
155
+ /** Resolves the concrete mutation target id for an action (never from URL). */
156
+ export function actionTargetId(action, kind) {
157
+ const ref = action.targetRefs.find((target) => target.kind === kind);
158
+ return ref?.id ?? null;
159
+ }
160
+ // Codex review P2 fix (localization): the decision panel's CHROME (action
161
+ // labels, next action, blocker headline) localizes in the client via stable
162
+ // semantic codes; narrative CONTENT (artifact-derived text) stays in its
163
+ // source language — the model-generated material cannot be translated by a
164
+ // key lookup, so a full locale-carried response remains a follow-up.
165
+ const ACTION_LABEL_KEY = {
166
+ approve: 'principles.detail.ownerDecision.actionLabel.approve',
167
+ reject: 'principles.detail.ownerDecision.actionLabel.reject',
168
+ edit_approval: 'principles.detail.ownerDecision.actionLabel.edit_approval',
169
+ disable: 'principles.detail.ownerDecision.actionLabel.disable',
170
+ };
171
+ export function localizeActionLabel(action, t) {
172
+ const key = ACTION_LABEL_KEY[action.semantic];
173
+ if (key === undefined)
174
+ return action.label;
175
+ const localized = t(key, { defaultValue: '' });
176
+ return localized === '' ? action.label : localized;
177
+ }
178
+ export function localizeNextAction(code, ownerText, t) {
179
+ const localized = t(`principles.detail.ownerDecision.next.${code}`, { defaultValue: '' });
180
+ return localized === '' ? ownerText : localized;
181
+ }
182
+ export function localizeBlocker(code, ownerText, t) {
183
+ const safeKey = code.replace(/[^a-zA-Z0-9_.]/g, '_');
184
+ const localized = t(`principles.detail.ownerDecision.blocker.${safeKey}`, { defaultValue: '' });
185
+ return localized === '' ? ownerText : localized;
186
+ }
187
+ const DECISION_STATE_LABEL_KEY = {
188
+ needs_owner_decision: 'principles.detail.ownerDecision.state.needs_owner_decision',
189
+ processing: 'principles.detail.ownerDecision.state.processing',
190
+ blocked: 'principles.detail.ownerDecision.state.blocked',
191
+ recovery_needed: 'principles.detail.ownerDecision.state.recovery_needed',
192
+ decided: 'principles.detail.ownerDecision.state.decided',
193
+ no_action: 'principles.detail.ownerDecision.state.no_action',
194
+ };
149
195
  export function PrincipleDetailPage() {
150
196
  const { t, i18n } = useTranslation("pages");
151
197
  const { id } = useParams();
@@ -156,15 +202,14 @@ export function PrincipleDetailPage() {
156
202
  const [trajectory, setTrajectory] = useState(null);
157
203
  const [governance, setGovernance] = useState(null);
158
204
  const [governanceUnavailable, setGovernanceUnavailable] = useState(null);
205
+ const [ownerDecision, setOwnerDecision] = useState(null);
206
+ const [ownerDecisionUnavailable, setOwnerDecisionUnavailable] = useState(null);
159
207
  const [receipts, setReceipts] = useState(null);
160
208
  const [loading, setLoading] = useState(true);
161
209
  const [error, setError] = useState(null);
162
- // Decision state
163
- const [showConfirm, setShowConfirm] = useState(false);
164
- const [showRejectInput, setShowRejectInput] = useState(false);
210
+ // Decision action state — driven by backend available_actions only.
211
+ const [pendingAction, setPendingAction] = useState(null);
165
212
  const [rejectReason, setRejectReason] = useState("");
166
- const [showEditInput, setShowEditInput] = useState(false);
167
- const [editReason, setEditReason] = useState("");
168
213
  const [newArtifactId, setNewArtifactId] = useState("");
169
214
  const [actionLoading, setActionLoading] = useState(false);
170
215
  // Evidence ID copy state (Wave 6: stop showing raw UUIDs as primary content)
@@ -179,14 +224,17 @@ export function PrincipleDetailPage() {
179
224
  setApprovalGroup(null); // Clear previous approval group to prevent stale actionability (P1)
180
225
  setGovernance(null);
181
226
  setGovernanceUnavailable(null);
227
+ setOwnerDecision(null);
228
+ setOwnerDecisionUnavailable(null);
182
229
  setReceipts(null);
183
230
  try {
184
- const [pResult, aResult, lResult, tResult, gResult, rResult] = await Promise.all([
231
+ const [pResult, aResult, lResult, tResult, gResult, dResult, rResult] = await Promise.all([
185
232
  fetchPrincipleDetail(id),
186
233
  fetchApprovalsGrouped(),
187
234
  fetchLifecycleMetrics(id),
188
235
  fetchPrincipleTrajectory(id),
189
236
  fetchPrincipleGovernance(id),
237
+ fetchOwnerDecisionView(id),
190
238
  fetchPrincipleReceipts(id),
191
239
  ]);
192
240
  if (!pResult.success) {
@@ -222,6 +270,14 @@ export function PrincipleDetailPage() {
222
270
  else if (!gResult.success && gResult.reason !== 'feature_disabled') {
223
271
  setGovernanceUnavailable({ reason: gResult.error, ...(gResult.nextAction === undefined ? {} : { nextAction: gResult.nextAction }) });
224
272
  }
273
+ // Owner Decision Experience v1: the canonical first layer. feature_disabled
274
+ // mirrors the governance flag-off path (no owner-decision data, no error).
275
+ if (dResult.success && dResult.data) {
276
+ setOwnerDecision(dResult.data);
277
+ }
278
+ else if (!dResult.success && dResult.reason !== 'feature_disabled') {
279
+ setOwnerDecisionUnavailable({ reason: dResult.error });
280
+ }
225
281
  // PRI-533: receipt history (degraded carries reason + nextAction)
226
282
  if (rResult.success && rResult.data) {
227
283
  setReceipts(rResult.data);
@@ -241,209 +297,80 @@ export function PrincipleDetailPage() {
241
297
  useEffect(() => {
242
298
  loadData();
243
299
  }, [loadData]);
244
- // ── Actions ─────────────────────────────────────────────────────────────
245
- // Actionable Approval Check (PRI-387)
246
- let isActionable = false;
247
- let reasonKey = "";
248
- let defaultReason = "";
249
- if (!approvalGroup) {
250
- reasonKey = "principles.detail.reasonDataUnavailable";
251
- defaultReason = "数据暂不可用";
252
- }
253
- else if (approvalGroup.status !== "pending") {
254
- reasonKey = "principles.detail.reasonAlreadyHandled";
255
- defaultReason = "已处理";
256
- }
257
- else if (approvalGroup.records.length === 0) {
258
- reasonKey = "principles.detail.reasonNoRecords";
259
- defaultReason = "暂无待审批记录";
260
- }
261
- else {
262
- const hasMvpChannel = approvalGroup.records.some((r) => r.channel === "prompt" || r.channel === "defer_archive");
263
- if (!hasMvpChannel) {
264
- reasonKey = "principles.detail.reasonUnsupportedChannel";
265
- defaultReason = "不是 MVP 支持通道";
266
- }
267
- else {
268
- isActionable = true;
269
- }
270
- }
271
- // PRI-582: the projection authorizes the decision controls. When it cannot,
272
- // controls are hidden together with a truthful reason instead of the generic
273
- // “no Owner decision is required” copy, which inverted the real cause.
274
- const governanceBlock = deriveGovernanceControlBlock({ governance, governanceUnavailable });
275
- const showDecisionControls = governanceBlock === null;
276
- if (!showDecisionControls) {
277
- isActionable = false;
278
- }
279
- const governanceBlockedNextAction = governanceBlock === null
280
- ? undefined
281
- : governanceBlock.source === 'server'
282
- ? governanceBlock.nextAction
283
- : governanceBlock.nextActionKey === undefined
284
- ? undefined
285
- : t(governanceBlock.nextActionKey);
286
- const handleApprove = () => {
287
- if (!isActionable)
288
- return;
289
- setShowConfirm(true);
290
- };
291
- const cancelConfirm = () => setShowConfirm(false);
292
- // ── Apply decision to all records in the group ──────────────────────────
293
- // Constraint: "多通道审批记录在 UI 上收拢成对一条原则的单次治理决策"
294
- // When owner approves/rejects a principle, ALL pending records must receive
295
- // the same decision. Partial failure is reported loudly (ERR-002 / EP-03).
296
- async function applyDecisionToAllRecords(action, reason) {
297
- if (!approvalGroup)
298
- return { allSucceeded: false, failedCount: 0, totalCount: 0 };
299
- const records = approvalGroup.records;
300
- let failedCount = 0;
301
- for (const record of records) {
302
- const result = action === "approve"
303
- ? await approveApproval(record.id)
304
- : await rejectApproval(record.id, reason ?? "");
305
- if (!result.success) {
306
- failedCount++;
307
- }
308
- }
309
- return {
310
- allSucceeded: failedCount === 0,
311
- failedCount,
312
- totalCount: records.length,
313
- };
314
- }
315
- const confirmApprove = async () => {
316
- if (!isActionable || !approvalGroup || actionLoading)
300
+ // ── Owner decision actions (SPEC §8.4: mutation service is the authority) ─
301
+ // The view's actions are advisory; every submission goes through the real
302
+ // mutation service. On failure: show the service reason, invalidate stale
303
+ // actions, re-GET the view. Never auto-retry a mutation.
304
+ const submitOwnerAction = async (action, reason) => {
305
+ if (actionLoading)
317
306
  return;
318
307
  setActionLoading(true);
319
308
  try {
320
- const { allSucceeded, failedCount, totalCount } = await applyDecisionToAllRecords("approve");
321
- if (allSucceeded) {
322
- toast.success(t("principles.detail.approved"));
323
- setShowConfirm(false);
324
- loadData();
325
- }
326
- else {
327
- // Partial failure — fail loud (EP-03)
328
- toast.error(t("principles.detail.partialFailure", {
329
- defaultValue: `批准完成,但 ${failedCount}/${totalCount} 条记录失败。请检查后重试。`,
330
- failedCount,
331
- totalCount,
332
- }));
333
- loadData();
309
+ let failed = false;
310
+ let failureDetail = '';
311
+ const settle = (result) => {
312
+ if (!result.success) {
313
+ failed = true;
314
+ failureDetail = `${result.error ?? ''}${result.nextAction ? ` ${result.nextAction}` : ''}`.trim();
315
+ }
316
+ };
317
+ if (action.semantic === 'approve') {
318
+ const approvalId = actionTargetId(action, 'approval');
319
+ if (approvalId === null) {
320
+ toast.error(t("principles.detail.ownerDecision.invalidTarget", { defaultValue: "动作目标缺失,无法提交。" }));
321
+ return;
322
+ }
323
+ settle(await approveApproval(approvalId));
334
324
  }
335
- }
336
- catch {
337
- toast.error(t("principles.detail.approveFailed"));
338
- }
339
- finally {
340
- setActionLoading(false);
341
- }
342
- };
343
- const handleReject = () => {
344
- if (!isActionable)
345
- return;
346
- setShowRejectInput(true);
347
- };
348
- const cancelReject = () => {
349
- setShowRejectInput(false);
350
- setRejectReason("");
351
- };
352
- const confirmReject = async () => {
353
- if (!isActionable || !approvalGroup || !rejectReason.trim() || actionLoading)
354
- return;
355
- setActionLoading(true);
356
- try {
357
- const { allSucceeded, failedCount, totalCount } = await applyDecisionToAllRecords("reject", rejectReason.trim());
358
- if (allSucceeded) {
359
- toast.success(t("principles.detail.rejected"));
360
- setShowRejectInput(false);
361
- setRejectReason("");
362
- loadData();
325
+ else if (action.semantic === 'reject') {
326
+ const approvalId = actionTargetId(action, 'approval');
327
+ if (approvalId === null) {
328
+ toast.error(t("principles.detail.ownerDecision.invalidTarget", { defaultValue: "动作目标缺失,无法提交。" }));
329
+ return;
330
+ }
331
+ settle(await rejectApproval(approvalId, reason ?? ""));
363
332
  }
364
- else {
365
- // Partial failure — fail loud (EP-03)
366
- toast.error(t("principles.detail.partialFailure", {
367
- defaultValue: `拒绝完成,但 ${failedCount}/${totalCount} 条记录失败。请检查后重试。`,
368
- failedCount,
369
- totalCount,
370
- }));
371
- loadData();
333
+ else if (action.semantic === 'edit_approval') {
334
+ const approvalId = actionTargetId(action, 'approval');
335
+ if (approvalId === null || reason === undefined || newArtifactId.trim() === '') {
336
+ toast.error(t("principles.detail.ownerDecision.invalidTarget", { defaultValue: "动作目标缺失,无法提交。" }));
337
+ return;
338
+ }
339
+ settle(await editApproval(approvalId, newArtifactId.trim(), reason));
372
340
  }
373
- }
374
- catch {
375
- toast.error(t("principles.detail.rejectFailed"));
376
- }
377
- finally {
378
- setActionLoading(false);
379
- }
380
- };
381
- const handlePark = () => {
382
- // No-op: Park is disabled and not available in this version.
383
- };
384
- const handleEdit = () => {
385
- if (!isActionable)
386
- return;
387
- setShowEditInput(true);
388
- setShowRejectInput(false);
389
- setShowConfirm(false);
390
- };
391
- const cancelEdit = () => {
392
- setShowEditInput(false);
393
- setEditReason("");
394
- setNewArtifactId("");
395
- };
396
- const currentArtifactId = approvalGroup?.records.find((r) => r.status === "pending")?.artifactId ?? "";
397
- const confirmEdit = async () => {
398
- if (!isActionable || !approvalGroup || !editReason.trim() || !newArtifactId.trim() || actionLoading)
399
- return;
400
- const pendingRecords = approvalGroup.records.filter((r) => r.status === "pending");
401
- // P1-5: guard against an empty pending set — without this the loop is a
402
- // no-op yet allSucceeded evaluates true, reporting a misleading success.
403
- if (pendingRecords.length === 0) {
404
- toast.error(t("principles.detail.editFailed", { defaultValue: "没有可编辑的待处理记录。" }));
405
- setShowEditInput(false);
406
- return;
407
- }
408
- setActionLoading(true);
409
- try {
410
- let failedCount = 0;
411
- let failureReason;
412
- for (const record of pendingRecords) {
413
- const result = await editApproval(record.id, newArtifactId.trim(), editReason.trim());
414
- if (!result.success) {
415
- failedCount++;
416
- if (!failureReason) {
417
- failureReason = result.nextAction ? `${result.error} ${result.nextAction}` : result.error;
418
- }
341
+ else if (action.semantic === 'disable') {
342
+ const activationId = actionTargetId(action, 'activation');
343
+ if (activationId === null) {
344
+ toast.error(t("principles.detail.ownerDecision.invalidTarget", { defaultValue: "动作目标缺失,无法提交。" }));
345
+ return;
419
346
  }
347
+ settle(await disableActivation(activationId));
420
348
  }
421
- const allSucceeded = failedCount === 0;
422
- if (allSucceeded) {
423
- toast.success(t("principles.detail.editSucceeded", { defaultValue: "已保存修订" }));
424
- setShowEditInput(false);
425
- setEditReason("");
426
- setNewArtifactId("");
427
- loadData();
349
+ else {
350
+ return; // unknown semantics are never rendered; belt-and-suspenders
351
+ }
352
+ if (!failed) {
353
+ toast.success(t("principles.detail.ownerDecision.actionDone", { defaultValue: "操作已提交。" }));
428
354
  }
429
355
  else {
430
- // P1-4: thread the backend failureReason into the toast so the owner
431
- // sees WHY records failed (matches FocusPage behaviour). Previously
432
- // failureReason was computed but never used (dead code).
433
- toast.error(t("principles.detail.partialFailure", {
434
- defaultValue: `编辑完成,但 ${failedCount}/${pendingRecords.length} 条记录失败:${failureReason ?? t("principles.detail.unknownFailure", { defaultValue: "请检查服务日志。" })}`,
435
- failedCount,
436
- totalCount: pendingRecords.length,
437
- reason: failureReason ?? t("principles.detail.unknownFailure", { defaultValue: "请检查服务日志。" }),
356
+ // SPEC §8.4: surface the service's real refusal reason and let the
357
+ // fresh GET decide what is actionable now — never auto-retry.
358
+ toast.error(t("principles.detail.ownerDecision.actionFailed", {
359
+ defaultValue: "操作被拒绝:{{detail}} 可用操作已刷新,请基于最新状态重新决定。",
360
+ detail: failureDetail === '' ? t("principles.detail.unknownFailure", { defaultValue: "请检查服务日志。" }) : failureDetail,
438
361
  }));
439
- loadData();
440
362
  }
441
363
  }
442
- catch {
443
- toast.error(t("principles.detail.editFailed", { defaultValue: "编辑失败,请稍后重试。" }));
364
+ catch (err) {
365
+ toast.error(err instanceof Error ? err.message : String(err));
444
366
  }
445
367
  finally {
446
368
  setActionLoading(false);
369
+ setPendingAction(null);
370
+ setRejectReason("");
371
+ setNewArtifactId("");
372
+ // Invalidate the stale action list and re-derive from a fresh GET.
373
+ await loadData();
447
374
  }
448
375
  };
449
376
  // ── Render ──────────────────────────────────────────────────────────────
@@ -453,21 +380,44 @@ export function PrincipleDetailPage() {
453
380
  if (error || !principle) {
454
381
  return (_jsxs(PageShell, { children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate("/principles"), className: "mb-4", children: ["\u2190 ", t("principles.detail.backToList")] }), _jsxs("div", { className: "border border-danger/30 rounded-[var(--radius-md)] p-4", children: [_jsx("p", { className: "text-danger text-sm", children: error ?? t("principles.notFound") }), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, className: "mt-2", children: t("principles.retry", { defaultValue: "Retry" }) })] })] }));
455
382
  }
456
- const isPending = approvalGroup?.status === "pending" || principle.status === "candidate" || principle.status === "probation";
457
383
  const hasRules = principle.rules.length > 0;
458
384
  const receiptPresentation = receipts?.status === 'ok'
459
385
  ? getReceiptPresentation(receipts.effectCount)
460
386
  : null;
461
- return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate("/principles"), 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", children: principle.text }), _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(Button, { variant: "outline", size: "sm", disabled: true, children: t("principles.detail.modifyWording") }), _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: () => {
387
+ return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate("/principles"), 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", children: ownerDecision?.learnedPrinciple.status === 'known'
388
+ ? ownerDecision.learnedPrinciple.value.text
389
+ : principle.text }), 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", {
390
+ defaultValue: "以上为原始技术建议文本;可读的行为准则暂缺(保留原文以保证可追溯)。",
391
+ }) })), _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(Button, { variant: "outline", size: "sm", disabled: true, children: t("principles.detail.modifyWording") }), _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: () => {
462
392
  const params = new URLSearchParams({ source: "principle_page", principleId: id ?? "" });
463
393
  navigate(`/report-problem?${params.toString()}`);
464
- }, children: t("reportProblem.entryFeedback") })] })] }), (governance !== null || governanceUnavailable !== null) && (_jsxs("section", { className: "mb-8", "aria-labelledby": "governance-summary-title", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.title') }), governance !== null ? (_jsxs("div", { "data-testid": "governance-summary", className: `rounded-[var(--radius-md)] border p-4 ${governance.dataQuality.degraded ? 'border-amber/30 bg-amber/5' : 'border-gov/25 bg-gov/5'}`, children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "governance-summary-title", className: "text-[18px] font-semibold text-ink", children: t(`principles.detail.${governance.summary.headlineCode}`, { defaultValue: governance.summary.headlineCode }) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: t(`principles.detail.${governance.summary.reasonCode}`, { defaultValue: governance.summary.reasonCode }) })] }), _jsx("span", { className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: t(`principles.detail.governance.confidence.${governance.dataQuality.degraded ? 'degraded' : 'strong'}`) })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.principleState') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.state.${governance.principleState.value}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.process') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: governance.process.stage === undefined ? t('principles.detail.governance.none') : t(`principles.detail.governance.stage.${governance.process.stage}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.automation') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.automationState.${governance.automation.state}`) })] })] }), _jsxs("div", { "data-testid": "governance-next-action", className: "mt-4 border-l-2 border-l-gov pl-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.nextAction') }), _jsx("p", { className: "text-[13px] text-ink-2", children: t(`principles.detail.${governance.summary.nextActionCode}`, { defaultValue: governance.summary.nextActionCode }) })] }), governance.attention.items.length > 0 && (_jsxs("div", { className: "mt-4", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.blockers') }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: governance.attention.items.map(item => _jsx("li", { children: t(`principles.detail.governance.attention.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.kind}-${item.sourceRef.type}-${item.sourceRef.id}`)) })] })), governance.dataQuality.degraded && (_jsxs("div", { "data-testid": "governance-data-quality", className: "mt-4 rounded-[var(--radius-sm)] border border-amber/20 p-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-amber", children: t('principles.detail.governance.uncertainty') }), _jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: governance.dataQuality.issues.map((item, index) => _jsx("li", { children: t(`principles.detail.governance.issue.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.source}-${item.reasonCode}-${index}`)) })] }))] })) : (_jsxs("div", { "data-testid": "governance-data-quality", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h2", { id: "governance-summary-title", className: "text-[16px] font-semibold text-ink", children: t('principles.detail.governance.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: t('principles.detail.governance.unavailableReason') }), _jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: t('principles.detail.governance.unavailableNextAction') })] }))] })), receipts !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "receipt-history-title", children: [_jsx(SectionTitle, { children: t('principles.detail.receipts.title') }), receipts.status === 'ok' ? (_jsxs("div", { "data-testid": "receipt-history", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("h2", { id: "receipt-history-title", className: "text-[15px] font-semibold text-ink", children: receiptPresentation?.headlineKey === 'principles.detail.receipts.headline'
394
+ }, children: t("reportProblem.entryFeedback") })] })] }), ownerDecision !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "owner-decision-title", "data-testid": "owner-decision-view", children: [_jsx(SectionTitle, { children: t("principles.detail.ownerDecision.title", { defaultValue: "这条原则的决策视图" }) }), _jsxs("div", { className: "rounded-[var(--radius-md)] border border-gov/25 bg-gov/5 p-4", children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "owner-decision-title", className: "text-[18px] font-semibold text-ink", children: t(DECISION_STATE_LABEL_KEY[ownerDecision.decisionState]) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: localizeNextAction(ownerDecision.nextAction.code, ownerDecision.nextAction.ownerText, t) })] }), _jsx("span", { "data-testid": "owner-decision-state", className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: ownerDecision.sourceReadStatus === 'complete'
395
+ ? t("principles.detail.ownerDecision.readsComplete", { defaultValue: "本次所需数据已读取" })
396
+ : t("principles.detail.ownerDecision.readsPartial", { defaultValue: "部分数据源读取失败" }) })] }), _jsx(OwnerNarrativeBlock, { testId: "owner-decision-incident", label: t("principles.detail.ownerDecision.incident", { defaultValue: "发生了什么" }), field: ownerDecision.incidentSummary }), _jsxs("div", { className: "mt-4", "data-testid": "owner-decision-learned", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.learned", { defaultValue: "Agent 学到的行为准则" }) }), ownerDecision.learnedPrinciple.status === 'known' ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mt-1 text-ink-2 text-[14px] leading-relaxed", children: ownerDecision.learnedPrinciple.value.text }), _jsx("p", { className: "mt-1 text-ink-4 text-[12px]", children: t("principles.detail.ownerDecision.sourceTierLabel", {
397
+ defaultValue: "来源:{{tier}}(未经改写)",
398
+ tier: t(`principles.detail.ownerDecision.tier.${ownerDecision.learnedPrinciple.value.sourceTier}`, { defaultValue: ownerDecision.learnedPrinciple.value.sourceTier }),
399
+ }) })] })) : (_jsx("p", { className: "mt-1 text-amber text-[13px] leading-relaxed", children: ownerDecision.learnedPrinciple.reason.ownerText }))] }), _jsx(OwnerNarrativeBlock, { testId: "owner-decision-rationale", label: t("principles.detail.ownerDecision.rationale", { defaultValue: "为什么这么判断" }), field: ownerDecision.rationale }), _jsx(OwnerNarrativeBlock, { testId: "owner-decision-applicability", label: t("principles.detail.ownerDecision.applicability", { defaultValue: "什么时候适用" }), field: ownerDecision.applicability }), _jsx(OwnerNarrativeBlock, { testId: "owner-decision-expected", label: t("principles.detail.ownerDecision.expected", { defaultValue: "批准后会怎样(拟议行为,未部署)" }), field: ownerDecision.expectedBehavior }), ownerDecision.currentEnforcement.status === 'known' && (_jsxs("div", { className: "mt-4", "data-testid": "owner-decision-enforcement", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.enforcementLabel", { defaultValue: "当前实际执行" }) }), _jsx("p", { className: "mt-1 text-ink-2 text-[14px] leading-relaxed", children: ownerDecision.currentEnforcement.value?.state === 'active'
400
+ ? t("principles.detail.ownerDecision.enforcement.active", { defaultValue: "当前有正在执行的生效方式(见下方明细)。" })
401
+ : ownerDecision.currentEnforcement.value?.state === 'deactivated'
402
+ ? t("principles.detail.ownerDecision.enforcement.deactivated", { defaultValue: "此前的执行方式已停用;当前没有正在执行的生效方式。" })
403
+ : ownerDecision.currentEnforcement.value?.state === 'partially_active'
404
+ ? t("principles.detail.ownerDecision.enforcement.partial", { defaultValue: "部分执行方式仍在运行(见下方明细)。" })
405
+ : t("principles.detail.ownerDecision.enforcement.none", { defaultValue: "这条原则当前没有生效中的执行方式。" }) }), ownerDecision.currentEnforcement.value?.items.map((item) => (_jsxs("p", { className: "mt-1 text-ink-3 text-[12px]", children: [item.mode.status === 'known' ? item.mode.value : item.channel, " · ", item.active
406
+ ? t("principles.detail.ownerDecision.enforcement.since", { defaultValue: "生效中(自 {{since}})", since: item.since.status === 'known' ? item.since.value.slice(0, 10) : '?' })
407
+ : t("principles.detail.ownerDecision.enforcement.stopped", { defaultValue: "已停用" })] }, item.activationRef.id)))] })), ownerDecision.currentEnforcement.status === 'unknown' && (_jsx("p", { className: "mt-4 text-amber text-[13px]", "data-testid": "owner-decision-enforcement-unknown", children: ownerDecision.currentEnforcement.reason?.ownerText })), ownerDecision.evidenceSummary.status === 'known' && (_jsxs("div", { className: "mt-4", "data-testid": "owner-decision-evidence", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.evidence", { defaultValue: "行为证据" }) }), _jsx("p", { className: "mt-1 text-ink-2 text-[13px] leading-relaxed", children: ownerDecision.evidenceSummary.value?.ownerExplanation })] })), _jsxs("div", { className: "mt-4", "data-testid": "owner-decision-uncertainty", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.uncertainty", { defaultValue: "风险与不确定性" }) }), ownerDecision.risk.status === 'known' && (ownerDecision.risk.value?.items.length ?? 0) > 0 && (_jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: ownerDecision.risk.value?.items.map((item, index) => (_jsx("li", { children: item.text }, `risk-${index}`))) })), ownerDecision.risk.status === 'known' && (ownerDecision.risk.value?.items.length ?? 0) === 0 && (_jsx("p", { className: "mt-1 text-ink-3 text-[13px]", children: t("principles.detail.ownerDecision.riskNotAssessed", { defaultValue: "尚未做过风险评估;这不代表没有风险。" }) })), ownerDecision.risk.status === 'unknown' && (_jsx("p", { className: "mt-1 text-ink-3 text-[13px]", children: ownerDecision.risk.reason?.ownerText })), ownerDecision.uncertainty.status === 'known' && ownerDecision.uncertainty.value.length > 0 && (_jsx("ul", { className: "mt-2 space-y-1 text-[12px] text-ink-3", children: ownerDecision.uncertainty.value.map((item, index) => (_jsx("li", { children: item.text }, `unc-${index}`))) }))] }), _jsxs("div", { className: "mt-4", "data-testid": "owner-decision-rollback", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.rollback", { defaultValue: "如何停止影响" }) }), ownerDecision.rollback.status === 'known' && (_jsxs(_Fragment, { children: [_jsx("p", { className: "mt-1 text-ink-2 text-[13px] leading-relaxed", children: ownerDecision.rollback.value?.ownerText }), ownerDecision.rollback.value?.stopFuture.status === 'known' && (_jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: ownerDecision.rollback.value.stopFuture.value.map((item, index) => (_jsx("li", { children: item.text }, `rb-${index}`))) })), ownerDecision.rollback.value?.stopFuture.status === 'not_applicable' && (_jsx("p", { className: "mt-1 text-ink-3 text-[12px]", children: ownerDecision.rollback.value.stopFuture.reason.ownerText }))] })), ownerDecision.rollback.status === 'unknown' && (_jsx("p", { className: "mt-1 text-amber text-[13px]", children: ownerDecision.rollback.reason?.ownerText }))] }), ownerDecision.decisionSubjects.some((subject) => subject.state !== 'pending') && (_jsxs("div", { className: "mt-3", "data-testid": "owner-decision-history", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.history", { defaultValue: "决定历史" }) }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: ownerDecision.decisionSubjects.filter((subject) => subject.state !== 'pending').map((subject) => {
408
+ const decidedAt = subject.targetRefs.find((target) => target.kind === 'approval')?.recordedAt;
409
+ const stateKey = `principles.detail.ownerDecision.historyState.${subject.state}`;
410
+ return (_jsxs("li", { children: [t(stateKey, { defaultValue: subject.state }), decidedAt !== undefined ? ` · ${decidedAt.slice(0, 10)}` : '', ' · ', t(`principles.detail.ownerDecision.channel.${subject.channel}`, { defaultValue: subject.channel })] }, `hist-${subject.key}`));
411
+ }) })] })), _jsxs("div", { className: "mt-5 border-t border-line pt-4", "data-testid": "owner-decision-actions", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.decision", { defaultValue: "你的决定" }) }), ownerDecision.availableActions.length === 0 && (_jsx("p", { className: "mt-1 text-ink-3 text-[13px]", children: t("principles.detail.ownerDecision.noActions", { defaultValue: "当前没有可以执行的操作。" }) })), ownerDecision.decisionSubjects.filter((subject) => subject.state === 'pending').length > 0 && (_jsx("div", { className: "mt-3 space-y-3", "data-testid": "owner-decision-subject-materials", children: ownerDecision.decisionSubjects.filter((subject) => subject.state === 'pending').map((subject) => (_jsxs("div", { className: "rounded-[var(--radius-sm)] border border-line p-3", "data-subject-key": subject.key, children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t("principles.detail.ownerDecision.subjectMaterial", { defaultValue: "这项决定的材料" }) }), subject.decisionMaterial.learnedPrinciple.status === 'known' ? (subject.decisionMaterial.learnedPrinciple.value.map((item, index) => (_jsx("p", { className: "mt-1 text-ink-2 text-[13px] leading-relaxed", children: item.text }, `sm-${index}`)))) : (_jsx("p", { className: "mt-1 text-amber text-[13px]", children: subject.decisionMaterial.learnedPrinciple.reason.ownerText })), subject.decisionMaterial.consequence.status === 'known' && (_jsx("p", { className: "mt-1 text-ink-3 text-[12px] leading-relaxed", children: subject.decisionMaterial.consequence.value.map((item) => item.text).join(' ') }))] }, subject.key))) })), _jsx("div", { className: "mt-2 flex flex-wrap gap-3", children: ownerDecision.availableActions.map((action) => (_jsx(Button, { variant: action.semantic === 'reject' ? 'destructive' : action.semantic === 'disable' ? 'outline' : 'default', size: "sm", disabled: actionLoading, "data-action-semantic": action.semantic, onClick: () => {
412
+ setRejectReason("");
413
+ setPendingAction(action);
414
+ }, children: localizeActionLabel(action, t) }, action.key))) }), ownerDecision.blockers.length > 0 && (_jsx("ul", { className: "mt-3 space-y-1 text-[12px] text-ink-3", children: ownerDecision.blockers.map((blocker, index) => (_jsx("li", { "data-testid": "owner-decision-blocker", children: localizeBlocker(blocker.reason.code, blocker.reason.ownerText, t) }, `blk-${index}`))) }))] })] })] })), ownerDecision === null && ownerDecisionUnavailable !== null && (_jsxs("section", { className: "mb-8", "data-testid": "owner-decision-unavailable", children: [_jsx(SectionTitle, { children: t("principles.detail.ownerDecision.title", { defaultValue: "这条原则的决策视图" }) }), _jsxs("div", { className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("p", { className: "text-ink-2 text-[13px]", children: t("principles.detail.ownerDecision.unavailable", { defaultValue: "决策视图暂时不可用,无法据此做决定。" }) }), _jsx("p", { className: "mt-1 text-ink-4 text-[12px] font-mono", children: ownerDecisionUnavailable.reason })] })] })), (governance !== null || governanceUnavailable !== null) && (_jsxs("section", { className: "mb-8", "aria-labelledby": "governance-summary-title", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.title') }), governance !== null ? (_jsxs("div", { "data-testid": "governance-summary", className: `rounded-[var(--radius-md)] border p-4 ${governance.dataQuality.degraded ? 'border-amber/30 bg-amber/5' : 'border-gov/25 bg-gov/5'}`, children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "governance-summary-title", className: "text-[18px] font-semibold text-ink", children: t(`principles.detail.${governance.summary.headlineCode}`, { defaultValue: governance.summary.headlineCode }) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: t(`principles.detail.${governance.summary.reasonCode}`, { defaultValue: governance.summary.reasonCode }) })] }), _jsx("span", { className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: t(`principles.detail.governance.confidence.${governance.dataQuality.degraded ? 'degraded' : 'strong'}`) })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.principleState') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.state.${governance.principleState.value}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.process') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: governance.process.stage === undefined ? t('principles.detail.governance.none') : t(`principles.detail.governance.stage.${governance.process.stage}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.automation') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.automationState.${governance.automation.state}`) })] })] }), _jsxs("div", { "data-testid": "governance-next-action", className: "mt-4 border-l-2 border-l-gov pl-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.nextAction') }), _jsx("p", { className: "text-[13px] text-ink-2", children: t(`principles.detail.${governance.summary.nextActionCode}`, { defaultValue: governance.summary.nextActionCode }) })] }), governance.attention.items.length > 0 && (_jsxs("div", { className: "mt-4", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.blockers') }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: governance.attention.items.map(item => _jsx("li", { children: t(`principles.detail.governance.attention.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.kind}-${item.sourceRef.type}-${item.sourceRef.id}`)) })] })), governance.dataQuality.degraded && (_jsxs("div", { "data-testid": "governance-data-quality", className: "mt-4 rounded-[var(--radius-sm)] border border-amber/20 p-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-amber", children: t('principles.detail.governance.uncertainty') }), _jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: governance.dataQuality.issues.map((item, index) => _jsx("li", { children: t(`principles.detail.governance.issue.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.source}-${item.reasonCode}-${index}`)) })] }))] })) : (_jsxs("div", { "data-testid": "governance-data-quality", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h2", { id: "governance-summary-title", className: "text-[16px] font-semibold text-ink", children: t('principles.detail.governance.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: t('principles.detail.governance.unavailableReason') }), _jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: t('principles.detail.governance.unavailableNextAction') })] }))] })), receipts !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "receipt-history-title", children: [_jsx(SectionTitle, { children: t('principles.detail.receipts.title') }), receipts.status === 'ok' ? (_jsxs("div", { "data-testid": "receipt-history", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("h2", { id: "receipt-history-title", className: "text-[15px] font-semibold text-ink", children: receiptPresentation?.headlineKey === 'principles.detail.receipts.headline'
465
415
  ? t(receiptPresentation.headlineKey, {
466
416
  defaultValue: '',
467
417
  effectCount: receipts.effectCount,
468
418
  lastEffectAt: receipts.lastEffectAt ? formatDate(receipts.lastEffectAt, i18n.language) : '',
469
419
  })
470
- : t('principles.detail.receipts.headlinePresence') }), _jsx("span", { "data-testid": "receipt-history-counts", className: "font-mono text-[11px] text-ink-3", children: t('principles.detail.receipts.counts', { effectCount: receipts.effectCount, presenceCount: receipts.presenceCount }) })] }), receiptPresentation?.showZeroEffectExplanation && (_jsx("p", { "data-testid": "receipt-history-zero-effect", className: "mt-2 text-[12px] leading-relaxed text-ink-3", children: t('principles.detail.receipts.zeroEffect') })), receipts.events.length > 0 ? (_jsx("ul", { className: "mt-3 space-y-1.5", children: receipts.events.map((event, index) => (_jsxs("li", { className: `flex flex-wrap items-baseline gap-2 text-[13px] ${event.level === 'presence' ? 'text-ink-4' : 'text-ink-2'}`, children: [_jsx("span", { className: "font-mono text-[11px] text-ink-4", children: event.createdAt.slice(0, 16).replace('T', ' ') }), _jsx("span", { className: "font-mono text-[11px]", children: t(`principles.detail.receipts.kind.${event.kind}`, { defaultValue: event.kind }) }), event.digest && _jsx("span", { className: "min-w-0 flex-1 truncate", title: event.digest, children: event.digest })] }, `${event.createdAt}-${index}`))) })) : (_jsx("p", { className: "mt-2 text-[13px] text-ink-3", children: t('principles.detail.receipts.empty') })), _jsx("p", { className: "mt-3 text-[11px] leading-relaxed text-ink-4", children: t('principles.detail.receipts.note') }), _jsx("div", { className: "mt-3 border-t border-line pt-3", children: _jsx(ReceiptCoverageDisclosure, { coverage: receipts.coverage }) })] })) : (_jsxs("div", { "data-testid": "receipt-history-degraded", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h3", { className: "text-[13px] font-semibold text-ink", children: i18n.t(getReceiptSourceStatusLabelKey(receipts.coverage.sourceStatus)) }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: receipts.reason ?? t('principles.detail.receipts.unavailableReason') }), receipts.nextAction && _jsx("p", { className: "mt-1 text-[12px] text-ink-3", children: receipts.nextAction })] }))] })), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.channel") }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: t("principles.detail.channelPromptReversible") })] }), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.whyExists") }), _jsxs("div", { className: "space-y-3 mb-6", children: [_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.applicable", { defaultValue: "适用场景" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: principle.triggerPattern || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] }), _jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.expectedBehavior", { defaultValue: "预期行为" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: principle.action || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] })] }), principle.derivedFromPainIds.length > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.evidence") }), _jsxs("div", { className: "mt-2 flex items-center gap-3 flex-wrap", children: [_jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: t("principles.detail.evidenceCount", {
420
+ : t('principles.detail.receipts.headlinePresence') }), _jsx("span", { "data-testid": "receipt-history-counts", className: "font-mono text-[11px] text-ink-3", children: t('principles.detail.receipts.counts', { effectCount: receipts.effectCount, presenceCount: receipts.presenceCount }) })] }), receiptPresentation?.showZeroEffectExplanation && (_jsx("p", { "data-testid": "receipt-history-zero-effect", className: "mt-2 text-[12px] leading-relaxed text-ink-3", children: t('principles.detail.receipts.zeroEffect') })), receipts.events.length > 0 ? (_jsx("ul", { className: "mt-3 space-y-1.5", children: receipts.events.map((event, index) => (_jsxs("li", { className: `flex flex-wrap items-baseline gap-2 text-[13px] ${event.level === 'presence' ? 'text-ink-4' : 'text-ink-2'}`, children: [_jsx("span", { className: "font-mono text-[11px] text-ink-4", children: event.createdAt.slice(0, 16).replace('T', ' ') }), _jsx("span", { className: "font-mono text-[11px]", children: t(`principles.detail.receipts.kind.${event.kind}`, { defaultValue: event.kind }) }), event.digest && _jsx("span", { className: "min-w-0 flex-1 truncate", title: event.digest, children: event.digest })] }, `${event.createdAt}-${index}`))) })) : (_jsx("p", { className: "mt-2 text-[13px] text-ink-3", children: t('principles.detail.receipts.empty') })), _jsx("p", { className: "mt-3 text-[11px] leading-relaxed text-ink-4", children: t('principles.detail.receipts.note') }), _jsx("div", { className: "mt-3 border-t border-line pt-3", children: _jsx(ReceiptCoverageDisclosure, { coverage: receipts.coverage }) })] })) : (_jsxs("div", { "data-testid": "receipt-history-degraded", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h3", { className: "text-[13px] font-semibold text-ink", children: i18n.t(getReceiptSourceStatusLabelKey(receipts.coverage.sourceStatus)) }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: receipts.reason ?? t('principles.detail.receipts.unavailableReason') }), receipts.nextAction && _jsx("p", { className: "mt-1 text-[12px] text-ink-3", children: receipts.nextAction })] }))] })), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.whyExists") }), _jsxs("div", { className: "space-y-3 mb-6", children: [_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.applicable", { defaultValue: "适用场景(原始触发配置)" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed font-mono break-all", children: principle.triggerPattern || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] }), _jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.expectedBehavior", { defaultValue: "预期行为(原始建议)" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: principle.action || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] })] }), principle.derivedFromPainIds.length > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.evidence") }), _jsxs("div", { className: "mt-2 flex items-center gap-3 flex-wrap", children: [_jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: t("principles.detail.evidenceCount", {
471
421
  defaultValue: "已关联 {{count}} 条证据记录",
472
422
  count: principle.derivedFromPainIds.length,
473
423
  }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
@@ -487,7 +437,7 @@ export function PrincipleDetailPage() {
487
437
  }
488
438
  }, className: "font-mono text-[11px] h-7", children: evidenceCopied
489
439
  ? t("principles.detail.evidenceCopied", { defaultValue: "已复制" })
490
- : t("principles.detail.copyEvidenceId", { defaultValue: "复制证据 ID" }) }), _jsx("button", { type: "button", onClick: () => setEvidenceExpanded((v) => !v), className: "font-mono text-[11px] text-ink-4 hover:text-ink-3 transition-colors underline-offset-2 hover:underline", children: t("principles.detail.expandTechDetails", { defaultValue: "展开技术细节" }) }), _jsx(Link, { to: "/evidence", className: "font-mono text-[11px] text-gov hover:underline transition-colors", children: t("principles.detail.viewEvidenceChain", { defaultValue: "在证据链页查看 →" }) })] }), evidenceExpanded && (_jsx("div", { className: "mt-3 p-3 bg-paper-2 border border-line rounded-[var(--radius-sm)]", children: _jsx("div", { className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-[13px]", children: principle.derivedFromPainIds.map((painId, idx) => (_jsxs("div", { className: "contents", children: [_jsx("span", { className: "font-mono text-ink-4", children: t("principles.detail.evidenceIdLabel", {
440
+ : t("principles.detail.copyEvidenceId", { defaultValue: "复制证据 ID" }) }), _jsx("button", { type: "button", onClick: () => setEvidenceExpanded((v) => !v), className: "font-mono text-[11px] text-ink-4 hover:text-ink-3 transition-colors underline-offset-2 hover:underline", children: t("principles.detail.expandTechDetails", { defaultValue: "展开技术细节" }) }), _jsx(Link, { to: "/evidence", className: "font-mono text-[11px] text-gov hover:underline transition-colors", children: t("principles.detail.viewEvidenceChain", { defaultValue: "在证据链页查看 →" }) })] }), evidenceExpanded && (_jsx("div", { className: "mt-3 p-3 bg-paper-2 border border-line rounded-[var(--radius-sm)]", children: _jsx("div", { className: "grid grid-cols-[auto 1fr] gap-x-4 gap-y-2 text-[13px]", children: principle.derivedFromPainIds.map((painId, idx) => (_jsxs("div", { className: "contents", children: [_jsx("span", { className: "font-mono text-ink-4", children: t("principles.detail.evidenceIdLabel", {
491
441
  defaultValue: "证据 {{n}} ID",
492
442
  n: idx + 1,
493
443
  }) }), _jsx("span", { className: "font-mono text-ink-2 break-all", children: painId })] }, `${painId}-${idx}`))) }) }))] }))] }), _jsxs("section", { className: "mb-8 border-t border-line pt-6", children: [_jsx(SectionTitle, { children: t("principles.detail.ownerReflection") }), _jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "border-l-2 border-l-gov pl-3 text-ink-2 text-[13px] leading-relaxed", children: t("principles.detail.reflectionQ1") }), _jsx("div", { className: "border-l-2 border-l-gov pl-3 text-ink-2 text-[13px] leading-relaxed", children: t("principles.detail.reflectionQ2") }), _jsx("div", { className: "border-l-2 border-l-gov pl-3 text-ink-2 text-[13px] leading-relaxed", children: t("principles.detail.reflectionQ3") })] })] }), _jsxs("details", { className: "mb-8 border border-line rounded-[var(--radius-md)]", children: [_jsx("summary", { className: "px-4 py-3 cursor-pointer font-mono text-[11px] uppercase tracking-[0.1em] text-ink-3 hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", children: t("principles.detail.trajectory") }), _jsxs("div", { className: "px-4 pb-4 border-t border-line pt-4", children: [governance !== null && (_jsxs("section", { className: "mb-6", "data-testid": "governance-timeline", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.timeline') }), governance.timeline.length === 0 ? _jsx("p", { className: "text-[13px] text-ink-4", children: t('principles.detail.governance.timelineEmpty') }) : (_jsx("ol", { className: "space-y-3 border-l border-line pl-4", children: governance.timeline.map((event, index) => {
@@ -497,7 +447,15 @@ export function PrincipleDetailPage() {
497
447
  // Fallback: show loading state or no-data message
498
448
  ['evidence', 'diagnosis', 'proposal', 'review', 'deploy', 'behavior'].map((key) => (_jsxs("div", { className: "flex gap-4", children: [_jsx("span", { className: "font-mono text-[12px] text-ink-3 min-w-[80px]", children: t(STAGE_LABEL_KEYS[key] ?? `principles.detail.stage${key}`, { defaultValue: key }) }), _jsx("div", { className: "flex-1", children: _jsx("p", { className: "text-ink-4 text-[13px]", children: trajectory === null
499
449
  ? t("principles.detail.trajectoryLoadFailed", { defaultValue: "无法加载轨迹数据" })
500
- : "—" }) })] }, key)))) }), hasRules && lifecycle && (_jsxs("div", { className: "mt-6 pt-4 border-t border-line", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.lifecycleMetrics", { defaultValue: "生命周期指标" }) }), _jsx("p", { className: "text-amber text-[12px] font-mono mt-1 mb-3", children: t("principles.detail.lifecycleNote") }), lifecycle.adherence.insufficientData ? (_jsx("p", { className: "text-ink-3 text-[13px] leading-relaxed", children: t("principles.detail.insufficientData") })) : (_jsxs("div", { children: [_jsxs("p", { className: "text-ink-2 text-[14px]", children: [t("principles.detail.adherenceRate", { defaultValue: "遵守率" }), ": ", lifecycle.adherence.rate !== null ? `${(lifecycle.adherence.rate * 100).toFixed(1)}%` : "—"] }), _jsx("p", { className: "text-ink-3 text-[12px] mt-1", children: lifecycle.adherence.note })] })), lifecycle.ruleMetrics.length > 0 && (_jsx("div", { className: "mt-3", children: lifecycle.ruleMetrics.map((rm) => (_jsxs("div", { className: "flex gap-3 text-[12px] font-mono text-ink-3", children: [_jsx("span", { children: rm.ruleId }), _jsxs("span", { children: [t("principles.detail.triggered", { defaultValue: "触发" }), ": ", rm.triggered] })] }, rm.ruleId))) }))] })), hasRules && !lifecycle && (_jsx("div", { className: "mt-6 pt-4 border-t border-line", children: _jsx("p", { className: "text-ink-3 text-[13px]", children: t("principles.detail.lifecycleLoadFailed", { defaultValue: "无法加载生命周期指标。你可以稍后重试。" }) }) }))] })] }), showDecisionControls && (_jsxs("div", { className: "border-t border-line pt-6 mt-6", children: [_jsx(SectionTitle, { children: t("principles.detail.decisionTitle", { defaultValue: "决策操作" }) }), _jsxs("div", { className: "flex gap-3 flex-wrap items-center", children: [_jsx(Button, { variant: "default", onClick: handleApprove, disabled: !isActionable || actionLoading, children: t("principles.detail.approve") }), _jsx(Button, { variant: "outline", onClick: handleEdit, disabled: !isActionable || actionLoading, children: t("principles.detail.editAction", { defaultValue: "编辑" }) }), _jsx(Button, { variant: "outline", onClick: handlePark, disabled: true, children: t("principles.detail.park") }), _jsx(Button, { variant: "destructive", onClick: handleReject, disabled: !isActionable || actionLoading, children: t("principles.detail.reject") }), _jsxs("span", { className: "text-ink-4 text-[13px]", children: ["(", t("principles.detail.parkUnavailable", { defaultValue: "暂存尚未可用" }), ")"] })] }), !isActionable && (_jsxs("p", { className: "text-danger text-[13px] mt-3 font-mono", children: [t("principles.detail.unactionableReasonPrefix", { defaultValue: "不可操作原因: " }), t(reasonKey, { defaultValue: defaultReason })] })), isActionable && showConfirm && (_jsxs("div", { className: "mt-4 p-3 bg-gov/5 border border-gov/20 rounded-[var(--radius-md)]", children: [_jsx("p", { className: "text-ink-2 text-[13px] mb-3", children: t("principles.detail.confirmApprove") }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { variant: "default", size: "sm", onClick: confirmApprove, disabled: actionLoading, children: t("principles.detail.confirm") }), _jsx(Button, { variant: "outline", size: "sm", onClick: cancelConfirm, children: t("principles.detail.cancel") })] })] })), isActionable && showRejectInput && (_jsxs("div", { className: "mt-4 p-3 border border-danger/20 rounded-[var(--radius-md)]", children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.rejectReasonLabel", { defaultValue: "拒绝原因" }) }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), placeholder: t("principles.detail.rejectReasonPlaceholder"), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.rejectReasonPlaceholder") }), _jsxs("div", { className: "flex gap-2 mt-2", children: [_jsx(Button, { variant: "destructive", size: "sm", onClick: confirmReject, disabled: !rejectReason.trim() || actionLoading, children: t("principles.detail.confirmReject") }), _jsx(Button, { variant: "outline", size: "sm", onClick: cancelReject, children: t("principles.detail.cancel") })] })] })), isActionable && showEditInput && (_jsxs("div", { className: "mt-4 p-3 border border-gov/20 rounded-[var(--radius-md)]", children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.editReasonLabel", { defaultValue: "编辑原因" }) }), _jsxs("div", { className: "mb-2 text-ink-4 text-[12px] font-mono", children: [t("principles.detail.currentArtifactLabel", { defaultValue: "当前工件" }), ": ", currentArtifactId] }), _jsx("input", { type: "text", value: newArtifactId, onChange: (e) => setNewArtifactId(e.target.value), placeholder: t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] mb-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }) }), _jsx("textarea", { value: editReason, onChange: (e) => setEditReason(e.target.value), placeholder: t("principles.detail.editReasonPlaceholder", { defaultValue: "请说明编辑原因" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov" }), _jsxs("div", { className: "flex gap-2 mt-2", children: [_jsx(Button, { variant: "default", size: "sm", onClick: confirmEdit, disabled: !editReason.trim() || !newArtifactId.trim() || actionLoading, children: t("principles.detail.confirmEdit", { defaultValue: "确认编辑" }) }), _jsx(Button, { variant: "outline", size: "sm", onClick: cancelEdit, children: t("principles.detail.cancel") })] })] }))] })), governanceBlock !== null && (_jsxs("div", { className: "border-t border-line pt-6 mt-6", "data-testid": "governance-decision-blocked", children: [_jsx(SectionTitle, { children: t("principles.detail.decisionTitle", { defaultValue: "决策操作" }) }), _jsx("p", { className: "text-danger text-[13px] mt-3 font-mono", "data-testid": "governance-decision-blocked-reason", children: governanceBlock.source === 'server'
501
- ? governanceBlock.reason
502
- : t(governanceBlock.reasonKey) }), governanceBlockedNextAction !== undefined && (_jsxs("p", { className: "text-ink-3 text-[13px] mt-1 font-mono", "data-testid": "governance-decision-blocked-next-action", children: [_jsx("span", { className: "font-medium", children: t(GOVERNANCE_BLOCK_I18N_KEYS.nextActionLabel) }), " ", governanceBlockedNextAction] }))] }))] }) }));
450
+ : "—" }) })] }, key)))) }), hasRules && lifecycle && (_jsxs("div", { className: "mt-6 pt-4 border-t border-line", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.lifecycleMetrics", { defaultValue: "生命周期指标" }) }), _jsx("p", { className: "text-amber text-[12px] font-mono mt-1 mb-3", children: t("principles.detail.lifecycleNote") }), lifecycle.adherence.insufficientData ? (_jsx("p", { className: "text-ink-3 text-[13px] leading-relaxed", children: t("principles.detail.insufficientData") })) : (_jsxs("div", { children: [_jsxs("p", { className: "text-ink-2 text-[14px]", children: [t("principles.detail.adherenceRate", { defaultValue: "遵守率" }), ": ", lifecycle.adherence.rate !== null ? `${(lifecycle.adherence.rate * 100).toFixed(1)}%` : "—"] }), _jsx("p", { className: "text-ink-3 text-[12px] mt-1", children: lifecycle.adherence.note })] })), lifecycle.ruleMetrics.length > 0 && (_jsx("div", { className: "mt-3", children: lifecycle.ruleMetrics.map((rm) => (_jsxs("div", { className: "flex gap-3 text-[12px] font-mono text-ink-3", children: [_jsx("span", { children: rm.ruleId }), _jsxs("span", { children: [t("principles.detail.triggered", { defaultValue: "触发" }), ": ", rm.triggered] })] }, rm.ruleId))) }))] })), hasRules && !lifecycle && (_jsx("div", { className: "mt-6 pt-4 border-t border-line", children: _jsx("p", { className: "text-ink-3 text-[13px]", children: t("principles.detail.lifecycleLoadFailed", { defaultValue: "无法加载生命周期指标。你可以稍后重试。" }) }) }))] })] }), pendingAction !== null && (_jsxs("div", { className: "border-t border-line pt-6 mt-6", "data-testid": "owner-decision-confirm", children: [_jsx(SectionTitle, { children: pendingAction.label }), _jsxs("div", { className: "mt-2 p-3 bg-gov/5 border border-gov/20 rounded-[var(--radius-md)]", children: [pendingAction.expectedConsequence.status === 'known' && (_jsx("div", { className: "mb-3", children: pendingAction.expectedConsequence.value.map((item, index) => (_jsx("p", { className: "text-ink-2 text-[13px] leading-relaxed mb-1", children: item.text }, `exp-${index}`))) })), _jsx("p", { className: "text-ink-2 text-[13px] mb-3", children: pendingAction.confirmation.text }), pendingAction.requirements.some((req) => req.name === 'newArtifactId') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.editNewArtifactLabel", { defaultValue: "新的已验证工件 ID" }) }), _jsx("input", { type: "text", value: newArtifactId, onChange: (e) => setNewArtifactId(e.target.value), placeholder: t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] mb-2 font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }) })] })), pendingAction.requirements.some((req) => req.name === 'reason') && (_jsxs(_Fragment, { children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("principles.detail.rejectReasonLabel", { defaultValue: "拒绝原因" }) }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), placeholder: t("principles.detail.rejectReasonPlaceholder"), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.detail.rejectReasonPlaceholder") })] })), _jsxs("div", { className: "flex gap-2 mt-3", children: [_jsx(Button, { variant: pendingAction.semantic === 'reject' ? 'destructive' : 'default', size: "sm", disabled: actionLoading
451
+ || (pendingAction.requirements.some((req) => req.name === 'reason') && !rejectReason.trim())
452
+ || (pendingAction.requirements.some((req) => req.name === 'newArtifactId') && !newArtifactId.trim()), onClick: () => {
453
+ const action = pendingAction;
454
+ void submitOwnerAction(action, rejectReason.trim() === '' ? undefined : rejectReason.trim());
455
+ }, children: t("principles.detail.confirm", { defaultValue: "确认" }) }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => { setPendingAction(null); setRejectReason(""); setNewArtifactId(""); }, children: t("principles.detail.cancel") })] })] })] }))] }) }));
456
+ }
457
+ /** Small presentational block: known narrative items or the honest unknown. */
458
+ function OwnerNarrativeBlock({ label, field, testId }) {
459
+ const rendered = narrativeTexts(field);
460
+ return (_jsxs("div", { className: "mt-4", "data-testid": testId, children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: label }), rendered.status === 'known' ? (rendered.texts.length > 0 ? (rendered.texts.map((text, index) => (_jsx("p", { className: "mt-1 text-ink-2 text-[14px] leading-relaxed", children: text }, `${testId}-${index}`)))) : (_jsx("p", { className: "mt-1 text-ink-4 text-[13px]", children: "\u2014" }))) : (_jsx("p", { className: "mt-1 text-ink-3 text-[13px] leading-relaxed", children: rendered.reason }))] }));
503
461
  }