create-principles-disciple 1.146.2 → 1.146.3
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/_release/product-identity.json +2 -2
- package/console/dist/server/routes/health.js +17 -0
- package/console/dist/server/routes/update-transaction.js +63 -3
- package/console/dist/ui/i18n/en.json +2 -0
- package/console/dist/ui/i18n/zh-CN.json +2 -0
- package/console/dist/ui/pages/settings/UpdatePage.js +1 -1
- package/console/dist/ui/utils/validators.d.ts +10 -0
- package/console/dist/ui/utils/validators.js +11 -1
- package/console/dist/web/assets/app.js +16 -16
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts +2 -0
- package/core/dist/runtime-v2/internalization/bash-risk.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/bash-risk.js +78 -0
- package/core/dist/runtime-v2/internalization/bash-risk.js.map +1 -1
- package/core/package.json +1 -1
- package/dist/installer.d.ts +81 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +480 -117
- package/dist/installer.js.map +1 -1
- package/dist/update/console-surface.d.ts +1 -1
- package/dist/update/console-surface.d.ts.map +1 -1
- package/dist/update/console-surface.js +1 -1
- package/dist/update/console-surface.js.map +1 -1
- package/dist/update/transaction-journal.d.ts +47 -0
- package/dist/update/transaction-journal.d.ts.map +1 -1
- package/dist/update/transaction-journal.js +87 -2
- package/dist/update/transaction-journal.js.map +1 -1
- package/host-runtime/package.json +1 -1
- package/package.json +2 -2
- package/plugin/dist/bundle.js +385 -385
- package/plugin/dist/rulehost-evidence.js +16 -16
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/dist/installer.d.ts +81 -0
- package/release-manager/dist/installer.d.ts.map +1 -1
- package/release-manager/dist/installer.js +480 -117
- package/release-manager/dist/installer.js.map +1 -1
- package/release-manager/dist/update/console-surface.d.ts +1 -1
- package/release-manager/dist/update/console-surface.d.ts.map +1 -1
- package/release-manager/dist/update/console-surface.js +1 -1
- package/release-manager/dist/update/console-surface.js.map +1 -1
- package/release-manager/dist/update/transaction-journal.d.ts +47 -0
- package/release-manager/dist/update/transaction-journal.d.ts.map +1 -1
- package/release-manager/dist/update/transaction-journal.js +87 -2
- package/release-manager/dist/update/transaction-journal.js.map +1 -1
- package/release-manager/package.json +2 -2
|
@@ -2,6 +2,16 @@ import { HealthCheckModel } from '../models/HealthCheckModel.js';
|
|
|
2
2
|
import { CodexGovernanceHealthModel } from '../models/CodexGovernanceHealthModel.js';
|
|
3
3
|
import { sendSuccess, sendError } from '../utils/response.js';
|
|
4
4
|
const MODEL_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
/**
|
|
6
|
+
* PRI-1069/G1 P0-1 (SPEC v1.2 §14): one-shot launcher nonce, read once at
|
|
7
|
+
* process start. The installer spawns this server with
|
|
8
|
+
* PD_CONSOLE_VERIFY_NONCE in the child env; the health response echoes it so
|
|
9
|
+
* the probe can bind the answer to THIS child instance. Absent on normal
|
|
10
|
+
* (user-launched) consoles. Never logged.
|
|
11
|
+
*/
|
|
12
|
+
const VERIFY_NONCE = process.env.PD_CONSOLE_VERIFY_NONCE && process.env.PD_CONSOLE_VERIFY_NONCE.length > 0
|
|
13
|
+
? process.env.PD_CONSOLE_VERIFY_NONCE
|
|
14
|
+
: null;
|
|
5
15
|
const models = new Map();
|
|
6
16
|
function getModel(workspaceDir) {
|
|
7
17
|
const cached = models.get(workspaceDir);
|
|
@@ -69,6 +79,13 @@ export async function handleHealthRoute(req, res, options) {
|
|
|
69
79
|
...health,
|
|
70
80
|
authenticationMode: options.authenticationMode,
|
|
71
81
|
codexGovernance,
|
|
82
|
+
// PRI-1069/G1 P0-1: installer identity binding. When the launching
|
|
83
|
+
// installer passes a one-shot nonce via env, echo it back so the probe
|
|
84
|
+
// can prove THIS child answered — an old console, another local service,
|
|
85
|
+
// or a proxy 502 cannot know the value. The nonce is short-lived,
|
|
86
|
+
// machine-local, never logged here, and absent entirely for consoles
|
|
87
|
+
// launched without it (normal user launches), keeping the legacy shape.
|
|
88
|
+
...(VERIFY_NONCE !== null ? { verifyNonce: VERIFY_NONCE } : {}),
|
|
72
89
|
});
|
|
73
90
|
}
|
|
74
91
|
catch (error) {
|
|
@@ -90,6 +90,10 @@ async function recoveryStatus(res) {
|
|
|
90
90
|
const unfinished = [];
|
|
91
91
|
const superseded = [];
|
|
92
92
|
const broken = [];
|
|
93
|
+
// P0-4: terminal `failed` transactions whose detail carries a restore
|
|
94
|
+
// inconsistency marker — the install on disk is NOT what the terminal state
|
|
95
|
+
// implies. They must stay visible in the recovery list.
|
|
96
|
+
const recoveryRequired = [];
|
|
93
97
|
// PRI-896: a non-terminal journal is only an ALARM if the product's own
|
|
94
98
|
// recovery decision cannot explain it away. The live release record is the
|
|
95
99
|
// evidence for that, so a missing/corrupt active record means NO evidence —
|
|
@@ -117,8 +121,26 @@ async function recoveryStatus(res) {
|
|
|
117
121
|
try {
|
|
118
122
|
const read = journal.readTransactionJournalForRecovery(path.join(paths.transactionsDir, entry.name));
|
|
119
123
|
const fact = factFromTransitions(journal, transactionId, read);
|
|
120
|
-
if (fact.terminal)
|
|
124
|
+
if (fact.terminal) {
|
|
125
|
+
// P0-4: terminal facts are normally closed history — EXCEPT a
|
|
126
|
+
// `failed` whose detail says the physical restore did not (or could
|
|
127
|
+
// not) complete. That combination is a live recovery need hiding
|
|
128
|
+
// behind a terminal state; skipping it is how PRI-1069 stayed
|
|
129
|
+
// invisible. rc-9: reported with its own bucket, never dropped.
|
|
130
|
+
const lastDetail = fact.transitions.length > 0
|
|
131
|
+
? (fact.transitions[fact.transitions.length - 1]?.detail ?? '')
|
|
132
|
+
: '';
|
|
133
|
+
if (fact.lastState === 'failed' && journal.RESTORE_INCONSISTENCY_MARKER.test(lastDetail)) {
|
|
134
|
+
// A later completed install records a `<txid>.recovered.json`
|
|
135
|
+
// resolution (journal module authority); until then the alarm
|
|
136
|
+
// stays. Skip only on real resolution evidence (review 2026-11-02:
|
|
137
|
+
// never clear the alarm just because time passed).
|
|
138
|
+
if (!journal.hasRecoveryResolution(paths.transactionsDir, transactionId)) {
|
|
139
|
+
recoveryRequired.push({ transactionId, reason: lastDetail.slice(0, 400) });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
121
142
|
continue;
|
|
143
|
+
}
|
|
122
144
|
const outcome = journal.recoverUnfinishedTransaction({
|
|
123
145
|
transitions: read.transitions,
|
|
124
146
|
activeRecord,
|
|
@@ -139,10 +161,11 @@ async function recoveryStatus(res) {
|
|
|
139
161
|
}
|
|
140
162
|
unfinished.sort((a, b) => (b.lastAt ?? '').localeCompare(a.lastAt ?? ''));
|
|
141
163
|
sendSuccess(res, {
|
|
142
|
-
needsRecovery: unfinished.length > 0 || broken.length > 0,
|
|
164
|
+
needsRecovery: unfinished.length > 0 || broken.length > 0 || recoveryRequired.length > 0,
|
|
143
165
|
unfinished,
|
|
144
166
|
superseded,
|
|
145
167
|
broken,
|
|
168
|
+
recoveryRequired,
|
|
146
169
|
// rc-9: when the active record could not be read, the recovery decision had
|
|
147
170
|
// no live-release evidence, so every non-terminal journal is conservatively
|
|
148
171
|
// treated as an alarm. Surfacing this explains WHY the alarm persists and
|
|
@@ -153,7 +176,9 @@ async function recoveryStatus(res) {
|
|
|
153
176
|
? activeRecordUnreadable
|
|
154
177
|
? 'The active release record (active.json) could not be read, so no transaction can be proven superseded and every unfinished one is reported. Repair active.json (re-run the official installer recovery), then re-check recovery before starting a new update.'
|
|
155
178
|
: 'An update transaction did not reach a terminal state. Review its phase, then continue, roll back, or repair through the official installer before starting a new update.'
|
|
156
|
-
:
|
|
179
|
+
: recoveryRequired.length > 0
|
|
180
|
+
? 'A completed-but-failed update left the physical installation inconsistent (restore incomplete or deferred). Close any running PD processes, then re-run the official installer to retry recovery before starting a new update.'
|
|
181
|
+
: undefined,
|
|
157
182
|
});
|
|
158
183
|
}
|
|
159
184
|
/**
|
|
@@ -192,6 +217,41 @@ async function recoveryResolve(res, req) {
|
|
|
192
217
|
}
|
|
193
218
|
try {
|
|
194
219
|
const read = journal.readTransactionJournalForRecovery(journalPath);
|
|
220
|
+
// Review 2026-11-02 (consistency): a terminal `failed` carrying a restore
|
|
221
|
+
// inconsistency marker must get the SAME verdict from both recovery
|
|
222
|
+
// surfaces. The generic decision below would answer `old_confirmed`
|
|
223
|
+
// ("previously confirmed release remains active") with no nextAction —
|
|
224
|
+
// read as "nothing to do" while the status endpoint demands repair. The
|
|
225
|
+
// generic decision cannot see physical state; refuse explicitly instead.
|
|
226
|
+
const last = read.transitions[read.transitions.length - 1];
|
|
227
|
+
if (last !== undefined && last.to === 'failed'
|
|
228
|
+
&& typeof last.detail === 'string' && journal.RESTORE_INCONSISTENCY_MARKER.test(last.detail)) {
|
|
229
|
+
if (journal.hasRecoveryResolution(paths.transactionsDir, transactionId)) {
|
|
230
|
+
// Resolved by a later completed install — report that instead of the
|
|
231
|
+
// stale refusal.
|
|
232
|
+
sendSuccess(res, {
|
|
233
|
+
ok: true,
|
|
234
|
+
transactionId,
|
|
235
|
+
outcome: {
|
|
236
|
+
kind: 'old_confirmed',
|
|
237
|
+
releaseId: null,
|
|
238
|
+
generation: null,
|
|
239
|
+
reason: `transaction ended in failed with a restore-inconsistency marker but a later install recorded its recovery; the previously confirmed release remains active`,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
sendSuccess(res, {
|
|
245
|
+
ok: true,
|
|
246
|
+
transactionId,
|
|
247
|
+
outcome: {
|
|
248
|
+
kind: 'explicit_refusal',
|
|
249
|
+
reason: last.detail.slice(0, 400),
|
|
250
|
+
nextAction: 'A completed-but-failed update left the physical installation inconsistent (restore incomplete or deferred). Close any running PD processes, then re-run the official installer to retry recovery before starting a new update.',
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
195
255
|
const activeRecord = journal.readActiveRecord(paths.activeRecordPath);
|
|
196
256
|
const outcome = journal.recoverUnfinishedTransaction({
|
|
197
257
|
transitions: read.transitions,
|
|
@@ -1826,6 +1826,8 @@
|
|
|
1826
1826
|
"reasonLabel": "Reason: ",
|
|
1827
1827
|
"recoveryBanner": "An unfinished update transaction was found",
|
|
1828
1828
|
"recoveryItem": "Transaction {{id}} stopped at phase \"{{phase}}\"",
|
|
1829
|
+
"recoveryRequiredBanner": "A failed update left the installation inconsistent (recovery required)",
|
|
1830
|
+
"recoveryRequiredItem": "Failed transaction {{id}} needs recovery — re-run the official installer",
|
|
1829
1831
|
"inProgressToast": "Update transaction accepted and running in the background; do not click again.",
|
|
1830
1832
|
"inProgressPanel": "Update in progress",
|
|
1831
1833
|
"inProgressPhase": "Current phase: {{phase}}",
|
|
@@ -1826,6 +1826,8 @@
|
|
|
1826
1826
|
"reasonLabel": "原因:",
|
|
1827
1827
|
"recoveryBanner": "检测到未完成的更新事务",
|
|
1828
1828
|
"recoveryItem": "事务 {{id}} 停留在阶段「{{phase}}」",
|
|
1829
|
+
"recoveryRequiredBanner": "一次失败的更新使安装处于不一致状态(需要恢复)",
|
|
1830
|
+
"recoveryRequiredItem": "失败事务 {{id}} 需要恢复——请重新运行官方安装器",
|
|
1829
1831
|
"inProgressToast": "更新事务已受理,正在后台执行;请勿重复点击。",
|
|
1830
1832
|
"inProgressPanel": "更新进行中",
|
|
1831
1833
|
"inProgressPhase": "当前阶段:{{phase}}",
|
|
@@ -202,7 +202,7 @@ export function UpdatePage() {
|
|
|
202
202
|
return (_jsxs(PageShell, { children: [_jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [recoveryData?.needsRecovery && recoveryData.unfinished.length > 0 && (_jsxs("div", { className: "mb-6 p-4 bg-amber/5 border border-amber/35 rounded-[6px] text-[13px] leading-relaxed text-ink-2", role: "alert", children: [_jsx("p", { className: "font-medium text-amber", children: t("pages.update.recoveryBanner") }), recoveryData.unfinished.slice(0, 3).map((item) => (_jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-3", children: t("pages.update.recoveryItem", {
|
|
203
203
|
id: item.transactionId,
|
|
204
204
|
phase: item.lastState ?? "—",
|
|
205
|
-
}) }, item.transactionId))), recoveryData.nextAction && (_jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: recoveryData.nextAction }))] })), _jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.update.eyebrow") }), _jsx("h1", { className: "text-[29px] font-semibold tracking-tight text-ink mb-2", children: t("pages.update.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.update.subtitle") }), _jsxs("section", { className: "mb-8", "aria-labelledby": "section-update-status", children: [_jsx(SectionTitle, { id: "section-update-status", children: t("pages.update.currentVersion") }), _jsxs("div", { className: "bg-panel border border-line rounded-[6px] p-5", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.currentVersion") }), _jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.currentVersion ?? "—" })] }), statusData?.identityDivergence && (_jsx("div", { className: "border border-amber/35 rounded-[4px] px-3 py-2 text-[12px] leading-relaxed text-ink-2", children: t("pages.update.identityDivergence", {
|
|
205
|
+
}) }, item.transactionId))), recoveryData.nextAction && (_jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: recoveryData.nextAction }))] })), recoveryData?.needsRecovery && recoveryData.recoveryRequired.length > 0 && (_jsxs("div", { className: "mb-6 p-4 bg-amber/5 border border-amber/35 rounded-[6px] text-[13px] leading-relaxed text-ink-2", role: "alert", children: [_jsx("p", { className: "font-medium text-amber", children: t("pages.update.recoveryRequiredBanner") }), recoveryData.recoveryRequired.slice(0, 3).map((item) => (_jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-3", children: t("pages.update.recoveryRequiredItem", { id: item.transactionId }) }, item.transactionId))), recoveryData.nextAction && (_jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: recoveryData.nextAction }))] })), _jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.update.eyebrow") }), _jsx("h1", { className: "text-[29px] font-semibold tracking-tight text-ink mb-2", children: t("pages.update.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.update.subtitle") }), _jsxs("section", { className: "mb-8", "aria-labelledby": "section-update-status", children: [_jsx(SectionTitle, { id: "section-update-status", children: t("pages.update.currentVersion") }), _jsxs("div", { className: "bg-panel border border-line rounded-[6px] p-5", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.currentVersion") }), _jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.currentVersion ?? "—" })] }), statusData?.identityDivergence && (_jsx("div", { className: "border border-amber/35 rounded-[4px] px-3 py-2 text-[12px] leading-relaxed text-ink-2", children: t("pages.update.identityDivergence", {
|
|
206
206
|
active: statusData.identityDivergence.activeVersion,
|
|
207
207
|
plugin: statusData.identityDivergence.pluginVersion,
|
|
208
208
|
}) })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.latestVersion") }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-mono text-[13px] text-ink", children: updateState === 'check_failed' ? "—" : (statusData?.latestVersion || "—") }), updateState === 'check_failed' && (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.checkFailed") })), updateState === 'update_blocked' && (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.blockedBadge") })), updateState === 'up_to_date' && (_jsx("span", { className: "inline-flex items-center border border-green/35 text-green rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.upToDate") })), updateState === 'update_available' && (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.updateAvailable") }))] })] })] }), (updateState === 'update_blocked' || updateState === 'check_failed') && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] leading-relaxed", children: [blockedReason && (_jsxs("p", { className: "text-ink-2", children: [t("pages.update.reasonLabel"), blockedReason] })), statusData?.message && updateState === 'update_blocked' && (_jsx("p", { className: "mt-1 text-ink-3", children: statusData.message })), statusData?.nextAction && (_jsx("p", { className: "mt-1 text-ink-3 font-mono", children: statusData.nextAction }))] })), statusData?.error && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] text-ink-4 font-mono", children: [t("pages.update.checkError"), ": ", statusData.error] })), inProgress && (_jsxs("div", { className: "mt-4 p-4 rounded-[6px] border border-gov/30 bg-surface text-[13px] leading-relaxed", role: "status", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-gov" }), _jsx("span", { className: "font-medium text-ink", children: t("pages.update.inProgressPanel") })] }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-3", children: t("pages.update.inProgressPhase", { phase: inProgress.phase ?? "…" }) }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-4", children: inProgress.transactionId })] })), _jsxs("div", { className: "mt-5 pt-4 border-t border-line flex items-center gap-3 flex-wrap", children: [_jsx("button", { type: "button", onClick: handleCheckForUpdates, disabled: checking || fullUpdating || inProgress !== null, className: "border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: checking ? t("pages.update.checking") : t("pages.update.checkForUpdates") }), !isUpToDate && updateState === 'update_available' && (_jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: checking || fullUpdating || inProgress !== null, className: "bg-gov text-white rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: fullUpdating ? (_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), t("pages.update.updating")] })) : (t("pages.update.applyUpdate")) }))] }), updateResult && (_jsx("div", { className: `mt-4 p-4 rounded-[6px] border animate-[pdFadeIn_400ms_ease-out] ${updateResult.success ? 'bg-green/5 border-green/20' : updateResult.refusal ? 'bg-amber/5 border-amber/25' : 'bg-danger/5 border-danger/20'}`, children: _jsxs("div", { className: "flex items-start gap-3", children: [updateResult.success ? (_jsx(CheckCircle2, { className: "h-6 w-6 text-green shrink-0 mt-0.5 animate-[pdFadeIn_600ms_ease-out]", style: { transform: 'scale(1)', animationFillMode: 'both' } })) : updateResult.refusal ? (_jsx(AlertTriangle, { className: "h-6 w-6 text-amber shrink-0 mt-0.5" })) : (_jsx(XCircle, { className: "h-6 w-6 text-danger shrink-0 mt-0.5" })), _jsxs("div", { className: "flex-1 min-w-0", children: [updateResult.refusal && (_jsx("p", { className: "text-[12px] font-mono uppercase text-amber mb-1", children: t("pages.update.blockedBadge") })), updateResult.success && updateResult.fromVersion && updateResult.newVersion && (_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("span", { className: "font-mono text-[13px] text-ink-3 line-through", children: updateResult.fromVersion }), _jsx(ArrowRight, { className: "h-3.5 w-3.5 text-green" }), _jsx("span", { className: "font-mono text-[13px] text-green font-medium", children: updateResult.newVersion })] })), _jsx("p", { className: `text-[13px] ${updateResult.success ? 'text-green' : updateResult.refusal ? 'text-ink-2' : 'text-danger'}`, children: updateResult.message }), updateResult.success && updateResult.requiresRestart && (_jsx("p", { className: "mt-2 p-2 rounded-[3px] bg-amber/5 border border-amber/20 text-[12px] text-amber leading-relaxed", children: t("pages.update.fullUpdateRestartPrompt") })), updateResult.gatewayNotice && (_jsx("p", { className: "mt-2 p-2 rounded-[3px] bg-amber/5 border border-amber/20 text-[12px] text-amber leading-relaxed font-mono", children: updateResult.gatewayNotice })), !updateResult.success && updateResult.nextAction && (_jsx("p", { className: "mt-1 text-[12px] text-ink-4 font-mono leading-relaxed", children: updateResult.nextAction })), !updateResult.success && (_jsxs("div", { className: "mt-2 flex items-center gap-2", children: [/network|fetch|timeout|ECONNREFUSED|ENOTFOUND/i.test(updateResult.message) && (_jsx("p", { className: "text-[12px] text-ink-4 font-mono", children: t("pages.update.networkErrorHint") })), _jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: fullUpdating, className: "text-[12px] text-danger underline hover:text-danger/80 disabled:opacity-50", children: t("pages.update.retry") })] }))] })] }) }))] })] }), _jsxs("section", { "aria-labelledby": "section-update-history", children: [_jsx(SectionTitle, { id: "section-update-history", children: t("pages.update.history") }), historyErrorReason ? (_jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: [t("pages.update.loadError"), " (", historyErrorReason, ")"] })) : historyData && historyData.updates.length > 0 ? (_jsx("div", { className: "space-y-[10px]", children: historyData.updates.map((entry) => (_jsxs("article", { className: "bg-panel border border-line rounded-[6px] px-5 py-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: t("pages.update.version") }), _jsxs("span", { className: "font-mono text-[13px] text-ink", children: [entry.fromVersion, " \u2192 ", entry.toVersion === "failed" ? "—" : entry.toVersion] }), entry.kind !== "failure" && (_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: t(`pages.update.historyKind.${entry.kind}`) })), !entry.success && entry.kind !== 'refusal' && (_jsx("span", { className: "inline-flex items-center border border-danger/35 text-danger rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.failed") }))] }), _jsx("div", { className: "flex items-center gap-3", children: _jsx("span", { className: "text-ink-3 text-[13px]", children: formatDate(entry.timestamp, i18n.language, {
|
|
@@ -904,6 +904,16 @@ export interface UpdateRecoveryData {
|
|
|
904
904
|
* Reported separately (rc-9: never a silent drop) but never hold the alarm.
|
|
905
905
|
*/
|
|
906
906
|
superseded: UpdateRecoverySuperseded[];
|
|
907
|
+
/**
|
|
908
|
+
* PRI-1069/1070 (G1 P0-4): terminal `failed` transactions whose detail
|
|
909
|
+
* carries a restore-inconsistency marker — the physical install is NOT what
|
|
910
|
+
* the terminal state implies. They stay alarmed until a later completed
|
|
911
|
+
* install records their recovery.
|
|
912
|
+
*/
|
|
913
|
+
recoveryRequired: {
|
|
914
|
+
transactionId: string;
|
|
915
|
+
reason: string;
|
|
916
|
+
}[];
|
|
907
917
|
/**
|
|
908
918
|
* rc-9 (PRI-896/CR-2): active.json could not be read, so recovery had no
|
|
909
919
|
* live-release evidence and every non-terminal journal is conservatively an
|
|
@@ -2536,7 +2536,17 @@ export function validateUpdateRecovery(v) {
|
|
|
2536
2536
|
superseded.push({ transactionId: item.transactionId, reason: item.reason });
|
|
2537
2537
|
}
|
|
2538
2538
|
}
|
|
2539
|
-
const
|
|
2539
|
+
const recoveryRequired = [];
|
|
2540
|
+
if (Object.hasOwn(v, 'recoveryRequired') && Array.isArray(v.recoveryRequired)) {
|
|
2541
|
+
for (const item of v.recoveryRequired) {
|
|
2542
|
+
if (!isObject(item) || !Object.hasOwn(item, 'transactionId') || !isString(item.transactionId))
|
|
2543
|
+
continue;
|
|
2544
|
+
if (!Object.hasOwn(item, 'reason') || !isString(item.reason))
|
|
2545
|
+
continue;
|
|
2546
|
+
recoveryRequired.push({ transactionId: item.transactionId, reason: item.reason });
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
const result = { needsRecovery: v.needsRecovery, unfinished, broken, superseded, recoveryRequired };
|
|
2540
2550
|
if (Object.hasOwn(v, 'activeRecordUnreadable') && typeof v.activeRecordUnreadable === 'boolean') {
|
|
2541
2551
|
result.activeRecordUnreadable = v.activeRecordUnreadable;
|
|
2542
2552
|
}
|