fix(uok-status): surface manualAttention bucket in status uok output
Codex audit follow-up (fix A). manual-attention outcomes were counted by getGateRunStats but dropped from the user-facing surface — they inflated `total` invisibly with no distinct column or key, so an operator couldn't tell a gate with 5 pass / 3 manual-attention apart from a gate with 5 pass / 3 fail. Adds `manualAttention: number` to GateHealthEntry and renders it as its own column between Fail and Retry in the human table. JSON consumers get the new key alongside pass/fail/retry. Test count for headless-uok-status.test.mjs: 30/30 (+2 new — column present in header, distinguishable from fail in row). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7794208340
commit
7000373e88
2 changed files with 31 additions and 6 deletions
|
|
@ -79,6 +79,14 @@ export interface GateHealthEntry {
|
|||
total: number;
|
||||
pass: number;
|
||||
fail: number;
|
||||
/**
|
||||
* Manual-attention outcomes (codex audit follow-up). Previously
|
||||
* counted by getGateRunStats but dropped from the JSON/table output,
|
||||
* which inflated `total` without an operator-visible bucket. Now
|
||||
* surfaced as its own column so manual-attention gates are
|
||||
* distinguishable from pass/fail in the at-a-glance view.
|
||||
*/
|
||||
manualAttention: number;
|
||||
retry: number;
|
||||
lastEvaluatedAt: string | null;
|
||||
circuitBreaker: string;
|
||||
|
|
@ -185,8 +193,8 @@ function formatTable(gates: GateHealthEntry[]): string {
|
|||
return "No gate run data found in the last 24h.\n";
|
||||
}
|
||||
const header =
|
||||
"| Gate | Scope | Coverage | Pass% | Pass | Fail | Retry | CB | Streak | Last Evaluated |\n" +
|
||||
"|------|-------|----------|-------|------|------|-------|----|--------|----------------|\n";
|
||||
"| Gate | Scope | Coverage | Pass% | Pass | Fail | Manual | Retry | CB | Streak | Last Evaluated |\n" +
|
||||
"|------|-------|----------|-------|------|------|--------|-------|----|--------|----------------|\n";
|
||||
const rows = gates
|
||||
.map((g) => {
|
||||
const last = g.lastEvaluatedAt
|
||||
|
|
@ -195,7 +203,7 @@ function formatTable(gates: GateHealthEntry[]): string {
|
|||
.replace("T", " ")
|
||||
.slice(0, 19)
|
||||
: "never";
|
||||
return `| ${g.id} | ${g.scope} | ${coverageIcon(g.coverageStatus)} | ${passRate(g)} | ${g.pass} | ${g.fail} | ${g.retry} | ${cbIcon(g.circuitBreaker)} ${g.circuitBreaker} | ${g.failureStreak} | ${last} |`;
|
||||
return `| ${g.id} | ${g.scope} | ${coverageIcon(g.coverageStatus)} | ${passRate(g)} | ${g.pass} | ${g.fail} | ${g.manualAttention} | ${g.retry} | ${cbIcon(g.circuitBreaker)} ${g.circuitBreaker} | ${g.failureStreak} | ${last} |`;
|
||||
})
|
||||
.join("\n");
|
||||
return `${header}${rows}\n`;
|
||||
|
|
@ -387,6 +395,7 @@ export async function handleUokStatus(
|
|||
total: stats.total ?? 0,
|
||||
pass: stats.pass ?? 0,
|
||||
fail: stats.fail ?? 0,
|
||||
manualAttention: stats.manualAttention ?? 0,
|
||||
retry: stats.retry ?? 0,
|
||||
// prefer stats window result; fall back to quality_gates last entry
|
||||
lastEvaluatedAt: stats.lastEvaluatedAt ?? runContext.lastEvaluatedAt,
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ function formatTable(gates) {
|
|||
return "No gate run data found in the last 24h.\n";
|
||||
}
|
||||
const header =
|
||||
"| Gate | Scope | Coverage | Pass% | Pass | Fail | Retry | CB | Streak | Last Evaluated |\n" +
|
||||
"|------|-------|----------|-------|------|------|-------|----|--------|----------------|\n";
|
||||
"| Gate | Scope | Coverage | Pass% | Pass | Fail | Manual | Retry | CB | Streak | Last Evaluated |\n" +
|
||||
"|------|-------|----------|-------|------|------|--------|-------|----|--------|----------------|\n";
|
||||
const rows = gates
|
||||
.map((g) => {
|
||||
const last = g.lastEvaluatedAt
|
||||
|
|
@ -48,7 +48,7 @@ function formatTable(gates) {
|
|||
.replace("T", " ")
|
||||
.slice(0, 19)
|
||||
: "never";
|
||||
return `| ${g.id} | ${g.scope} | ${coverageIcon(g.coverageStatus)} | ${passRate(g)} | ${g.pass} | ${g.fail} | ${g.retry} | ${cbIcon(g.circuitBreaker)} ${g.circuitBreaker} | ${g.failureStreak} | ${last} |`;
|
||||
return `| ${g.id} | ${g.scope} | ${coverageIcon(g.coverageStatus)} | ${passRate(g)} | ${g.pass} | ${g.fail} | ${g.manualAttention ?? 0} | ${g.retry} | ${cbIcon(g.circuitBreaker)} ${g.circuitBreaker} | ${g.failureStreak} | ${last} |`;
|
||||
})
|
||||
.join("\n");
|
||||
return `${header}${rows}\n`;
|
||||
|
|
@ -105,6 +105,7 @@ function makeGate(overrides = {}) {
|
|||
total: 10,
|
||||
pass: 8,
|
||||
fail: 2,
|
||||
manualAttention: 0,
|
||||
retry: 0,
|
||||
lastEvaluatedAt: "2026-05-11T12:00:00.000Z",
|
||||
circuitBreaker: "closed",
|
||||
|
|
@ -172,6 +173,21 @@ describe("formatTable", () => {
|
|||
assert.ok(result.includes("⚠ stale"));
|
||||
});
|
||||
|
||||
it("includes_manual_attention_column_distinct_from_fail", () => {
|
||||
// Codex audit follow-up: manualAttention used to be counted by
|
||||
// getGateRunStats but dropped from the rendered output, inflating
|
||||
// `total` invisibly. Now it's a column between Fail and Retry.
|
||||
const result = formatTable([
|
||||
makeGate({ id: "g-manual", fail: 0, manualAttention: 3 }),
|
||||
]);
|
||||
assert.ok(result.includes("| Manual |"), "header has Manual column");
|
||||
// The row contains "| 0 | 3 |" — fail=0, manualAttention=3.
|
||||
assert.ok(
|
||||
/\|\s*0\s*\|\s*3\s*\|/.test(result),
|
||||
"row shows manualAttention=3 distinct from fail=0",
|
||||
);
|
||||
});
|
||||
|
||||
it("includes_separator_row", () => {
|
||||
const result = formatTable([makeGate()]);
|
||||
assert.ok(result.includes("|------|"));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue