|
823 | 823 | controlParked: "已停车", |
824 | 824 | controlOwnerDecision: "等待我决定", |
825 | 825 | controlCandidateBoard: "候选生命周期", |
826 | | - controlDataReady: "快照已加载", |
| 826 | + controlDataReady: "快照已同步(不代表策略可运行)", |
827 | 827 | controlDataStale: "快照已过期", |
828 | 828 | controlDataUnavailable: "等待可用快照", |
| 829 | + controlAttentionResearchOnly: "仅研究,未授予执行权限", |
| 830 | + controlAttentionRequired: "研究状态需要关注", |
| 831 | + controlAttentionUnavailable: "尚无已验证研究状态", |
829 | 832 | controlComputedAt: "最近计算:{time}", |
830 | 833 | controlLoginNotice: "登录后读取私有全局快照;没有快照时不会展示虚构信息。", |
831 | 834 | controlStaleNotice: "全局快照已超过允许的新鲜度窗口;页面保留原始记录,但不把它作为当前结论。", |
832 | 835 | controlUnavailableNotice: "还没有可用的全局快照;当前页面保持 fail-closed 空状态。", |
833 | 836 | controlUpstreamNotice: "快照已加载,但有 {count} 个上游提示;缺失信息不会被补成推断结论。", |
| 837 | + controlAttentionNotice: "研究状态需要关注(延期 {deferred}、停车 {parked}、信号 {signals})。快照送达不等于策略验证或执行授权;系统保持不交易。", |
834 | 838 | controlEmptyCandidates: "暂无可展示的候选快照。", |
835 | 839 | controlNoRecommendation: "没有可用的机器建议。", |
836 | 840 | controlStageMeta: "阶段:{stage} · 状态:{status} · 证据:{freshness}", |
|
1040 | 1044 | controlParked: "Parked", |
1041 | 1045 | controlOwnerDecision: "Needs my decision", |
1042 | 1046 | controlCandidateBoard: "Candidate lifecycle", |
1043 | | - controlDataReady: "Snapshot loaded", |
| 1047 | + controlDataReady: "Snapshot synced (not strategy readiness)", |
1044 | 1048 | controlDataStale: "Snapshot is stale", |
1045 | 1049 | controlDataUnavailable: "Waiting for a usable snapshot", |
| 1050 | + controlAttentionResearchOnly: "Research only; no execution authority", |
| 1051 | + controlAttentionRequired: "Research state needs attention", |
| 1052 | + controlAttentionUnavailable: "No verified research state", |
1046 | 1053 | controlComputedAt: "Last computed: {time}", |
1047 | 1054 | controlLoginNotice: "Sign in to read the private global snapshot. Missing data is never invented.", |
1048 | 1055 | controlStaleNotice: "This snapshot is outside its freshness window. The record remains visible, but is not a current conclusion.", |
1049 | 1056 | controlUnavailableNotice: "No usable global snapshot is available yet. This page remains fail-closed and empty.", |
1050 | 1057 | controlUpstreamNotice: "The snapshot loaded with {count} upstream notice(s); missing information is never inferred.", |
| 1058 | + controlAttentionNotice: "Research needs attention ({deferred} deferred, {parked} parked, {signals} signal(s)). Snapshot delivery is not strategy validation or execution authority; the system remains no-order.", |
1051 | 1059 | controlEmptyCandidates: "No candidate snapshot is available to display.", |
1052 | 1060 | controlNoRecommendation: "No machine recommendation is available.", |
1053 | 1061 | controlStageMeta: "Stage: {stage} · status: {status} · evidence: {freshness}", |
|
1294 | 1302 | data_status: "unavailable", |
1295 | 1303 | computed_at: null, |
1296 | 1304 | summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 }, |
| 1305 | + attention: { status: "unavailable", reason_codes: ["control_plane_source_unavailable"] }, |
1297 | 1306 | candidates: [], |
1298 | 1307 | policy: { p4_p5_automation: "not_configured", p6_owner_decision_required: true }, |
1299 | 1308 | errors: [], |
|
3373 | 3382 | data_status: ["ready", "stale", "unavailable"].includes(payload.data_status) ? payload.data_status : "unavailable", |
3374 | 3383 | computed_at: payload.computed_at || null, |
3375 | 3384 | summary: payload.summary && typeof payload.summary === "object" ? payload.summary : {}, |
| 3385 | + attention: payload.attention && typeof payload.attention === "object" && !Array.isArray(payload.attention) |
| 3386 | + ? { |
| 3387 | + status: ["research_only", "attention_required", "unavailable"].includes(payload.attention.status) |
| 3388 | + ? payload.attention.status |
| 3389 | + : "unavailable", |
| 3390 | + reason_codes: Array.isArray(payload.attention.reason_codes) ? payload.attention.reason_codes : [], |
| 3391 | + } |
| 3392 | + : { status: "unavailable", reason_codes: ["control_plane_attention_missing"] }, |
3376 | 3393 | candidates: candidates.filter((item) => item && typeof item === "object" && item.lifecycle && typeof item.lifecycle === "object"), |
3377 | 3394 | policy: payload.policy && typeof payload.policy === "object" ? payload.policy : {}, |
3378 | 3395 | errors: Array.isArray(payload.errors) ? payload.errors : [], |
|
3385 | 3402 | : (status === "stale" ? t("controlDataStale") : t("controlDataUnavailable")); |
3386 | 3403 | } |
3387 | 3404 |
|
| 3405 | + function controlPlaneAttentionText(attention) { |
| 3406 | + const status = attention?.status || "unavailable"; |
| 3407 | + if (status === "research_only") return t("controlAttentionResearchOnly"); |
| 3408 | + if (status === "attention_required") return t("controlAttentionRequired"); |
| 3409 | + return t("controlAttentionUnavailable"); |
| 3410 | + } |
| 3411 | + |
3388 | 3412 | function renderControlPlane() { |
3389 | 3413 | const payload = state.controlPlane.payload; |
3390 | 3414 | const summary = payload.summary || {}; |
3391 | | - el("control-plane-status").textContent = controlPlaneDataStatusText(payload.data_status); |
| 3415 | + el("control-plane-status").textContent = `${controlPlaneDataStatusText(payload.data_status)} · ${controlPlaneAttentionText(payload.attention)}`; |
3392 | 3416 | el("control-plane-computed-at").textContent = payload.computed_at |
3393 | 3417 | ? t("controlComputedAt").replace("{time}", new Date(payload.computed_at).toLocaleString()) |
3394 | 3418 | : t("controlComputedAt").replace("{time}", "—"); |
|
3404 | 3428 | notice.textContent = t("controlStaleNotice"); |
3405 | 3429 | } else if (payload.data_status !== "ready") { |
3406 | 3430 | notice.textContent = t("controlUnavailableNotice"); |
| 3431 | + } else if (payload.attention?.status === "attention_required") { |
| 3432 | + notice.textContent = t("controlAttentionNotice") |
| 3433 | + .replace("{deferred}", String(Number(summary.deferred) || 0)) |
| 3434 | + .replace("{parked}", String(Number(summary.parked) || 0)) |
| 3435 | + .replace("{signals}", String(payload.attention.reason_codes?.length || 0)); |
3407 | 3436 | } else if (payload.errors?.length) { |
3408 | 3437 | notice.textContent = t("controlUpstreamNotice").replace("{count}", payload.errors.length); |
3409 | 3438 | } else { |
|
3793 | 3822 | data_status: "unavailable", |
3794 | 3823 | computed_at: null, |
3795 | 3824 | summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 }, |
| 3825 | + attention: { status: "unavailable", reason_codes: ["control_plane_request_failed"] }, |
3796 | 3826 | candidates: [], |
3797 | 3827 | policy: { p4_p5_automation: "not_configured", p6_owner_decision_required: true }, |
3798 | 3828 | errors: ["control_plane_request_failed"], |
|
0 commit comments