@khorsheed/dsh-ankh-guard 0.4.1 → 0.4.2
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/CHANGELOG.md +4 -0
- package/lib/client.js +20 -0
- package/lib/types/client/index.js +27 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.4.2(2026-09-30)
|
|
4
|
+
|
|
5
|
+
- **修复重启浮层卡死**:标签页带着 pending handoff 时,若 cutover 已不经由本标签页收口(操作员 restore、handoff off、监督链楔死),服务端对该 cutoverId 永远回 409,「正在重启」浮层会无限重试不下台。现在每 8 次失败 ACK 花一次 poll——`idle` 即证明没有在途事务,丢弃 pending 并收浮层。sessionStorage 按标签页存活,所以旧实例上已卡住的标签页不受影响此修复——关掉重开即可(3080 实测案例)
|
|
6
|
+
|
|
3
7
|
## 0.4.1(2026-09-30)
|
|
4
8
|
|
|
5
9
|
修复 2026-09-29/30 的 0.2.0-rc.2 切换事故暴露的三个缺口:
|
package/lib/client.js
CHANGED
|
@@ -159,6 +159,8 @@ window.__ModuleLoader__.load({
|
|
|
159
159
|
let timer;
|
|
160
160
|
let pending = consumeFallbackFragment() ?? readPending();
|
|
161
161
|
let cap;
|
|
162
|
+
/** Consecutive failed ACKs for the current pending handoff. */
|
|
163
|
+
let ackFailures = 0;
|
|
162
164
|
let errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
163
165
|
let firstFailureAt;
|
|
164
166
|
let request;
|
|
@@ -226,6 +228,22 @@ window.__ModuleLoader__.load({
|
|
|
226
228
|
location.replace(returnPath);
|
|
227
229
|
return;
|
|
228
230
|
}
|
|
231
|
+
} else {
|
|
232
|
+
ackFailures += 1;
|
|
233
|
+
if (ackFailures >= 8) {
|
|
234
|
+
ackFailures = 0;
|
|
235
|
+
const probe = await post({
|
|
236
|
+
version: 1,
|
|
237
|
+
operation: "poll",
|
|
238
|
+
capability: cap ??= capability()
|
|
239
|
+
}, current.signal);
|
|
240
|
+
if (stale()) return;
|
|
241
|
+
if (probe.ok && (await probe.json()).state === "idle") {
|
|
242
|
+
sessionStorage.removeItem(PENDING_KEY);
|
|
243
|
+
sessionStorage.removeItem(CAPABILITY_KEY);
|
|
244
|
+
pending = null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
229
247
|
}
|
|
230
248
|
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
231
249
|
firstFailureAt = void 0;
|
|
@@ -278,6 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
278
296
|
};
|
|
279
297
|
pending = nextPending;
|
|
280
298
|
writePending(nextPending);
|
|
299
|
+
ackFailures = 0;
|
|
281
300
|
waitingOverlay();
|
|
282
301
|
location.reload();
|
|
283
302
|
return;
|
|
@@ -299,6 +318,7 @@ window.__ModuleLoader__.load({
|
|
|
299
318
|
};
|
|
300
319
|
pending = nextPending;
|
|
301
320
|
writePending(nextPending);
|
|
321
|
+
ackFailures = 0;
|
|
302
322
|
waitingOverlay();
|
|
303
323
|
location.replace(launch.href);
|
|
304
324
|
return;
|
|
@@ -155,6 +155,8 @@ export function apply(_ctx) {
|
|
|
155
155
|
let timer;
|
|
156
156
|
let pending = consumeFallbackFragment() ?? readPending();
|
|
157
157
|
let cap;
|
|
158
|
+
/** Consecutive failed ACKs for the current pending handoff. */
|
|
159
|
+
let ackFailures = 0;
|
|
158
160
|
let errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
159
161
|
let firstFailureAt;
|
|
160
162
|
let request;
|
|
@@ -231,6 +233,29 @@ export function apply(_ctx) {
|
|
|
231
233
|
return;
|
|
232
234
|
}
|
|
233
235
|
}
|
|
236
|
+
else {
|
|
237
|
+
// A failed ACK can be permanent: the cutover settled without this tab
|
|
238
|
+
// (operator restore, handoff off, a wedged supervisor) and the server
|
|
239
|
+
// keeps answering 409 'waiting'/'stale' for a dead cutoverId — the
|
|
240
|
+
// veil would otherwise stay up forever (observed 2026-09-30). Every
|
|
241
|
+
// few failures, spend one poll: 'idle' proves nothing is in flight,
|
|
242
|
+
// so the pending handoff is dead — drop it and let the ordinary
|
|
243
|
+
// poll loop remove the veil on the next tick.
|
|
244
|
+
ackFailures += 1;
|
|
245
|
+
if (ackFailures >= 8) {
|
|
246
|
+
ackFailures = 0;
|
|
247
|
+
const probe = await post({
|
|
248
|
+
version: 1, operation: 'poll', capability: cap ??= capability(),
|
|
249
|
+
}, current.signal);
|
|
250
|
+
if (stale())
|
|
251
|
+
return;
|
|
252
|
+
if (probe.ok && (await probe.json()).state === 'idle') {
|
|
253
|
+
sessionStorage.removeItem(PENDING_KEY);
|
|
254
|
+
sessionStorage.removeItem(CAPABILITY_KEY);
|
|
255
|
+
pending = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
234
259
|
errorRetryMs = ERROR_RETRY_MIN_MS;
|
|
235
260
|
firstFailureAt = undefined;
|
|
236
261
|
schedule(ACTIVE_RETRY_MS);
|
|
@@ -287,6 +312,7 @@ export function apply(_ctx) {
|
|
|
287
312
|
};
|
|
288
313
|
pending = nextPending;
|
|
289
314
|
writePending(nextPending);
|
|
315
|
+
ackFailures = 0;
|
|
290
316
|
waitingOverlay();
|
|
291
317
|
location.reload();
|
|
292
318
|
return;
|
|
@@ -307,6 +333,7 @@ export function apply(_ctx) {
|
|
|
307
333
|
};
|
|
308
334
|
pending = nextPending;
|
|
309
335
|
writePending(nextPending);
|
|
336
|
+
ackFailures = 0;
|
|
310
337
|
waitingOverlay();
|
|
311
338
|
// The bearer remains a local variable for the shortest possible time.
|
|
312
339
|
location.replace(launch.href);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khorsheed/dsh-ankh-guard",
|
|
3
3
|
"description": "Hard gate for self-modification restarts: a green-build credential bound to the git HEAD, checked before any restart of the running instance",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|