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.
- package/console/dist/server/index.js +19 -0
- package/console/dist/server/models/OwnerDecisionViewModel.d.ts +65 -0
- package/console/dist/server/models/OwnerDecisionViewModel.js +686 -0
- package/console/dist/server/routes/owner-decision.d.ts +40 -0
- package/console/dist/server/routes/owner-decision.js +88 -0
- package/console/dist/server/routes/principles.d.ts +3 -0
- package/console/dist/server/routes/principles.js +3 -0
- package/console/dist/ui/api.d.ts +7 -3
- package/console/dist/ui/api.js +13 -2
- package/console/dist/ui/i18n/en.json +70 -4
- package/console/dist/ui/i18n/zh-CN.json +70 -4
- package/console/dist/ui/pages/focus/FocusPage.js +25 -2
- package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +24 -0
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +168 -210
- package/console/dist/ui/pages/principles/PrinciplesPage.js +25 -10
- package/console/dist/ui/utils/enum-labels.d.ts +1 -1
- package/console/dist/ui/utils/enum-labels.js +8 -0
- package/console/dist/ui/utils/validators.d.ts +99 -1
- package/console/dist/ui/utils/validators.js +115 -0
- package/console/dist/web/assets/app.css +3 -0
- package/console/dist/web/assets/app.js +590 -349
- package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js +25 -0
- package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +16 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +22 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +7 -0
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +4 -0
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts +2 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js +559 -0
- package/core/dist/runtime-v2/owner-decision/__tests__/owner-decision-view.test.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts +7868 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js +367 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view-contract.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts +415 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.js +856 -0
- package/core/dist/runtime-v2/owner-decision/owner-decision-view.js.map +1 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts +72 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.d.ts.map +1 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.js +176 -0
- package/core/dist/runtime-v2/owner-decision/semantic-selector.js.map +1 -0
- package/package.json +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +47 -7
- package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +84 -0
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +215 -38
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/plugin/dist/bundle.js +526 -526
- package/plugin/dist/governance-audit.js +205 -205
- package/plugin/dist/rulehost-evidence.js +246 -246
- 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:
|
|
54
|
-
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 [
|
|
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
|
-
// ──
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
defaultValue:
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
//
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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(
|
|
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:
|
|
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") })] })] }),
|
|
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.
|
|
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-[
|
|
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: "无法加载生命周期指标。你可以稍后重试。" }) }) }))] })] }),
|
|
501
|
-
|
|
502
|
-
|
|
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
|
}
|