Skip to content

Commit f475757

Browse files
Surface run-wait-age on dw system:operator-metrics
The operator-metrics rollout-safety contract now freezes operator_metrics.runs.waiting (integer), operator_metrics.runs.oldest_wait_started_at (ISO-8601 or null), and operator_metrics.runs.max_wait_age_ms (integer milliseconds) as the run-wait-age surface — the count of running runs parked at a durable resume point, the earliest wait_started_at among them, and the largest wait age in milliseconds. The signal covers every kind of wait (signal, update, timer, compatibility-blocked) because each is a durable resume point the system is parked on. Render the trio on the Runs section of dw system:operator-metrics as "Waiting (durable resume): N", "Oldest wait age: N ms", and "Oldest wait started at: ISO", sibling to the existing Repair needed / Claim failed / Compatibility blocked counts. Pin all three keys under runs in schemas/output/operator-metrics.schema.json for --json consumers so the contract is enforced regardless of the textual renderer. Extends SystemCommandTest with schema and human-rendering assertions pinned unconditionally, mirroring the existing dispatch-overdue and ready-due age pins.
1 parent 678e0d2 commit f475757

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

schemas/output/operator-metrics.schema.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
"properties": {
1616
"repair_needed": { "type": ["integer", "null"] },
1717
"claim_failed": { "type": ["integer", "null"] },
18-
"compatibility_blocked": { "type": ["integer", "null"] }
18+
"compatibility_blocked": { "type": ["integer", "null"] },
19+
"waiting": { "type": ["integer", "null"] },
20+
"oldest_wait_started_at": { "type": ["string", "null"] },
21+
"max_wait_age_ms": { "type": ["integer", "null"] }
1922
},
2023
"additionalProperties": true
2124
},

src/Commands/SystemCommand/OperatorMetricsCommand.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ private function renderRuns(OutputInterface $output, array $runs): void
9494
$output->writeln(sprintf(' Repair needed: %d', (int) ($runs['repair_needed'] ?? 0)));
9595
$output->writeln(sprintf(' Claim failed: %d', (int) ($runs['claim_failed'] ?? 0)));
9696
$output->writeln(sprintf(' Compatibility blocked: %d', (int) ($runs['compatibility_blocked'] ?? 0)));
97+
if (array_key_exists('waiting', $runs)) {
98+
$output->writeln(sprintf(' Waiting (durable resume): %d', (int) ($runs['waiting'] ?? 0)));
99+
}
100+
if (array_key_exists('max_wait_age_ms', $runs)) {
101+
$output->writeln(sprintf(
102+
' Oldest wait age: %d ms',
103+
(int) ($runs['max_wait_age_ms'] ?? 0),
104+
));
105+
}
106+
if (is_string($runs['oldest_wait_started_at'] ?? null)) {
107+
$output->writeln(sprintf(' Oldest wait started at: %s', $runs['oldest_wait_started_at']));
108+
}
97109
$output->writeln('');
98110
}
99111

tests/Commands/SystemCommandTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ public function test_operator_metrics_command_renders_rollout_safety_signals():
507507
self::assertStringContainsString('Repair needed: 4', $display);
508508
self::assertStringContainsString('Claim failed: 2', $display);
509509
self::assertStringContainsString('Compatibility blocked: 1', $display);
510+
self::assertStringContainsString('Waiting (durable resume): 6', $display);
511+
self::assertStringContainsString('Oldest wait age: 285000 ms', $display);
512+
self::assertStringContainsString('Oldest wait started at: 2026-04-24T11:25:15Z', $display);
510513

511514
self::assertStringContainsString('Queue depth: 9 ready (7 due), 3 delayed, 5 leased', $display);
512515
self::assertStringContainsString(
@@ -650,6 +653,21 @@ public function test_operator_metrics_schema_pins_dispatch_overdue_age_keys(): v
650653
self::assertSame(['integer', 'null'], $tasks['max_dispatch_overdue_age_ms']['type']);
651654
}
652655

656+
public function test_operator_metrics_schema_pins_run_wait_age_keys(): void
657+
{
658+
$schema = json_decode(
659+
(string) file_get_contents(__DIR__.'/../../schemas/output/operator-metrics.schema.json'),
660+
true,
661+
flags: JSON_THROW_ON_ERROR,
662+
);
663+
664+
$runs = $schema['properties']['operator_metrics']['properties']['runs']['properties'];
665+
666+
self::assertSame(['integer', 'null'], $runs['waiting']['type']);
667+
self::assertSame(['string', 'null'], $runs['oldest_wait_started_at']['type']);
668+
self::assertSame(['integer', 'null'], $runs['max_wait_age_ms']['type']);
669+
}
670+
653671
public function test_operator_metrics_command_tolerates_minimal_payload(): void
654672
{
655673
$command = new OperatorMetricsCommand();
@@ -679,6 +697,9 @@ private static function operatorMetricsPayload(): array
679697
'repair_needed' => 4,
680698
'claim_failed' => 2,
681699
'compatibility_blocked' => 1,
700+
'waiting' => 6,
701+
'oldest_wait_started_at' => '2026-04-24T11:25:15Z',
702+
'max_wait_age_ms' => 285000,
682703
],
683704
'tasks' => [
684705
'ready' => 9,

0 commit comments

Comments
 (0)