fix: avoid blocking sentinel on terminal timing correction (#992)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-26 15:06:19 +08:00
committed by GitHub
parent dc4d94e62a
commit 5fa5708fe1
2 changed files with 21 additions and 3 deletions
@@ -571,3 +571,5 @@ Dashboard 增强执行 issue 为 [#935](https://github.com/pikasTech/unidesk/iss
P7 P6 收口必须区分 public dashboard validation 与 targetValidation quick verify`monitor.pikapython.com` root/CSS/JS 200 只证明公开入口和静态 dashboard 资源可用;quick verify 的 `observe command`、采样样本、analyze report 和 red finding 仍是业务恢复判定的一部分。若 quick verify 因 `observe-command-newSession-failed``no-samples` 或等价采样器控制失败而 blocked,P6 issue 必须保持未关闭或显式拆出后续 blocker,不得只凭 public dashboard 200 关闭。
P8 哨兵恢复执行 issue 为 [#971](https://github.com/pikasTech/unidesk/issues/971)。P8 closeout 必须回写:SPEC P8 引用、120s warning 证据、`quick-verify-no-business-turn` 或等价业务触达证据、`browser-timeout` 分类修正、中文运维页面验证、`monitor.pikapython.com` 公网入口验证、k3s 内部 Service DNS quick verify 路径、D601/v03 用户入口 smoke 结果,以及仍未解除的真实业务 blocker 是否已单独拆出。
P8-P8 起,targetValidation 的 availability blocker 与 Workbench timing 架构治理必须分层。若同一 trace 在 terminal 前最后一帧仍为 running,随后 terminal commit 的 sealed `durationMs` 把可见 `totalElapsed` 小幅校正到更短值,且 drop 不超过 YAML `turnTimingSampleSlackSeconds`、final response 与完成行均已可见,则该现象作为 terminal-boundary timing correction 证据保留,不生成 `turn-timing-total-elapsed-decrease` red blocker。归零、running/running 下降、terminal 后增长、完成耗时与卡片耗时超出 slack、trace 乱序和完成行非最后仍保持 red;根因治理继续归 [HWLAB #2055](https://github.com/pikasTech/HWLAB/issues/2055) 和 [HWLAB #2125](https://github.com/pikasTech/HWLAB/issues/2125),不得在 UI、CLI renderer 或 analyzer 中做读侧 repair。
@@ -91,6 +91,7 @@ export function nodeWebObserveAnalyzerTimingSource(): string {
elapsedZeroResets: timingEvents.elapsedZeroResets,
totalElapsedForwardJumps: timingEvents.totalElapsedForwardJumps,
terminalElapsedGrowth: timingEvents.terminalElapsedGrowth,
terminalElapsedCorrections: timingEvents.terminalElapsedCorrections,
recentUpdateResets: timingEvents.recentUpdateResets,
recentUpdateSteps: timingEvents.recentUpdateSteps
};
@@ -120,6 +121,7 @@ function detectTurnTimingNonMonotonic(columns, rows) {
const elapsedZeroResets = [];
const totalElapsedForwardJumps = [];
const terminalElapsedGrowth = [];
const terminalElapsedCorrections = [];
const recentUpdateResets = [];
const recentUpdateSteps = [];
for (const column of columns) {
@@ -135,6 +137,9 @@ function detectTurnTimingNonMonotonic(columns, rows) {
const previous = previousByMetric.get(metric);
if (previous && metric === "totalElapsedSeconds" && current < previous.value) {
const anomaly = previous.value > 0 && current === 0 ? "zero-reset" : "decrease";
const terminalTransition = !isTerminalTurnStatus(previous.status) && isTerminalTurnStatus(cell.status);
const dropSeconds = previous.value - current;
const allowedDropSeconds = Math.max(1, Number(alertThresholds?.turnTimingSampleSlackSeconds || 0));
const event = {
columnId: column.id,
columnLabel: column.label,
@@ -144,9 +149,11 @@ function detectTurnTimingNonMonotonic(columns, rows) {
fromSeq: previous.seq,
fromTs: previous.ts,
fromValue: previous.value,
fromStatus: previous.status ?? null,
toSeq: row.seq ?? null,
toTs: row.ts ?? null,
toValue: current,
toStatus: cell.status ?? null,
delta: current - previous.value,
traceId: cell.traceId ?? column.traceId ?? null,
messageId: cell.messageId ?? column.messageId ?? null,
@@ -158,8 +165,17 @@ function detectTurnTimingNonMonotonic(columns, rows) {
pageEpoch: cell.pageEpoch ?? row.pageEpoch ?? column.pageEpoch ?? null,
valuesRedacted: true
};
anomalies.push(event);
if (anomaly === "zero-reset") elapsedZeroResets.push(event);
if (terminalTransition && anomaly === "decrease" && dropSeconds <= allowedDropSeconds) {
terminalElapsedCorrections.push({
...event,
anomaly: "terminal-boundary-correction",
expectedPattern: "terminal-sealed-duration-may-correct-running-elapsed-within-slack",
allowedDropSeconds,
});
} else {
anomalies.push(event);
if (anomaly === "zero-reset") elapsedZeroResets.push(event);
}
}
if (previous && metric === "totalElapsedSeconds" && current > previous.value) {
const sampleDeltaSeconds = elapsedSecondsBetween(previous.ts, row.ts);
@@ -319,7 +335,7 @@ function detectTurnTimingNonMonotonic(columns, rows) {
}
}
}
return { anomalies, elapsedZeroResets, totalElapsedForwardJumps, terminalElapsedGrowth, recentUpdateResets, recentUpdateSteps };
return { anomalies, elapsedZeroResets, totalElapsedForwardJumps, terminalElapsedGrowth, terminalElapsedCorrections, recentUpdateResets, recentUpdateSteps };
}
function elapsedSecondsBetween(fromTs, toTs) {