Log in
scripts/platform_lifecycle.py 4256 lines · 161.2 KB · blame Source
88e86b3faccd Rebuild benchmarks as a clean single-root reposi 2 months ago
1
#!/usr/bin/env python3
2
"""The platform lane: hosted integration workflows, end to end.
3
4
Measures GitHub-the-platform vs oak hosted workflows from "task branch exists
5
locally" to "change merged in main, present in local checkout" β€” including
6
the async waits agents actually experience (GitHub computes PR mergeability
7
asynchronously; that settling is a measured operation, observed by polling on
8
one local monotonic clock).
9
10
Drivers per platform:
11
  cli         shells out to gh/git/oak via run_timed β€” full token/output
12
              accounting from captured output, like every other CLI lane.
13
  harness-api stdlib urllib REST against the GitHub API β€” exact api_metrics
14
              (request_count/bytes, rate-limit header, request ids); token
15
              fields stay null because API JSON is not an agent transcript
16
              and token costs would be fiction (ADR-0002).
17
  fake-provider
18
              deterministic in-process branch triage provider for oracle
19
              coverage when no hosted platform is available; not comparable
20
              to real platform timings.
21
22
This lane runs against real hosted services and is EXCLUDED from devloop:
23
external variance must never poison changeset verdicts.
24
25
Without credentials/configured repos (the only path CI exercises) every
26
operation becomes a skip row with an explicit skip_reason and the process
27
exits 3 (all-skipped), never silent absence.
28
29
Setup:
30
  GITHUB_TOKEN=... python3 scripts/platform_lifecycle.py \\
31
    --platform github --driver harness-api --scenario pr_single_anatomy \\
32
    --repo your-org/disposable-bench-repo --runs 3
33
"""
34
35
from __future__ import annotations
36
37
import argparse
38
import concurrent.futures
39
import json
40
import os
41
import platform as platform_module
42
import re
43
import shlex
44
import shutil
45
import subprocess
46
import sys
47
import tempfile
48
import threading
49
import time
50
from dataclasses import dataclass
51
from datetime import datetime, timezone
52
from pathlib import Path
53
from typing import Any, Callable, Optional
54
55
from oakbench import platform_clock
56
from oakbench import tokens as oakbench_tokens
57
from oakbench.environment import ENV_ISOLATION_VERSION, base_env as oakbench_base_env
58
from oakbench.environment import command_display
59
from oakbench.execution import run_timed
60
from oakbench.platform_api import GitHubApiError, GitHubClient, RateLimitBudgeter
61
from oakbench.remotes import disposable_branch
62
from oakbench.results import ResultsStore
63
from oakbench.rows import row_returncode
64
from oakbench.runlock import measurement_lock
65
from oakbench.runner import runner_fields, stamp_row
66
67
from mount_probe import parse_yaml_subset
68
69
ROOT = Path(__file__).resolve().parents[1]
70
DEFAULT_SPEC = ROOT / "scenarios" / "platform.yaml"
71
DEFAULT_SEMANTICS = ROOT / "config" / "platform_semantics.json"
72
DEFAULT_WORKDIR = Path(tempfile.gettempdir()) / "oak-platform-lane"
73
SKIP_RETURNCODE = 77
74
EXIT_ALL_SKIPPED = 3
75
PR_URL_RE = re.compile(r"/pull/(\d+)")
76
STRUCTURAL_SOURCE = "structural_platform_semantics_zero_commands"
77
78
# integration_race_nN: N task branches each through publish -> PR -> merge,
79
# serially or as fast as RateLimitBudgeter pacing allows (project plan:
80
# default 500 content ops/hour). Injected pacing waits are REPORTED as
81
# race.pacing.injected, never subtracted from operation durations.
82
RACE_SCENARIO_RE = re.compile(r"^integration_race_n(\d+)$")
83
BRANCH_TRIAGE_SCENARIO_RE = re.compile(r"^branch_triage_n(\d+)$")
84
BRANCH_FLEET_SCENARIO_RE = re.compile(r"^branch_fleet_n(\d+)$")
85
DEFAULT_RACE_OPS_PER_HOUR = 500
86
# gh-cli race orchestration (N clones + interleaved gh polling under one
87
# budgeter) is deferred to Phase 4: run_timed's one-command-at-a-time
88
# accounting cannot yet attribute interleaved waits honestly across branches.
89
RACE_CLI_DEFER_REASON = "race_cli_driver_deferred"
90
# check-instant protection variant: the HARNESS posts this required status
91
# instantly via the commit status API (deterministic, free, no Actions).
92
CHECK_INSTANT_CONTEXT = "oakbench/check-instant"
93
CHECKS_GREEN_OPERATION = "integration.checks.green.settle"
94
BRANCH_TRIAGE_FAKE_DRIVER = "fake-provider"
95
BRANCH_TRIAGE_CLASSES = ("clean", "stale", "duplicate", "conflicting")
96
BRANCH_TRIAGE_ACTIONS = {
97
    "clean": "merge",
98
    "stale": "refresh_then_merge",
99
    "duplicate": "close_duplicate",
100
    "conflicting": "keep_open_manual_resolution",
101
}
102
BRANCH_FLEET_PROVIDER_FAKE = "fake"
103
BRANCH_FLEET_PROVIDER_GITHUB_API = "github:harness-api"
104
BRANCH_FLEET_PROVIDER_OAK_CLI = "oak:cli"
105
BRANCH_FLEET_LIVE_LARGE_LIMIT = 100
106
BRANCH_FLEET_LIVE_LARGE_ENV = "OAK_BENCH_BRANCH_FLEET_LIVE_LARGE"
107
BRANCH_FLEET_CLASSIFY_PARSE_STDOUT_BYTES = 50 * 1024 * 1024
108
BRANCH_FLEET_SEED_TRANSIENT_RETRIES = 2
109
BRANCH_FLEET_SEED_RETRY_DELAY_S = 2.0
110
BRANCH_FLEET_SEED_TIMEOUT_RETCODE = 124
111
BRANCH_FLEET_SEED_WORKERS_ENV = "OAK_BENCH_BRANCH_FLEET_SEED_WORKERS"
112
BRANCH_FLEET_CLEANUP_WORKERS_ENV = "OAK_BENCH_BRANCH_FLEET_CLEANUP_WORKERS"
113
BRANCH_FLEET_SEED_WORKERS_DEFAULT = 4
114
BRANCH_FLEET_CLEANUP_WORKERS_DEFAULT = 4
115
BRANCH_FLEET_WORKERS_MAX = 16
116
# Posting a commit status needs the REST API; gh/git in this lane cannot do
117
# it, so a cli-driver run under check-instant would merge on a protected repo
118
# without the required check ever existing (or fail outright) while its rows
119
# claimed the variant held. Honest gap instead: every operation row skips.
120
CHECK_INSTANT_CLI_SKIP_REASON = "protection_variant_requires_api_driver:check-instant"
121
122
123
# --------------------------------------------------------------------------
124
# Configuration
125
# --------------------------------------------------------------------------
126
127
128
def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
129
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
130
    parser.add_argument("--platform", choices=("github", "oak"), default="github")
131
    parser.add_argument("--driver", choices=("cli", "harness-api", BRANCH_TRIAGE_FAKE_DRIVER), default="cli")
132
    parser.add_argument("--scenario", default="platform_capability_probe", help="Scenario name from scenarios/platform.yaml")
133
    parser.add_argument("--repo", default="", help="org/repo for github; oak repo for oak. Must be DISPOSABLE.")
134
    parser.add_argument("--results", type=Path, default=ROOT / "results" / "platform")
135
    parser.add_argument("--runs", type=int, default=1)
136
    parser.add_argument("--protection-variant", default="protection-none")
137
    parser.add_argument("--semantics-class", default="integration-default")
138
    parser.add_argument("--token-env", default="GITHUB_TOKEN", help="NAME of the env var holding the PAT")
139
    parser.add_argument("--spec", type=Path, default=DEFAULT_SPEC)
140
    parser.add_argument("--semantics-config", type=Path, default=DEFAULT_SEMANTICS)
141
    parser.add_argument("--workdir", type=Path, default=DEFAULT_WORKDIR)
142
    parser.add_argument("--admitted-output-chars", type=int, default=20_000)
143
    parser.add_argument("--keep-workdirs", action="store_true")
144
    return parser.parse_args(argv)
145
146
147
def load_platform_spec(path: Path) -> dict[str, Any]:
148
    return parse_yaml_subset(path.read_text())
149
150
151
def scenario_by_name(spec: dict[str, Any], name: str) -> Optional[dict[str, Any]]:
152
    for scenario in spec.get("scenarios") or []:
153
        if isinstance(scenario, dict) and scenario.get("name") == name:
154
            return scenario
155
    return None
156
157
158
def load_semantics(path: Path) -> dict[str, Any]:
159
    return json.loads(path.read_text())
160
161
162
def compute_semantic_contract_match(
163
    semantics: dict[str, Any], semantics_class: str, protection_variant: str
164
) -> bool:
165
    """True when this run executes a declared semantics class under an
166
    implemented protection variant β€” the precondition for any cross-platform
167
    comparison of its rows."""
168
    classes = semantics.get("classes") or {}
169
    implemented = semantics.get("implemented_protection_variants") or []
170
    return semantics_class in classes and protection_variant in implemented
171
172
173
def platform_comparison_key(
174
    semantics_version: Any, semantics_class: str, protection_variant: str
175
) -> str:
176
    """Stable join key for cross-platform platform-lane comparisons.
177
178
    The protection variant is part of the key by design: a
179
    protection-none row and a check-instant row may share a semantics class,
180
    but they are not the same hosted workflow contract.
181
    """
182
    return f"{semantics_version}:{semantics_class}:{protection_variant}"
183
184
185
def platform_rows_comparable(row_a: dict[str, Any], row_b: dict[str, Any]) -> bool:
186
    """Pure guard for consumers: compare only same semantics+variant rows."""
187
    if row_a.get("semantic_contract_match") is not True:
188
        return False
189
    if row_b.get("semantic_contract_match") is not True:
190
        return False
191
    key_a = row_a.get("platform_comparison_key")
192
    key_b = row_b.get("platform_comparison_key")
193
    if key_a is not None or key_b is not None:
194
        return key_a == key_b
195
    # Backward-compatible fallback for older rows that predate the explicit key.
196
    return (
197
        row_a.get("platform_semantics_version") or row_a.get("semantics_version"),
198
        row_a.get("platform_semantics_class"),
199
        row_a.get("protection_variant"),
200
    ) == (
201
        row_b.get("platform_semantics_version") or row_b.get("semantics_version"),
202
        row_b.get("platform_semantics_class"),
203
        row_b.get("protection_variant"),
204
    )
205
206
207
def protection_variant_executable(
208
    platform_name: str, driver: str, protection_variant: str
209
) -> bool:
210
    """Whether this driver can actually EXECUTE the protection variant.
211
212
    check-instant means the harness posts the required commit status via the
213
    REST API; on GitHub only the harness-api driver can do that, so a
214
    cli-driver run never executes the variant and its rows must never claim
215
    semantic_contract_match (the cli runner emits
216
    CHECK_INSTANT_CLI_SKIP_REASON skip rows instead). Everything else is
217
    unchanged: implementedness is compute_semantic_contract_match's job."""
218
    if driver == BRANCH_TRIAGE_FAKE_DRIVER:
219
        return False
220
    if (
221
        platform_name == "github"
222
        and driver == "cli"
223
        and protection_variant == "check-instant"
224
    ):
225
        return False
226
    return True
227
228
229
@dataclass(frozen=True)
230
class Settings:
231
    interval_s: float
232
    settle_timeout_s: float
233
    timeout_s: float
234
    admitted: int
235
236
237
def scenario_settings(spec: dict[str, Any], scenario: dict[str, Any], admitted: int) -> Settings:
238
    defaults = spec.get("defaults") or {}
239
    interval = scenario.get("poll_interval_s", defaults.get("settle_interval_s", 2))
240
    return Settings(
241
        interval_s=float(interval),
242
        settle_timeout_s=float(defaults.get("settle_timeout_s", 300)),
243
        timeout_s=float(defaults.get("timeout_seconds", 120)),
244
        admitted=admitted,
245
    )
246
247
248
# --------------------------------------------------------------------------
249
# Readiness: missing PAT/repos/binaries become skip rows, never failures.
250
# --------------------------------------------------------------------------
251
252
253
def readiness_skip_reason(
254
    platform_name: str,
255
    driver: str,
256
    repo: str,
257
    token_env: str,
258
    *,
259
    requires_remote: bool,
260
    which: Callable[[str], Optional[str]] = shutil.which,
261
    environ: Optional[dict[str, str]] = None,
262
) -> Optional[str]:
263
    if driver == BRANCH_TRIAGE_FAKE_DRIVER:
264
        return None
265
    env = os.environ if environ is None else environ
266
    if platform_name == "github":
267
        if driver == "cli" and which("gh") is None:
268
            return "gh_cli_missing"
269
        if not str(env.get(token_env, "")).strip():
270
            return f"github_token_missing:{token_env}"
271
        if requires_remote and not repo:
272
            return "github_repo_not_configured"
273
        return None
274
    if driver == "harness-api":
275
        return "oak_harness_api_not_implemented"
276
    if which("oak") is None:
277
        return "oak_cli_missing"
278
    if requires_remote and not repo:
279
        return "oak_remote_missing"
280
    if requires_remote and not oak_credentials_path(env).is_file():
281
        return "oak_credentials_missing"
282
    return None
283
284
285
def oak_cli_commit_push_skip_reason(
286
    env: dict[str, str],
287
    *,
288
    runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
289
) -> Optional[str]:
290
    """Return a skip reason when the live Oak CLI cannot publish explicitly."""
291
    try:
292
        probe = runner(
293
            ["oak", "commit", "--help"],
294
            capture_output=True,
295
            text=True,
296
            env=env,
297
            timeout=15,
298
            check=False,
299
        )
300
    except FileNotFoundError:
301
        return "oak_cli_missing"
302
    except subprocess.TimeoutExpired:
303
        return "oak_commit_push_capability_timeout"
304
    help_text = (probe.stdout or "") + "\n" + (probe.stderr or "")
305
    if probe.returncode != 0:
306
        return "oak_commit_push_capability_failed"
307
    if "--push" not in help_text:
308
        return "oak_commit_push_not_supported:requires_current_oak"
309
    return None
310
311
312
# --------------------------------------------------------------------------
313
# Row builders
314
# --------------------------------------------------------------------------
315
316
317
def base_env() -> dict[str, str]:
318
    return oakbench_base_env(
319
        author_name="Oak Platform Lane",
320
        author_email="[email protected]",
321
        oak_author="oak-platform-lane",
322
    )
323
324
325
def oak_credentials_path(environ: Optional[dict[str, str]] = None) -> Path:
326
    env = os.environ if environ is None else environ
327
    override = str(env.get("OAK_CREDENTIALS_FILE") or "").strip()
328
    if override:
329
        return Path(override).expanduser()
330
    return Path(str(env.get("HOME") or "")).expanduser() / ".oak" / "credentials"
331
332
333
def install_oak_credentials(env: dict[str, str]) -> bool:
334
    source = oak_credentials_path()
335
    if not source.is_file():
336
        return False
337
    dest = Path(env["HOME"]) / ".oak" / "credentials"
338
    dest.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
339
    shutil.copy2(source, dest)
340
    dest.chmod(0o600)
341
    return True
342
343
344
def cli_env(args: argparse.Namespace) -> dict[str, str]:
345
    env = base_env()
346
    token = os.environ.get(args.token_env, "")
347
    if args.platform == "github" and token:
348
        # gh reads GH_TOKEN; git over https uses it via gh's credential helper.
349
        env.setdefault("GH_TOKEN", token)
350
        env.setdefault("GITHUB_TOKEN", token)
351
    if args.platform == "oak":
352
        install_oak_credentials(env)
353
    return env
354
355
356
def null_token_fields() -> dict[str, Any]:
357
    return {
358
        "estimated_tokens_total": None,
359
        "estimated_tokens_input": None,
360
        "estimated_tokens_output": None,
361
        "estimated_tokens_agent_emitted": None,
362
        "estimated_tokens_agent_ingested": None,
363
        "estimated_cost_weighted_tokens": None,
364
        "token_fields_note": (
365
            "harness-api driver: REST JSON is not an agent transcript; "
366
            "token costs would be fiction. Null means unmeasured (ADR-0002)."
367
        ),
368
    }
369
370
371
def fake_provider_token_fields() -> dict[str, Any]:
372
    return {
373
        "estimated_tokens_total": None,
374
        "estimated_tokens_input": None,
375
        "estimated_tokens_output": None,
376
        "estimated_tokens_agent_emitted": None,
377
        "estimated_tokens_agent_ingested": None,
378
        "estimated_cost_weighted_tokens": None,
379
        "raw_output_bytes": None,
380
        "stdout_bytes": None,
381
        "stderr_bytes": None,
382
        "token_fields_note": (
383
            "fake-provider driver: in-process deterministic oracle fixture; "
384
            "no terminal command or agent-visible command output exists."
385
        ),
386
    }
387
388
389
def skip_row(
390
    meta: dict[str, Any],
391
    scenario: str,
392
    operation: str,
393
    run_index: int,
394
    reason: str,
395
    *,
396
    settings: Optional[Settings] = None,
397
) -> dict[str, Any]:
398
    row = {
399
        **meta,
400
        "scenario": scenario,
401
        "run": run_index,
402
        "operation": operation,
403
        "elapsed_ms": 0.0,
404
        "returncode": SKIP_RETURNCODE,
405
        "command": [],
406
        "skipped": True,
407
        "skip_reason": reason,
408
        "tool_call_count": 0,
409
    }
410
    if settings is not None and operation.endswith(".settle"):
411
        row.update(
412
            {
413
                "settled": None,
414
                "settle_elapsed_ms": None,
415
                "observed_wait_ms": 0.0,
416
                "poll_iterations": 0,
417
                "poll_interval_s": settings.interval_s,
418
                "poll_quantization_ms": settings.interval_s * 1000.0,
419
            }
420
        )
421
    return row
422
423
424
def cli_step_row(
425
    meta: dict[str, Any],
426
    scenario: str,
427
    operation: str,
428
    command: list[str],
429
    cwd: Path,
430
    run_index: int,
431
    env: dict[str, str],
432
    admitted: int,
433
    full_stdout_bytes: Optional[int] = None,
434
) -> dict[str, Any]:
435
    capture = run_timed(command, cwd, env, admitted, full_output_bytes=full_stdout_bytes)
436
    row = {
437
        **meta,
438
        "scenario": scenario,
439
        "run": run_index,
440
        "operation": operation,
441
        "elapsed_ms": round(capture.elapsed_ms, 3),
442
        "returncode": capture.returncode,
443
        "command": command,
444
        "tool_call_count": 1,
445
        "terminal_tool_call_count": 1,
446
        **oakbench_tokens.interaction_token_fields(
447
            command_display(command),
448
            capture.stdout_text,
449
            capture.stderr_text,
450
            capture.stdout_bytes,
451
            capture.stderr_bytes,
452
            capture.stdout_truncated,
453
            capture.stderr_truncated,
454
        ),
455
        "_stdout": capture.stdout_full_text
456
        if capture.stdout_full_text is not None
457
        else capture.stdout_text,
458
    }
459
    if capture.returncode != 0:
460
        row["stderr"] = capture.stderr_text[-4000:]
461
    return row
462
463
464
def cli_sequence_row(
465
    meta: dict[str, Any],
466
    scenario: str,
467
    operation: str,
468
    commands: list[list[str]],
469
    cwd: Path,
470
    run_index: int,
471
    env: dict[str, str],
472
    admitted: int,
473
) -> dict[str, Any]:
474
    start = platform_clock.monotonic_ms()
475
    stdout_parts: list[str] = []
476
    stderr_parts: list[str] = []
477
    failed: Optional[subprocess.CompletedProcess[str]] = None
478
    for command in commands:
479
        proc = run_untimed(command, cwd, env)
480
        stdout_parts.append(proc.stdout)
481
        stderr_parts.append(proc.stderr)
482
        if proc.returncode != 0:
483
            failed = proc
484
            break
485
    elapsed_ms = platform_clock.monotonic_ms() - start
486
    stdout = "\n".join(part for part in stdout_parts if part)
487
    stderr = "\n".join(part for part in stderr_parts if part)
488
    command_text = "\n".join(command_display(command) for command in commands)
489
    row = {
490
        **meta,
491
        "scenario": scenario,
492
        "run": run_index,
493
        "operation": operation,
494
        "elapsed_ms": round(elapsed_ms, 3),
495
        "returncode": 0 if failed is None else int(failed.returncode),
496
        "command": commands,
497
        "tool_call_count": len(commands if failed is None else commands[: len(stdout_parts)]),
498
        "terminal_tool_call_count": len(commands if failed is None else commands[: len(stdout_parts)]),
499
        **oakbench_tokens.interaction_token_fields(
500
            command_text,
501
            stdout[:admitted],
502
            stderr[:admitted],
503
            len(stdout.encode("utf-8")),
504
            len(stderr.encode("utf-8")),
505
            len(stdout) > admitted,
506
            len(stderr) > admitted,
507
        ),
508
        "_stdout": stdout,
509
    }
510
    if failed is not None:
511
        row["stderr"] = stderr[-4000:]
512
    return row
513
514
515
def settle_fields(result: dict[str, Any]) -> dict[str, Any]:
516
    return {
517
        "settled": result["settled"],
518
        "settle_elapsed_ms": result["elapsed_ms"],
519
        "observed_wait_ms": round(result["observed_wait_ms"], 3),
520
        "poll_iterations": result["poll_iterations"],
521
        "poll_interval_s": result["poll_interval_s"],
522
        "poll_quantization_ms": result["poll_quantization_ms"],
523
    }
524
525
526
def settle_row(
527
    meta: dict[str, Any],
528
    scenario: str,
529
    operation: str,
530
    run_index: int,
531
    poll_fn: Callable[[], Any],
532
    settings: Settings,
533
    command: list[str],
534
    poll_is_tool_call: bool,
535
    clock: Callable[[], float] = time.monotonic,
536
    sleep: Callable[[float], None] = time.sleep,
537
) -> dict[str, Any]:
538
    result = platform_clock.settle(
539
        poll_fn,
540
        interval_s=settings.interval_s,
541
        timeout_s=settings.settle_timeout_s,
542
        clock=clock,
543
        sleep=sleep,
544
    )
545
    # Print the resolution bound the way devloop prints detection limits: a
546
    # settle observed at this cadence can never claim finer precision.
547
    print(
548
        f"[quantization] {operation} poll_interval_s={settings.interval_s} "
549
        f"poll_quantization_ms={result['poll_quantization_ms']}",
550
        flush=True,
551
    )
552
    return {
553
        **meta,
554
        "scenario": scenario,
555
        "run": run_index,
556
        "operation": operation,
557
        # The wall time spent on this operation is always measured; the
558
        # settle duration itself is null on timeout (platform_clock policy).
559
        "elapsed_ms": round(result["observed_wait_ms"], 3),
560
        "returncode": 0 if result["settled"] else 1,
561
        "command": command,
562
        "tool_call_count": result["poll_iterations"] if poll_is_tool_call else None,
563
        **settle_fields(result),
564
    }
565
566
567
def structural_row(
568
    meta: dict[str, Any],
569
    scenario: str,
570
    operation: str,
571
    run_index: int,
572
    note: str,
573
) -> dict[str, Any]:
574
    """A platform-semantics zero: the operation has no equivalent because the
575
    platform completes it synchronously elsewhere. Zero commands were run
576
    (measured zero); the settle value is null (no equivalent, ADR-0002)."""
577
    return {
578
        **meta,
579
        "scenario": scenario,
580
        "run": run_index,
581
        "operation": operation,
582
        "elapsed_ms": 0.0,
583
        "returncode": 0,
584
        "command": [],
585
        "structural": True,
586
        "note": note,
587
        "settled": True,
588
        "settle_elapsed_ms": None,
589
        "poll_iterations": 0,
590
        "tool_call_count": 0,
591
        "measurement_source": STRUCTURAL_SOURCE,
592
    }
593
594
595
def metrics_snapshot(client: GitHubClient) -> dict[str, Any]:
596
    metrics = client.api_metrics
597
    return {**metrics, "request_ids": list(metrics["request_ids"])}
598
599
600
def metrics_delta(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]:
601
    delta_count = after["request_count"] - before["request_count"]
602
    ids = after["request_ids"][-delta_count:] if delta_count else []
603
    return {
604
        "request_count": delta_count,
605
        "request_bytes": after["request_bytes"] - before["request_bytes"],
606
        "response_bytes": after["response_bytes"] - before["response_bytes"],
607
        "rate_limit_remaining": after["rate_limit_remaining"],
608
        "request_ids": ids,
609
    }
610
611
612
def total_row(
613
    meta: dict[str, Any],
614
    scenario: str,
615
    run_index: int,
616
    rows: list[dict[str, Any]],
617
    driver: str,
618
) -> dict[str, Any]:
619
    failures = [
620
        row for row in rows if row_returncode(row) not in (0, SKIP_RETURNCODE)
621
    ]
622
    row: dict[str, Any] = {
623
        **meta,
624
        "scenario": scenario,
625
        "run": run_index,
626
        "operation": "integration.total",
627
        "elapsed_ms": round(sum(float(row.get("elapsed_ms") or 0.0) for row in rows), 3),
628
        "returncode": 1 if failures else 0,
629
        "command": [row["command"] for row in rows],
630
        "summarized_operations": [row["operation"] for row in rows],
631
        "poll_iterations_total": sum(int(row.get("poll_iterations") or 0) for row in rows),
632
    }
633
    if driver == "harness-api":
634
        row.update(null_token_fields())
635
        row["tool_call_count"] = None
636
        deltas = [row_["api_metrics"] for row_ in rows if isinstance(row_.get("api_metrics"), dict)]
637
        row["api_metrics"] = {
638
            "request_count": sum(delta["request_count"] for delta in deltas),
639
            "request_bytes": sum(delta["request_bytes"] for delta in deltas),
640
            "response_bytes": sum(delta["response_bytes"] for delta in deltas),
641
            "rate_limit_remaining": deltas[-1]["rate_limit_remaining"] if deltas else None,
642
        }
643
    else:
644
        row["tool_call_count"] = sum(int(row_.get("tool_call_count") or 0) for row_ in rows)
645
        row.update(
646
            oakbench_tokens.summed_token_fields(
647
                rows, "sum_of_platform_lifecycle_steps_command_plus_admitted_output_chars_div_4"
648
            )
649
        )
650
    return row
651
652
653
# --------------------------------------------------------------------------
654
# With-credentials runners (not exercised in CI; kept simple and linear).
655
# Each runner is driven by the scenario's operation list so emitted
656
# operations always match scenarios/platform.yaml (ADR-0005).
657
# --------------------------------------------------------------------------
658
659
660
def run_untimed(
661
    command: list[str], cwd: Optional[Path], env: dict[str, str], timeout: float = 300.0
662
) -> subprocess.CompletedProcess[str]:
663
    return subprocess.run(
664
        command,
665
        cwd=cwd,
666
        env=env,
667
        text=True,
668
        stdout=subprocess.PIPE,
669
        stderr=subprocess.PIPE,
670
        timeout=timeout,
671
        check=False,
672
    )
673
674
675
def run_operations(
676
    operations: list[str],
677
    handlers: dict[str, Callable[[], dict[str, Any]]],
678
    meta: dict[str, Any],
679
    scenario_name: str,
680
    run_index: int,
681
    driver: str,
682
) -> list[dict[str, Any]]:
683
    """Run the scenario's declared operations in order; a failed step turns
684
    the remaining steps into skip rows (a recorded gap, never a cascade of
685
    misleading failures)."""
686
    rows: list[dict[str, Any]] = []
687
    aborted_at: Optional[str] = None
688
    for operation in operations:
689
        if operation == "integration.total":
690
            rows.append(total_row(meta, scenario_name, run_index, rows, driver))
691
            continue
692
        if aborted_at is not None:
693
            rows.append(
694
                skip_row(meta, scenario_name, operation, run_index, f"prior_step_failed:{aborted_at}")
695
            )
696
            continue
697
        handler = handlers.get(operation)
698
        if handler is None:
699
            rows.append(skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented"))
700
            continue
701
        row = handler()
702
        row.pop("_stdout", None)
703
        rows.append(row)
704
        if row_returncode(row) not in (0, SKIP_RETURNCODE):
705
            aborted_at = operation
706
    return rows
707
708
709
def run_capability_probe(
710
    args: argparse.Namespace, meta: dict[str, Any], run_index: int, settings: Settings
711
) -> list[dict[str, Any]]:
712
    scenario_name = "platform_capability_probe"
713
    if args.driver == "harness-api":
714
        if not args.repo:
715
            return [skip_row(meta, scenario_name, "capability.probe", run_index, "github_repo_not_configured")]
716
        client = GitHubClient(token=os.environ.get(args.token_env))
717
        return [
718
            api_step_row(
719
                meta, scenario_name, "capability.probe", run_index, client,
720
                lambda: client.get_repo(args.repo), [f"api:GET /repos/{args.repo}"],
721
            )
722
        ]
723
    command = ["gh", "auth", "status"] if args.platform == "github" else ["oak", "--version"]
724
    return [
725
        cli_step_row(
726
            meta, scenario_name, "capability.probe", command, Path.cwd(), run_index,
727
            cli_env(args), settings.admitted,
728
        )
729
    ]
730
731
732
def api_step_row(
733
    meta: dict[str, Any],
734
    scenario: str,
735
    operation: str,
736
    run_index: int,
737
    client: GitHubClient,
738
    func: Callable[[], Any],
739
    command: list[str],
740
) -> dict[str, Any]:
741
    before = metrics_snapshot(client)
742
    start = platform_clock.monotonic_ms()
743
    error: Optional[GitHubApiError] = None
744
    try:
745
        func()
746
    except GitHubApiError as exc:
747
        error = exc
748
    elapsed_ms = platform_clock.monotonic_ms() - start
749
    row = {
750
        **meta,
751
        "scenario": scenario,
752
        "run": run_index,
753
        "operation": operation,
754
        "elapsed_ms": round(elapsed_ms, 3),
755
        "returncode": 0 if error is None else 1,
756
        "command": command,
757
        "tool_call_count": None,
758
        "api_metrics": metrics_delta(before, metrics_snapshot(client)),
759
        **null_token_fields(),
760
    }
761
    if error is not None:
762
        row["stderr"] = str(error)[:4000]
763
    return row
764
765
766
def api_settle_row(
767
    meta: dict[str, Any],
768
    scenario: str,
769
    operation: str,
770
    run_index: int,
771
    client: GitHubClient,
772
    poll_fn: Callable[[], Any],
773
    settings: Settings,
774
    command: list[str],
775
    clock: Callable[[], float] = time.monotonic,
776
    sleep: Callable[[float], None] = time.sleep,
777
) -> dict[str, Any]:
778
    before = metrics_snapshot(client)
779
    row = settle_row(
780
        meta, scenario, operation, run_index, poll_fn, settings, command,
781
        poll_is_tool_call=False, clock=clock, sleep=sleep,
782
    )
783
    row["api_metrics"] = metrics_delta(before, metrics_snapshot(client))
784
    row.update(null_token_fields())
785
    return row
786
787
788
def api_check_instant_checks_green_row(
789
    meta: dict[str, Any],
790
    scenario_name: str,
791
    run_index: int,
792
    client: GitHubClient,
793
    repo: str,
794
    head_sha: str,
795
    settings: Settings,
796
    *,
797
    pace: Callable[[], None] = lambda: None,
798
    clock: Callable[[], float] = time.monotonic,
799
    sleep: Callable[[float], None] = time.sleep,
800
) -> dict[str, Any]:
801
    """check-instant, executed: the harness posts the required status itself
802
    via the commit status API (deterministic, free, no Actions), then settles
803
    on the combined status going green before merge. Shared by the race
804
    runner and the non-race harness-api runner so the variant means the same
805
    thing wherever it is executed (ADR-0005)."""
806
    operation = CHECKS_GREEN_OPERATION
807
    command = [
808
        f"api:POST /repos/{repo}/statuses/<head-sha>",
809
        f"api:GET /repos/{repo}/commits/<head-sha>/status (poll)",
810
    ]
811
    before = metrics_snapshot(client)
812
    error_text: Optional[str] = None
813
    post_start = platform_clock.monotonic_ms()
814
    if not head_sha:
815
        error_text = "head_sha_unknown_cannot_post_status"
816
    else:
817
        try:
818
            pace()
819
            client.post_status(
820
                repo, head_sha, "success", CHECK_INSTANT_CONTEXT,
821
                description="oakbench harness-posted instant check",
822
            )
823
        except GitHubApiError as exc:
824
            error_text = str(exc)[:4000]
825
    post_elapsed_ms = platform_clock.monotonic_ms() - post_start
826
    if error_text is not None:
827
        return {
828
            **meta,
829
            "scenario": scenario_name,
830
            "run": run_index,
831
            "operation": operation,
832
            "elapsed_ms": round(post_elapsed_ms, 3),
833
            "returncode": 1,
834
            "command": command,
835
            "tool_call_count": None,
836
            "stderr": error_text,
837
            "api_metrics": metrics_delta(before, metrics_snapshot(client)),
838
            **null_token_fields(),
839
        }
840
841
    def poll() -> bool:
842
        try:
843
            combined = client.get_combined_status(repo, head_sha)
844
        except GitHubApiError:
845
            return False
846
        return combined.get("state") == "success"
847
848
    result = platform_clock.settle(
849
        poll, interval_s=settings.interval_s, timeout_s=settings.settle_timeout_s,
850
        clock=clock, sleep=sleep,
851
    )
852
    return {
853
        **meta,
854
        "scenario": scenario_name,
855
        "run": run_index,
856
        "operation": operation,
857
        # post + settle: the harness-posted status is part of the
858
        # operation's measured cost, never hidden setup.
859
        "elapsed_ms": round(post_elapsed_ms + result["observed_wait_ms"], 3),
860
        "returncode": 0 if result["settled"] else 1,
861
        "command": command,
862
        "tool_call_count": None,
863
        **settle_fields(result),
864
        "status_post_elapsed_ms": round(post_elapsed_ms, 3),
865
        "status_context": CHECK_INSTANT_CONTEXT,
866
        "api_metrics": metrics_delta(before, metrics_snapshot(client)),
867
        **null_token_fields(),
868
    }
869
870
871
def run_github_api_scenario(
872
    args: argparse.Namespace,
873
    scenario: dict[str, Any],
874
    meta: dict[str, Any],
875
    run_index: int,
876
    settings: Settings,
877
    *,
878
    client: Optional[GitHubClient] = None,
879
    clock: Callable[[], float] = time.monotonic,
880
    sleep: Callable[[float], None] = time.sleep,
881
) -> list[dict[str, Any]]:
882
    scenario_name = str(scenario["name"])
883
    operations = [str(op) for op in scenario.get("operations") or []]
884
    check_instant = args.protection_variant == "check-instant"
885
    if check_instant and CHECKS_GREEN_OPERATION not in operations:
886
        # check-instant is only executed when the required status is actually
887
        # posted and settled green BEFORE the merge request; the non-race
888
        # anatomy scenarios do not declare the operation, so it is inserted
889
        # ahead of integration.merge.request (the race ordering).
890
        anchor = (
891
            operations.index("integration.merge.request")
892
            if "integration.merge.request" in operations
893
            else len(operations)
894
        )
895
        operations = operations[:anchor] + [CHECKS_GREEN_OPERATION] + operations[anchor:]
896
    repo = args.repo
897
    if client is None:
898
        client = GitHubClient(token=os.environ.get(args.token_env))
899
    branch = f"oakbench-platform-{meta['bench_id'].lower()}-r{run_index}"
900
    payload = f"platform lane {meta['bench_id']} run={run_index}\n".encode("utf-8")
901
    state: dict[str, Any] = {}
902
903
    try:
904
        state["base_sha"] = client.get_main_sha(repo)
905
    except (GitHubApiError, KeyError, json.JSONDecodeError) as exc:
906
        return [
907
            skip_row(meta, scenario_name, op, run_index, f"setup_failed:{str(exc)[:160]}")
908
            for op in operations
909
        ]
910
911
    def publish() -> dict[str, Any]:
912
        def do() -> None:
913
            client.create_ref(repo, f"refs/heads/{branch}", state["base_sha"])
914
            put = client.put_file(
915
                repo,
916
                path=f".oak-bench/platform-{run_index}.md",
917
                message=f"bench platform lane {meta['bench_id']} r{run_index}",
918
                content=payload,
919
                branch=branch,
920
            )
921
            commit = put.get("commit") if isinstance(put, dict) else None
922
            if isinstance(commit, dict) and commit.get("sha"):
923
                state["head_sha"] = str(commit["sha"])
924
        return api_step_row(
925
            meta, scenario_name, "branch.publish", run_index, client, do,
926
            [f"api:POST /repos/{repo}/git/refs", f"api:PUT /repos/{repo}/contents/..."],
927
        )
928
929
    def open_pr() -> dict[str, Any]:
930
        def do() -> None:
931
            pr = client.create_pr(
932
                repo, title=f"bench platform lane r{run_index}", head=branch, base="main"
933
            )
934
            state["pr_number"] = int(pr["number"])
935
            head = pr.get("head") or {}
936
            if not state.get("head_sha") and head.get("sha"):
937
                state["head_sha"] = str(head["sha"])
938
        return api_step_row(
939
            meta, scenario_name, "integration.open", run_index, client, do,
940
            [f"api:POST /repos/{repo}/pulls"],
941
        )
942
943
    def get_pr_quiet() -> Optional[dict[str, Any]]:
944
        try:
945
            return client.get_pr(repo, state["pr_number"])
946
        except GitHubApiError:
947
            return None
948
949
    def visible() -> dict[str, Any]:
950
        return api_settle_row(
951
            meta, scenario_name, "integration.visible.settle", run_index, client,
952
            lambda: get_pr_quiet() is not None, settings,
953
            [f"api:GET /repos/{repo}/pulls/<n> (poll)"], clock=clock, sleep=sleep,
954
        )
955
956
    def mergeable() -> dict[str, Any]:
957
        def poll() -> bool:
958
            pr = get_pr_quiet()
959
            return pr is not None and pr.get("mergeable") is not None
960
        return api_settle_row(
961
            meta, scenario_name, "integration.mergeable.settle", run_index, client,
962
            poll, settings, [f"api:GET /repos/{repo}/pulls/<n> mergeable (poll)"],
963
            clock=clock, sleep=sleep,
964
        )
965
966
    def checks_green() -> dict[str, Any]:
967
        # Reached only under check-instant (the operation is inserted above);
968
        # the status is posted after the PR is open and must settle green
969
        # before integration.merge.request runs.
970
        return api_check_instant_checks_green_row(
971
            meta, scenario_name, run_index, client, repo,
972
            str(state.get("head_sha") or ""), settings, clock=clock, sleep=sleep,
973
        )
974
975
    def merge() -> dict[str, Any]:
976
        return api_step_row(
977
            meta, scenario_name, "integration.merge.request", run_index, client,
978
            lambda: client.merge_pr(repo, state["pr_number"]),
979
            [f"api:PUT /repos/{repo}/pulls/<n>/merge"],
980
        )
981
982
    def merged() -> dict[str, Any]:
983
        def poll() -> bool:
984
            pr = get_pr_quiet()
985
            return bool(pr and pr.get("merged"))
986
        return api_settle_row(
987
            meta, scenario_name, "integration.merged.settle", run_index, client,
988
            poll, settings, [f"api:GET /repos/{repo}/pulls/<n> merged (poll)"],
989
            clock=clock, sleep=sleep,
990
        )
991
992
    def main_settle() -> dict[str, Any]:
993
        def poll() -> bool:
994
            try:
995
                return client.get_main_sha(repo) != state["base_sha"]
996
            except GitHubApiError:
997
                return False
998
        return api_settle_row(
999
            meta, scenario_name, "integration.main.settle", run_index, client,
1000
            poll, settings, [f"api:GET /repos/{repo}/git/ref/heads/main (poll)"],
1001
            clock=clock, sleep=sleep,
1002
        )
1003
1004
    def sync_local() -> dict[str, Any]:
1005
        row = api_step_row(
1006
            meta, scenario_name, "integration.sync.local", run_index, client,
1007
            lambda: client.get_main_sha(repo),
1008
            [f"api:GET /repos/{repo}/git/ref/heads/main"],
1009
        )
1010
        row["note"] = (
1011
            "harness-api driver has no local working copy; this is the API-side "
1012
            "equivalent (observe merged main ref). Local-checkout sync cost is a "
1013
            "cli-driver measurement."
1014
        )
1015
        return row
1016
1017
    handlers = {
1018
        "branch.publish": publish,
1019
        "integration.open": open_pr,
1020
        "integration.visible.settle": visible,
1021
        "integration.mergeable.settle": mergeable,
1022
        CHECKS_GREEN_OPERATION: checks_green,
1023
        "integration.merge.request": merge,
1024
        "integration.merged.settle": merged,
1025
        "integration.main.settle": main_settle,
1026
        "integration.sync.local": sync_local,
1027
    }
1028
    rows = run_operations(operations, handlers, meta, scenario_name, run_index, "harness-api")
1029
    try:
1030
        client.delete_ref(repo, f"heads/{branch}")
1031
    except GitHubApiError:
1032
        pass  # untimed cleanup; merged-branch deletion failures are not data
1033
    return rows
1034
1035
1036
def run_github_cli_scenario(
1037
    args: argparse.Namespace,
1038
    scenario: dict[str, Any],
1039
    meta: dict[str, Any],
1040
    run_index: int,
1041
    run_root: Path,
1042
    settings: Settings,
1043
) -> list[dict[str, Any]]:
1044
    scenario_name = str(scenario["name"])
1045
    operations = [str(op) for op in scenario.get("operations") or []]
1046
    if args.protection_variant == "check-instant":
1047
        # gh/git cannot post the harness commit status, so the cli driver
1048
        # cannot execute this variant: running anyway would merge (or fail)
1049
        # while the rows claimed the protection held. Honest gap instead.
1050
        return [
1051
            skip_row(
1052
                meta,
1053
                scenario_name,
1054
                op,
1055
                run_index,
1056
                CHECK_INSTANT_CLI_SKIP_REASON,
1057
                settings=settings,
1058
            )
1059
            for op in operations
1060
        ]
1061
    repo = args.repo
1062
    env = cli_env(args)
1063
    branch = f"oakbench-platform-{meta['bench_id'].lower()}-r{run_index}"
1064
    workdir = run_root / f"github-cli-r{run_index}"
1065
    state: dict[str, Any] = {}
1066
1067
    # Untimed setup: the lane's precondition is "task branch exists locally".
1068
    def setup() -> Optional[str]:
1069
        clone = run_untimed(["gh", "repo", "clone", repo, str(workdir)], run_root, env)
1070
        if clone.returncode != 0:
1071
            return f"setup_failed:clone:{clone.stderr.strip()[:120]}"
1072
        for command in (
1073
            ["git", "switch", "-c", branch],
1074
            ["/bin/zsh", "-c", f"mkdir -p .oak-bench && echo 'platform lane r{run_index}' > .oak-bench/platform-{run_index}.md"],
1075
            ["git", "add", "."],
1076
            ["git", "commit", "-q", "-m", f"bench platform lane {meta['bench_id']} r{run_index}"],
1077
        ):
1078
            proc = run_untimed(command, workdir, env)
1079
            if proc.returncode != 0:
1080
                return f"setup_failed:{command[0]}:{proc.stderr.strip()[:120]}"
1081
        ls = run_untimed(["git", "ls-remote", "origin", "refs/heads/main"], workdir, env)
1082
        state["base_sha"] = ls.stdout.split()[0] if ls.returncode == 0 and ls.stdout.split() else ""
1083
        return None
1084
1085
    setup_error = setup()
1086
    if setup_error:
1087
        return [skip_row(meta, scenario_name, op, run_index, setup_error) for op in operations]
1088
1089
    def remote_main_sha() -> str:
1090
        ls = run_untimed(["git", "ls-remote", "origin", "refs/heads/main"], workdir, env)
1091
        parts = ls.stdout.split()
1092
        return parts[0] if ls.returncode == 0 and parts else ""
1093
1094
    def pr_number() -> str:
1095
        return str(state.get("pr_number", ""))
1096
1097
    def publish() -> dict[str, Any]:
1098
        return cli_step_row(
1099
            meta, scenario_name, "branch.publish",
1100
            ["git", "push", "-q", "-u", "origin", branch], workdir, run_index, env, settings.admitted,
1101
        )
1102
1103
    def open_pr() -> dict[str, Any]:
1104
        row = cli_step_row(
1105
            meta, scenario_name, "integration.open",
1106
            [
1107
                "gh", "pr", "create", "--repo", repo, "--head", branch, "--base", "main",
1108
                "--title", f"bench platform lane r{run_index}",
1109
                "--body", f"Disposable platform-lane benchmark PR ({meta['bench_id']}).",
1110
            ],
1111
            workdir, run_index, env, settings.admitted,
1112
        )
1113
        match = PR_URL_RE.search(row.get("_stdout", ""))
1114
        if match:
1115
            state["pr_number"] = int(match.group(1))
1116
        elif row["returncode"] == 0:
1117
            row["returncode"] = 1
1118
            row["stderr"] = "pr_number_unparsed_from_gh_output"
1119
        return row
1120
1121
    def gh_pr_json(field: str) -> str:
1122
        proc = run_untimed(
1123
            ["gh", "pr", "view", pr_number(), "--repo", repo, "--json", field, "-q", f".{field}"],
1124
            workdir, env,
1125
        )
1126
        return proc.stdout.strip() if proc.returncode == 0 else ""
1127
1128
    def visible() -> dict[str, Any]:
1129
        return settle_row(
1130
            meta, scenario_name, "integration.visible.settle", run_index,
1131
            lambda: bool(gh_pr_json("state")), settings,
1132
            ["gh", "pr", "view", "<n>", "--json", "state", "(poll)"], poll_is_tool_call=True,
1133
        )
1134
1135
    def mergeable() -> dict[str, Any]:
1136
        return settle_row(
1137
            meta, scenario_name, "integration.mergeable.settle", run_index,
1138
            lambda: gh_pr_json("mergeable") not in ("", "UNKNOWN"), settings,
1139
            ["gh", "pr", "view", "<n>", "--json", "mergeable", "(poll)"], poll_is_tool_call=True,
1140
        )
1141
1142
    def merge() -> dict[str, Any]:
1143
        return cli_step_row(
1144
            meta, scenario_name, "integration.merge.request",
1145
            ["gh", "pr", "merge", pr_number(), "--repo", repo, "--merge"],
1146
            workdir, run_index, env, settings.admitted,
1147
        )
1148
1149
    def merged() -> dict[str, Any]:
1150
        return settle_row(
1151
            meta, scenario_name, "integration.merged.settle", run_index,
1152
            lambda: gh_pr_json("state") == "MERGED", settings,
1153
            ["gh", "pr", "view", "<n>", "--json", "state", "(poll)"], poll_is_tool_call=True,
1154
        )
1155
1156
    def main_settle() -> dict[str, Any]:
1157
        return settle_row(
1158
            meta, scenario_name, "integration.main.settle", run_index,
1159
            lambda: remote_main_sha() not in ("", state["base_sha"]), settings,
1160
            ["git", "ls-remote", "origin", "refs/heads/main", "(poll)"], poll_is_tool_call=True,
1161
        )
1162
1163
    def sync_local() -> dict[str, Any]:
1164
        run_untimed(["git", "switch", "-q", "main"], workdir, env)
1165
        return cli_step_row(
1166
            meta, scenario_name, "integration.sync.local",
1167
            ["git", "pull", "-q", "--ff-only", "origin", "main"],
1168
            workdir, run_index, env, settings.admitted,
1169
        )
1170
1171
    handlers = {
1172
        "branch.publish": publish,
1173
        "integration.open": open_pr,
1174
        "integration.visible.settle": visible,
1175
        "integration.mergeable.settle": mergeable,
1176
        "integration.merge.request": merge,
1177
        "integration.merged.settle": merged,
1178
        "integration.main.settle": main_settle,
1179
        "integration.sync.local": sync_local,
1180
    }
1181
    rows = run_operations(operations, handlers, meta, scenario_name, run_index, "cli")
1182
    run_untimed(["git", "push", "-q", "origin", "--delete", branch], workdir, env)  # untimed cleanup
1183
    return rows
1184
1185
1186
def run_oak_cli_scenario(
1187
    args: argparse.Namespace,
1188
    scenario: dict[str, Any],
1189
    meta: dict[str, Any],
1190
    run_index: int,
1191
    run_root: Path,
1192
    settings: Settings,
1193
) -> list[dict[str, Any]]:
1194
    scenario_name = str(scenario["name"])
1195
    operations = [str(op) for op in scenario.get("operations") or []]
1196
    env = cli_env(args)
1197
    workdir = run_root / f"oak-cli-r{run_index}"
1198
1199
    # Untimed setup: mount and commit so the task branch exists locally.
1200
    mount = run_untimed(["oak", "mount", args.repo, str(workdir)], run_root, env)
1201
    if mount.returncode != 0:
1202
        reason = f"setup_failed:oak_mount:{mount.stderr.strip()[:120]}"
1203
        return [skip_row(meta, scenario_name, op, run_index, reason) for op in operations]
1204
    payload_dir = workdir / ".oak-bench"
1205
    payload_dir.mkdir(parents=True, exist_ok=True)
1206
    (payload_dir / f"platform-{run_index}.md").write_text(
1207
        f"platform lane {meta['bench_id']} run={run_index}\n"
1208
    )
1209
    commit = run_untimed(["oak", "commit", "--no-verify"], workdir, env)
1210
    if commit.returncode != 0:
1211
        run_untimed(["oak", "mount", "end", str(workdir), "-f"], run_root, env)
1212
        reason = f"setup_failed:oak_commit:{commit.stderr.strip()[:120]}"
1213
        return [skip_row(meta, scenario_name, op, run_index, reason) for op in operations]
1214
1215
    def cli(operation: str, command: list[str]) -> Callable[[], dict[str, Any]]:
1216
        return lambda: cli_step_row(
1217
            meta, scenario_name, operation, command, workdir, run_index, env, settings.admitted
1218
        )
1219
1220
    def structural(operation: str, note: str) -> Callable[[], dict[str, Any]]:
1221
        return lambda: structural_row(meta, scenario_name, operation, run_index, note)
1222
1223
    # oak merge is synchronous: zero polls is structural (a measured zero β€”
1224
    # no commands run), and the settle value is null because the async
1225
    # concept has no equivalent on this platform (ADR-0002).
1226
    handlers = {
1227
        "branch.publish": cli("branch.publish", ["oak", "push"]),
1228
        "integration.open": structural(
1229
            "integration.open",
1230
            "integration object exists at publish on oak; no separate open call",
1231
        ),
1232
        "integration.visible.settle": structural(
1233
            "integration.visible.settle",
1234
            "oak push returns after the branch is visible on the server; nothing to poll",
1235
        ),
1236
        "integration.mergeable.settle": structural(
1237
            "integration.mergeable.settle", "no async mergeability concept"
1238
        ),
1239
        "integration.merge.request": cli("integration.merge.request", ["oak", "merge"]),
1240
        "integration.merged.settle": structural(
1241
            "integration.merged.settle", "oak merge is synchronous; merged state holds at return"
1242
        ),
1243
        "integration.main.settle": structural(
1244
            "integration.main.settle", "oak merge returns after main is updated; nothing to poll"
1245
        ),
1246
        "integration.sync.local": cli("integration.sync.local", ["oak", "switch", "main"]),
1247
    }
1248
    rows = run_operations(operations, handlers, meta, scenario_name, run_index, "cli")
1249
    run_untimed(["oak", "mount", "end", str(workdir), "-f"], run_root, env)  # untimed cleanup
1250
    return rows
1251
1252
1253
# --------------------------------------------------------------------------
1254
# integration_race_nN: the flagship race family. N task branches each go
1255
# through publish -> PR -> merge (per-branch anatomy rows reuse the
1256
# vocabulary above), landing serially or as fast as RateLimitBudgeter pacing
1257
# allows, plus race-level rows: race.publish.window (first publish start ->
1258
# last publish end), race.integrate.window (first integration.open -> last
1259
# integration.main.settle), race.pacing.injected (sum of budgeter-injected
1260
# waits - REPORTED, never subtracted), race.total. clock/sleep are injectable
1261
# so tests never wait on real time (fake transports only, no network).
1262
# --------------------------------------------------------------------------
1263
1264
1265
def is_race_scenario(scenario: dict[str, Any]) -> bool:
1266
    return RACE_SCENARIO_RE.match(str(scenario.get("name", ""))) is not None
1267
1268
1269
def race_branch_count(scenario: dict[str, Any]) -> int:
1270
    if scenario.get("branch_count") is not None:
1271
        return int(scenario["branch_count"])
1272
    match = RACE_SCENARIO_RE.match(str(scenario.get("name", "")))
1273
    return int(match.group(1)) if match else 1
1274
1275
1276
def race_branch_skip(
1277
    meta: dict[str, Any],
1278
    scenario_name: str,
1279
    operation: str,
1280
    run_index: int,
1281
    reason: str,
1282
    branch_index: int,
1283
) -> dict[str, Any]:
1284
    row = skip_row(meta, scenario_name, operation, run_index, reason)
1285
    row["race_branch"] = branch_index
1286
    return row
1287
1288
1289
def race_window_ms(
1290
    timeline: dict[str, list[tuple[float, float]]], start_op: str, end_op: str
1291
) -> Optional[float]:
1292
    """First start of ``start_op`` -> last end of ``end_op``, on the race's
1293
    single monotonic clock. None when either endpoint was never observed."""
1294
    starts = timeline.get(start_op) or []
1295
    ends = timeline.get(end_op) or []
1296
    if not starts or not ends:
1297
        return None
1298
    return (max(end for _, end in ends) - min(start for start, _ in starts)) * 1000.0
1299
1300
1301
def race_summary_rows(
1302
    meta: dict[str, Any],
1303
    scenario_name: str,
1304
    run_index: int,
1305
    driver: str,
1306
    race_operations: list[str],
1307
    branch_rows: list[dict[str, Any]],
1308
    timeline: dict[str, list[tuple[float, float]]],
1309
    budgeter: RateLimitBudgeter,
1310
    race_start: float,
1311
    race_end: float,
1312
    branch_count: int,
1313
) -> list[dict[str, Any]]:
1314
    failures = [
1315
        row for row in branch_rows
1316
        if row_returncode(row) not in (0, SKIP_RETURNCODE)
1317
    ]
1318
    failed_operations = {str(row.get("operation")) for row in failures}
1319
1320
    def base_row(operation: str, elapsed_ms: float, returncode: int, note: str) -> dict[str, Any]:
1321
        row: dict[str, Any] = {
1322
            **meta,
1323
            "scenario": scenario_name,
1324
            "run": run_index,
1325
            "operation": operation,
1326
            "elapsed_ms": round(elapsed_ms, 3),
1327
            "returncode": returncode,
1328
            "command": [],
1329
            "tool_call_count": None,
1330
            "race_branch_count": branch_count,
1331
            "note": note,
1332
        }
1333
        if driver == "harness-api":
1334
            row.update(null_token_fields())
1335
        return row
1336
1337
    def window_row(operation: str, start_op: str, end_op: str, note: str) -> dict[str, Any]:
1338
        window = race_window_ms(timeline, start_op, end_op)
1339
        if window is None:
1340
            # The window's endpoints were never observed (e.g. every branch
1341
            # aborted before reaching them): a recorded gap, never a zero.
1342
            row = skip_row(
1343
                meta, scenario_name, operation, run_index,
1344
                f"race_window_unobserved:{start_op}->{end_op}",
1345
            )
1346
            row["race_branch_count"] = branch_count
1347
            return row
1348
        complete = (
1349
            len(timeline.get(start_op) or []) == branch_count
1350
            and len(timeline.get(end_op) or []) == branch_count
1351
            and start_op not in failed_operations
1352
            and end_op not in failed_operations
1353
        )
1354
        row = base_row(operation, window, 0 if complete else 1, note)
1355
        row["window_start_operation"] = start_op
1356
        row["window_end_operation"] = end_op
1357
        return row
1358
1359
    def pacing_row() -> dict[str, Any]:
1360
        row = base_row(
1361
            "race.pacing.injected",
1362
            budgeter.injected_wait_s_total * 1000.0,
1363
            0,
1364
            "sum of RateLimitBudgeter-injected pacing waits across the race; "
1365
            "REPORTED here, never subtracted from any operation duration",
1366
        )
1367
        row["budget_ops_per_hour"] = budgeter.ops_per_hour
1368
        return row
1369
1370
    def total_row_race() -> dict[str, Any]:
1371
        row = base_row(
1372
            "race.total",
1373
            (race_end - race_start) * 1000.0,
1374
            1 if failures else 0,
1375
            "whole-race wall clock on one local monotonic clock: before the "
1376
            "first branch's publish to after the last branch's final operation",
1377
        )
1378
        row["summarized_operations"] = [r["operation"] for r in branch_rows]
1379
        row["poll_iterations_total"] = sum(
1380
            int(r.get("poll_iterations") or 0) for r in branch_rows
1381
        )
1382
        if driver == "harness-api":
1383
            deltas = [
1384
                r["api_metrics"] for r in branch_rows if isinstance(r.get("api_metrics"), dict)
1385
            ]
1386
            row["api_metrics"] = {
1387
                "request_count": sum(delta["request_count"] for delta in deltas),
1388
                "request_bytes": sum(delta["request_bytes"] for delta in deltas),
1389
                "response_bytes": sum(delta["response_bytes"] for delta in deltas),
1390
                "rate_limit_remaining": deltas[-1]["rate_limit_remaining"] if deltas else None,
1391
            }
1392
        else:
1393
            row["tool_call_count"] = sum(
1394
                int(r.get("tool_call_count") or 0) for r in branch_rows
1395
            )
1396
            row.update(
1397
                oakbench_tokens.summed_token_fields(
1398
                    branch_rows,
1399
                    "sum_of_platform_lifecycle_steps_command_plus_admitted_output_chars_div_4",
1400
                )
1401
            )
1402
        return row
1403
1404
    emitters: dict[str, Callable[[], dict[str, Any]]] = {
1405
        "race.publish.window": lambda: window_row(
1406
            "race.publish.window", "branch.publish", "branch.publish",
1407
            "first branch.publish start -> last branch.publish end across all branches",
1408
        ),
1409
        "race.integrate.window": lambda: window_row(
1410
            "race.integrate.window", "integration.open", "integration.main.settle",
1411
            "first integration.open start -> last integration.main.settle end across all branches",
1412
        ),
1413
        "race.pacing.injected": pacing_row,
1414
        "race.total": total_row_race,
1415
    }
1416
    rows: list[dict[str, Any]] = []
1417
    for operation in race_operations:
1418
        emitter = emitters.get(operation)
1419
        if emitter is None:
1420
            rows.append(
1421
                skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented")
1422
            )
1423
            continue
1424
        rows.append(emitter())
1425
    return rows
1426
1427
1428
# A branch factory does the branch's untimed setup and returns
1429
# (setup_error_or_None, operation handlers, untimed cleanup callable).
1430
RaceBranchFactory = Callable[
1431
    [int, str],
1432
    tuple[Optional[str], dict[str, Callable[[], dict[str, Any]]], Callable[[], None]],
1433
]
1434
1435
1436
def run_race_operations(
1437
    scenario: dict[str, Any],
1438
    meta: dict[str, Any],
1439
    run_index: int,
1440
    driver: str,
1441
    branch_count: int,
1442
    make_branch: RaceBranchFactory,
1443
    budgeter: RateLimitBudgeter,
1444
    clock: Callable[[], float],
1445
) -> list[dict[str, Any]]:
1446
    """Race orchestration shared by drivers: loop branch-0..N-1 serially,
1447
    run each branch's declared per-branch operations, record a start/end
1448
    timeline for the race windows, then emit the race-level rows. A failed
1449
    step turns every remaining per-branch step into a skip row (a recorded
1450
    gap, never a cascade of misleading failures)."""
1451
    scenario_name = str(scenario["name"])
1452
    operations = [str(op) for op in scenario.get("operations") or []]
1453
    branch_operations = [op for op in operations if not op.startswith("race.")]
1454
    race_operations = [op for op in operations if op.startswith("race.")]
1455
1456
    rows: list[dict[str, Any]] = []
1457
    timeline: dict[str, list[tuple[float, float]]] = {}
1458
    cleanups: list[Callable[[], None]] = []
1459
    aborted_at: Optional[str] = None
1460
    race_start = clock()
1461
1462
    for branch_index in range(branch_count):
1463
        branch = disposable_branch(meta["bench_id"], scenario_name, f"b{branch_index}", run_index)
1464
        if aborted_at is not None:
1465
            rows.extend(
1466
                race_branch_skip(
1467
                    meta, scenario_name, operation, run_index,
1468
                    f"prior_step_failed:{aborted_at}", branch_index,
1469
                )
1470
                for operation in branch_operations
1471
            )
1472
            continue
1473
        setup_error, handlers, cleanup = make_branch(branch_index, branch)
1474
        cleanups.append(cleanup)
1475
        if setup_error is not None:
1476
            aborted_at = f"branch-{branch_index}:setup"
1477
            rows.extend(
1478
                race_branch_skip(meta, scenario_name, operation, run_index, setup_error, branch_index)
1479
                for operation in branch_operations
1480
            )
1481
            continue
1482
        for operation in branch_operations:
1483
            if aborted_at is not None:
1484
                rows.append(
1485
                    race_branch_skip(
1486
                        meta, scenario_name, operation, run_index,
1487
                        f"prior_step_failed:{aborted_at}", branch_index,
1488
                    )
1489
                )
1490
                continue
1491
            handler = handlers.get(operation)
1492
            if handler is None:
1493
                rows.append(
1494
                    race_branch_skip(
1495
                        meta, scenario_name, operation, run_index,
1496
                        "operation_not_implemented", branch_index,
1497
                    )
1498
                )
1499
                continue
1500
            injected_before = budgeter.injected_wait_s_total
1501
            start = clock()
1502
            row = handler()
1503
            end = clock()
1504
            row.pop("_stdout", None)
1505
            row["race_branch"] = branch_index
1506
            row["race_branch_name"] = branch
1507
            injected_s = budgeter.injected_wait_s_total - injected_before
1508
            if injected_s > 0:
1509
                row["pacing_injected_ms"] = round(injected_s * 1000.0, 3)
1510
                row["pacing_note"] = (
1511
                    "elapsed_ms includes this budgeter-injected wait; it is "
1512
                    "reported here and summed into race.pacing.injected, never subtracted"
1513
                )
1514
            timeline.setdefault(operation, []).append((start, end))
1515
            rows.append(row)
1516
            if row_returncode(row) not in (0, SKIP_RETURNCODE):
1517
                aborted_at = f"branch-{branch_index}:{operation}"
1518
1519
    race_end = clock()
1520
    rows.extend(
1521
        race_summary_rows(
1522
            meta, scenario_name, run_index, driver, race_operations, list(rows),
1523
            timeline, budgeter, race_start, race_end, branch_count,
1524
        )
1525
    )
1526
    for cleanup in cleanups:
1527
        cleanup()  # untimed best-effort cleanup; cleanup failures are not data
1528
    return rows
1529
1530
1531
def run_github_api_race(
1532
    args: argparse.Namespace,
1533
    scenario: dict[str, Any],
1534
    meta: dict[str, Any],
1535
    run_index: int,
1536
    settings: Settings,
1537
    *,
1538
    client: Optional[GitHubClient] = None,
1539
    budgeter: Optional[RateLimitBudgeter] = None,
1540
    sleep: Callable[[float], None] = time.sleep,
1541
    clock: Callable[[], float] = time.monotonic,
1542
) -> list[dict[str, Any]]:
1543
    scenario_name = str(scenario["name"])
1544
    repo = args.repo
1545
    if client is None:
1546
        client = GitHubClient(token=os.environ.get(args.token_env))
1547
    if budgeter is None:
1548
        budgeter = RateLimitBudgeter(DEFAULT_RACE_OPS_PER_HOUR)
1549
    check_instant = args.protection_variant == "check-instant"
1550
1551
    def pace_content_op() -> None:
1552
        # Pace before every content (write) op; the budgeter accumulates the
1553
        # injected waits, which race.pacing.injected reports.
1554
        wait = budgeter.pace()
1555
        if wait > 0:
1556
            sleep(wait)
1557
1558
    def make_branch(branch_index: int, branch: str) -> tuple[
1559
        Optional[str], dict[str, Callable[[], dict[str, Any]]], Callable[[], None]
1560
    ]:
1561
        state: dict[str, Any] = {}
1562
        try:
1563
            # Serial landing: each branch baselines whatever main is now.
1564
            state["base_sha"] = client.get_main_sha(repo)
1565
        except (GitHubApiError, KeyError, json.JSONDecodeError) as exc:
1566
            return f"setup_failed:{str(exc)[:160]}", {}, lambda: None
1567
1568
        payload = (
1569
            f"platform race {meta['bench_id']} run={run_index} branch={branch_index}\n"
1570
        ).encode("utf-8")
1571
1572
        def publish() -> dict[str, Any]:
1573
            def do() -> None:
1574
                pace_content_op()
1575
                client.create_ref(repo, f"refs/heads/{branch}", state["base_sha"])
1576
                pace_content_op()
1577
                put = client.put_file(
1578
                    repo,
1579
                    path=f".oak-bench/race-{run_index}-b{branch_index}.md",
1580
                    message=f"bench race {meta['bench_id']} r{run_index} b{branch_index}",
1581
                    content=payload,
1582
                    branch=branch,
1583
                )
1584
                commit = put.get("commit") if isinstance(put, dict) else None
1585
                if isinstance(commit, dict) and commit.get("sha"):
1586
                    state["head_sha"] = str(commit["sha"])
1587
            return api_step_row(
1588
                meta, scenario_name, "branch.publish", run_index, client, do,
1589
                [f"api:POST /repos/{repo}/git/refs", f"api:PUT /repos/{repo}/contents/..."],
1590
            )
1591
1592
        def open_pr() -> dict[str, Any]:
1593
            def do() -> None:
1594
                pace_content_op()
1595
                pr = client.create_pr(
1596
                    repo,
1597
                    title=f"bench race {scenario_name} r{run_index} b{branch_index}",
1598
                    head=branch,
1599
                    base="main",
1600
                )
1601
                state["pr_number"] = int(pr["number"])
1602
                head = pr.get("head") or {}
1603
                if not state.get("head_sha") and head.get("sha"):
1604
                    state["head_sha"] = str(head["sha"])
1605
            return api_step_row(
1606
                meta, scenario_name, "integration.open", run_index, client, do,
1607
                [f"api:POST /repos/{repo}/pulls"],
1608
            )
1609
1610
        def get_pr_quiet() -> Optional[dict[str, Any]]:
1611
            try:
1612
                return client.get_pr(repo, state["pr_number"])
1613
            except GitHubApiError:
1614
                return None
1615
1616
        def visible() -> dict[str, Any]:
1617
            return api_settle_row(
1618
                meta, scenario_name, "integration.visible.settle", run_index, client,
1619
                lambda: get_pr_quiet() is not None, settings,
1620
                [f"api:GET /repos/{repo}/pulls/<n> (poll)"], clock=clock, sleep=sleep,
1621
            )
1622
1623
        def mergeable() -> dict[str, Any]:
1624
            def poll() -> bool:
1625
                pr = get_pr_quiet()
1626
                return pr is not None and pr.get("mergeable") is not None
1627
            return api_settle_row(
1628
                meta, scenario_name, "integration.mergeable.settle", run_index, client,
1629
                poll, settings, [f"api:GET /repos/{repo}/pulls/<n> mergeable (poll)"],
1630
                clock=clock, sleep=sleep,
1631
            )
1632
1633
        def checks_green() -> dict[str, Any]:
1634
            if not check_instant:
1635
                return structural_row(
1636
                    meta, scenario_name, CHECKS_GREEN_OPERATION, run_index,
1637
                    "protection-none: no required status checks; nothing to wait for",
1638
                )
1639
            return api_check_instant_checks_green_row(
1640
                meta, scenario_name, run_index, client, repo,
1641
                str(state.get("head_sha") or ""), settings,
1642
                pace=pace_content_op, clock=clock, sleep=sleep,
1643
            )
1644
1645
        def merge() -> dict[str, Any]:
1646
            def do() -> None:
1647
                pace_content_op()
1648
                client.merge_pr(repo, state["pr_number"])
1649
            return api_step_row(
1650
                meta, scenario_name, "integration.merge.request", run_index, client, do,
1651
                [f"api:PUT /repos/{repo}/pulls/<n>/merge"],
1652
            )
1653
1654
        def merged() -> dict[str, Any]:
1655
            def poll() -> bool:
1656
                pr = get_pr_quiet()
1657
                return bool(pr and pr.get("merged"))
1658
            return api_settle_row(
1659
                meta, scenario_name, "integration.merged.settle", run_index, client,
1660
                poll, settings, [f"api:GET /repos/{repo}/pulls/<n> merged (poll)"],
1661
                clock=clock, sleep=sleep,
1662
            )
1663
1664
        def main_settle() -> dict[str, Any]:
1665
            def poll() -> bool:
1666
                try:
1667
                    return client.get_main_sha(repo) != state["base_sha"]
1668
                except GitHubApiError:
1669
                    return False
1670
            return api_settle_row(
1671
                meta, scenario_name, "integration.main.settle", run_index, client,
1672
                poll, settings, [f"api:GET /repos/{repo}/git/ref/heads/main (poll)"],
1673
                clock=clock, sleep=sleep,
1674
            )
1675
1676
        def cleanup() -> None:
1677
            try:
1678
                client.delete_ref(repo, f"heads/{branch}")
1679
            except GitHubApiError:
1680
                pass  # untimed cleanup; merged-branch deletion failures are not data
1681
1682
        handlers = {
1683
            "branch.publish": publish,
1684
            "integration.open": open_pr,
1685
            "integration.visible.settle": visible,
1686
            "integration.mergeable.settle": mergeable,
1687
            "integration.checks.green.settle": checks_green,
1688
            "integration.merge.request": merge,
1689
            "integration.merged.settle": merged,
1690
            "integration.main.settle": main_settle,
1691
        }
1692
        return None, handlers, cleanup
1693
1694
    return run_race_operations(
1695
        scenario, meta, run_index, "harness-api", race_branch_count(scenario),
1696
        make_branch, budgeter, clock,
1697
    )
1698
1699
1700
def run_oak_cli_race(
1701
    args: argparse.Namespace,
1702
    scenario: dict[str, Any],
1703
    meta: dict[str, Any],
1704
    run_index: int,
1705
    run_root: Path,
1706
    settings: Settings,
1707
    *,
1708
    budgeter: Optional[RateLimitBudgeter] = None,
1709
    sleep: Callable[[float], None] = time.sleep,
1710
    clock: Callable[[], float] = time.monotonic,
1711
) -> list[dict[str, Any]]:
1712
    scenario_name = str(scenario["name"])
1713
    env = cli_env(args)
1714
    if budgeter is None:
1715
        budgeter = RateLimitBudgeter(DEFAULT_RACE_OPS_PER_HOUR)
1716
1717
    def pace_content_op() -> None:
1718
        wait = budgeter.pace()
1719
        if wait > 0:
1720
            sleep(wait)
1721
1722
    def make_branch(branch_index: int, branch: str) -> tuple[
1723
        Optional[str], dict[str, Callable[[], dict[str, Any]]], Callable[[], None]
1724
    ]:
1725
        workdir = run_root / f"oak-cli-race-r{run_index}-b{branch_index}"
1726
        mount = run_untimed(["oak", "mount", args.repo, str(workdir)], run_root, env)
1727
        if mount.returncode != 0:
1728
            return (
1729
                f"setup_failed:oak_mount:{mount.stderr.strip()[:120]}", {}, lambda: None
1730
            )
1731
1732
        def cleanup() -> None:
1733
            run_untimed(["oak", "mount", "end", str(workdir), "-f"], run_root, env)
1734
1735
        payload_dir = workdir / ".oak-bench"
1736
        payload_dir.mkdir(parents=True, exist_ok=True)
1737
        (payload_dir / f"race-{run_index}-b{branch_index}.md").write_text(
1738
            f"platform race {meta['bench_id']} run={run_index} branch={branch_index}\n"
1739
        )
1740
        commit = run_untimed(["oak", "commit", "--no-verify"], workdir, env)
1741
        if commit.returncode != 0:
1742
            return f"setup_failed:oak_commit:{commit.stderr.strip()[:120]}", {}, cleanup
1743
1744
        def paced_cli(operation: str, command: list[str]) -> Callable[[], dict[str, Any]]:
1745
            def handler() -> dict[str, Any]:
1746
                pace_content_op()
1747
                return cli_step_row(
1748
                    meta, scenario_name, operation, command, workdir, run_index, env,
1749
                    settings.admitted,
1750
                )
1751
            return handler
1752
1753
        def structural(operation: str, note: str) -> Callable[[], dict[str, Any]]:
1754
            return lambda: structural_row(meta, scenario_name, operation, run_index, note)
1755
1756
        handlers = {
1757
            "branch.publish": paced_cli("branch.publish", ["oak", "push"]),
1758
            "integration.open": structural(
1759
                "integration.open",
1760
                "integration object exists at publish on oak; no separate open call",
1761
            ),
1762
            "integration.visible.settle": structural(
1763
                "integration.visible.settle",
1764
                "oak push returns after the branch is visible on the server; nothing to poll",
1765
            ),
1766
            "integration.mergeable.settle": structural(
1767
                "integration.mergeable.settle", "no async mergeability concept"
1768
            ),
1769
            "integration.checks.green.settle": structural(
1770
                "integration.checks.green.settle",
1771
                "no hosted status-check API on oak; check-instant is a GitHub-side "
1772
                "harness construct with no equivalent operation here (ADR-0002)",
1773
            ),
1774
            "integration.merge.request": paced_cli(
1775
                "integration.merge.request", ["oak", "merge"]
1776
            ),
1777
            "integration.merged.settle": structural(
1778
                "integration.merged.settle",
1779
                "oak merge is synchronous; merged state holds at return",
1780
            ),
1781
            "integration.main.settle": structural(
1782
                "integration.main.settle",
1783
                "oak merge returns after main is updated; nothing to poll",
1784
            ),
1785
        }
1786
        return None, handlers, cleanup
1787
1788
    return run_race_operations(
1789
        scenario, meta, run_index, "cli", race_branch_count(scenario),
1790
        make_branch, budgeter, clock,
1791
    )
1792
1793
1794
def run_race_with_credentials(
1795
    args: argparse.Namespace,
1796
    scenario: dict[str, Any],
1797
    meta: dict[str, Any],
1798
    run_index: int,
1799
    run_root: Path,
1800
    settings: Settings,
1801
) -> list[dict[str, Any]]:
1802
    if args.platform == "github" and args.driver == "harness-api":
1803
        return run_github_api_race(args, scenario, meta, run_index, settings)
1804
    if args.platform == "github":
1805
        # gh-cli race orchestration is deferred to Phase 4 (see
1806
        # RACE_CLI_DEFER_REASON): per-op skip rows, a recorded gap, never
1807
        # silently different semantics.
1808
        scenario_name = str(scenario["name"])
1809
        return [
1810
            skip_row(meta, scenario_name, str(operation), run_index, RACE_CLI_DEFER_REASON)
1811
            for operation in scenario.get("operations") or []
1812
        ]
1813
    return run_oak_cli_race(args, scenario, meta, run_index, run_root, settings)
1814
1815
1816
# --------------------------------------------------------------------------
1817
# branch_fleet_nN: end-to-end branch fleet triage and landing.
1818
#
1819
# N open branches are seeded; roughly 30% are constructed as conflicts and the
1820
# rest are clean. The timed workflow is: classify -> plan -> merge clean
1821
# branches -> leave conflicts open -> sync/observe final main -> oracle. This
1822
# is the agent-scale workflow Git/GitHub makes agents perform with many round
1823
# trips, while Oak should collapse classification into one checkout-free batch.
1824
# --------------------------------------------------------------------------
1825
1826
1827
def is_branch_fleet_scenario(scenario: dict[str, Any]) -> bool:
1828
    return BRANCH_FLEET_SCENARIO_RE.match(str(scenario.get("name", ""))) is not None
1829
1830
1831
def branch_fleet_count(scenario: dict[str, Any]) -> int:
1832
    if scenario.get("branch_count") is not None:
1833
        return int(scenario["branch_count"])
1834
    match = BRANCH_FLEET_SCENARIO_RE.match(str(scenario.get("name", "")))
1835
    return int(match.group(1)) if match else 10
1836
1837
1838
def branch_fleet_live_skip_reason(
1839
    scenario: dict[str, Any],
1840
    driver: str,
1841
    environ: Optional[dict[str, str]] = None,
1842
) -> Optional[str]:
1843
    env = os.environ if environ is None else environ
1844
    if driver == BRANCH_TRIAGE_FAKE_DRIVER:
1845
        return None
1846
    if branch_fleet_count(scenario) <= BRANCH_FLEET_LIVE_LARGE_LIMIT:
1847
        return None
1848
    if str(env.get(BRANCH_FLEET_LIVE_LARGE_ENV) or "").strip() == "1":
1849
        return None
1850
    return (
1851
        f"branch_fleet_large_live_requires_opt_in:"
1852
        f"{BRANCH_FLEET_LIVE_LARGE_ENV}=1"
1853
    )
1854
1855
1856
def branch_fleet_worker_count(
1857
    branch_count: int,
1858
    *,
1859
    env_name: str,
1860
    default: int,
1861
    environ: Optional[dict[str, str]] = None,
1862
) -> int:
1863
    env = os.environ if environ is None else environ
1864
    raw = str(env.get(env_name) or "").strip()
1865
    if raw:
1866
        try:
1867
            configured = int(raw)
1868
        except ValueError:
1869
            configured = default
1870
    else:
1871
        configured = default
1872
    bounded = max(1, min(configured, BRANCH_FLEET_WORKERS_MAX))
1873
    return max(1, min(bounded, max(1, branch_count)))
1874
1875
1876
def branch_fleet_seed_worker_count(
1877
    branch_count: int, environ: Optional[dict[str, str]] = None
1878
) -> int:
1879
    return branch_fleet_worker_count(
1880
        branch_count,
1881
        env_name=BRANCH_FLEET_SEED_WORKERS_ENV,
1882
        default=BRANCH_FLEET_SEED_WORKERS_DEFAULT,
1883
        environ=environ,
1884
    )
1885
1886
1887
def branch_fleet_cleanup_worker_count(
1888
    branch_count: int, environ: Optional[dict[str, str]] = None
1889
) -> int:
1890
    return branch_fleet_worker_count(
1891
        branch_count,
1892
        env_name=BRANCH_FLEET_CLEANUP_WORKERS_ENV,
1893
        default=BRANCH_FLEET_CLEANUP_WORKERS_DEFAULT,
1894
        environ=environ,
1895
    )
1896
1897
1898
def branch_fleet_conflict_count(branch_count: int) -> int:
1899
    # The launch/demo shape is "10 branches, 3 conflicts, 7 clean"; scale that
1900
    # ratio for larger fleets while keeping at least one conflict when N > 1.
1901
    if branch_count <= 1:
1902
        return 0
1903
    return max(1, round(branch_count * 0.3))
1904
1905
1906
def branch_fleet_specs(
1907
    *, bench_id: str, scenario_name: str, run_index: int, branch_count: int
1908
) -> list[dict[str, Any]]:
1909
    conflict_count = branch_fleet_conflict_count(branch_count)
1910
    clean_count = branch_count - conflict_count
1911
    prefix = disposable_branch(bench_id, scenario_name, "fleet", run_index)
1912
    specs: list[dict[str, Any]] = []
1913
    for index in range(branch_count):
1914
        kind = "conflicting" if index >= clean_count else "clean"
1915
        specs.append(
1916
            {
1917
                "index": index,
1918
                "name": f"{prefix}-{kind[:4]}-{index:04d}",
1919
                "kind": kind,
1920
                "expected_action": "leave_conflict_open" if kind == "conflicting" else "merge",
1921
                "path": (
1922
                    f".oak-bench/fleet/conflict-{index:04d}.txt"
1923
                    if kind == "conflicting"
1924
                    else f".oak-bench/fleet/clean-{index:04d}.txt"
1925
                ),
1926
            }
1927
        )
1928
    return specs
1929
1930
1931
def branch_fleet_plan(reviewed: list[dict[str, Any]]) -> dict[str, Any]:
1932
    actions: list[dict[str, str]] = []
1933
    merge_order: list[str] = []
1934
    clean_detected = 0
1935
    conflict_detected = 0
1936
    unknown_detected = 0
1937
    for branch in reviewed:
1938
        name = str(branch["name"])
1939
        mergeable = branch.get("mergeable")
1940
        if mergeable is True:
1941
            classification = "clean"
1942
            action = "merge"
1943
            clean_detected += 1
1944
            merge_order.append(name)
1945
        elif mergeable is False:
1946
            classification = "conflicting"
1947
            action = "leave_conflict_open"
1948
            conflict_detected += 1
1949
        else:
1950
            classification = "unknown"
1951
            action = "review"
1952
            unknown_detected += 1
1953
        actions.append({"branch": name, "classification": classification, "action": action})
1954
    return {
1955
        "actions": actions,
1956
        "merge_order": merge_order,
1957
        "detected_counts": {
1958
            "clean": clean_detected,
1959
            "conflicting": conflict_detected,
1960
            "unknown": unknown_detected,
1961
        },
1962
    }
1963
1964
1965
def branch_fleet_oracle(
1966
    specs: list[dict[str, Any]],
1967
    plan: dict[str, Any],
1968
    apply_result: dict[str, Any],
1969
) -> dict[str, Any]:
1970
    expected_actions = [
1971
        {
1972
            "branch": str(spec["name"]),
1973
            "classification": str(spec["kind"]),
1974
            "action": str(spec["expected_action"]),
1975
        }
1976
        for spec in specs
1977
    ]
1978
    expected_merge_order = [
1979
        str(spec["name"]) for spec in specs if spec["expected_action"] == "merge"
1980
    ]
1981
    expected_survivors = [
1982
        str(spec["name"])
1983
        for spec in specs
1984
        if spec["expected_action"] == "leave_conflict_open"
1985
    ]
1986
    apply_succeeded = apply_result.get("apply_succeeded") is not False
1987
    final_survivors = sorted(str(name) for name in apply_result.get("final_survivors") or [])
1988
    return {
1989
        "apply_succeeded": apply_succeeded,
1990
        "action_plan_correct": plan.get("actions") == expected_actions,
1991
        "merge_order_correct": plan.get("merge_order") == expected_merge_order,
1992
        "final_survivors_correct": apply_succeeded and final_survivors == sorted(expected_survivors),
1993
        "expected_action_plan": expected_actions,
1994
        "expected_merge_order": expected_merge_order,
1995
        "expected_final_survivors": sorted(expected_survivors),
1996
        "final_survivors": final_survivors,
1997
    }
1998
1999
2000
def branch_fleet_survivors_from_sync_stdout(
2001
    stdout: str,
2002
    specs: list[dict[str, Any]],
2003
) -> list[str]:
2004
    payload = json.loads(stdout)
2005
    if isinstance(payload, list):
2006
        rows = payload
2007
    elif isinstance(payload, dict):
2008
        if "branches" in payload:
2009
            rows = payload["branches"]
2010
        elif "rows" in payload:
2011
            rows = payload["rows"]
2012
        else:
2013
            raise ValueError("branch_list_missing_rows")
2014
        if not isinstance(rows, list):
2015
            raise ValueError("branch_list_rows_not_list")
2016
    else:
2017
        raise ValueError("branch_list_not_array_or_object")
2018
    expected_names = {str(spec["name"]) for spec in specs}
2019
    names = set()
2020
    for item in rows:
2021
        if not isinstance(item, dict):
2022
            raise ValueError("branch_list_row_not_object")
2023
        name = item.get("name") or item.get("branch")
2024
        if not name:
2025
            raise ValueError("branch_list_row_missing_name")
2026
        names.add(str(name))
2027
    return sorted(name for name in names if name in expected_names)
2028
2029
2030
BRANCH_FLEET_WORKFLOW_OPERATIONS = (
2031
    "fleet.classify",
2032
    "fleet.plan",
2033
    "fleet.apply",
2034
    "fleet.sync",
2035
    "fleet.oracle",
2036
)
2037
2038
2039
def branch_fleet_workflow_summary_fields(rows: list[dict[str, Any]]) -> dict[str, Any]:
2040
    setup_rows = [row for row in rows if row.get("operation") == "fleet.seed"]
2041
    workflow_rows = [
2042
        row for row in rows
2043
        if row.get("operation") in BRANCH_FLEET_WORKFLOW_OPERATIONS
2044
    ]
2045
    workflow_failures = [
2046
        row for row in workflow_rows
2047
        if row_returncode(row) not in (0, SKIP_RETURNCODE)
2048
    ]
2049
    workflow_skips = [
2050
        row for row in workflow_rows
2051
        if row_returncode(row) == SKIP_RETURNCODE
2052
    ]
2053
    workflow_returncode: Optional[int]
2054
    if not workflow_rows:
2055
        workflow_returncode = None
2056
    elif workflow_failures:
2057
        workflow_returncode = 1
2058
    elif len(workflow_skips) == len(workflow_rows):
2059
        workflow_returncode = SKIP_RETURNCODE
2060
    else:
2061
        workflow_returncode = 0
2062
    tool_counts = [
2063
        int(row["tool_call_count"])
2064
        for row in workflow_rows
2065
        if row.get("tool_call_count") is not None
2066
    ]
2067
    return {
2068
        "branch_fleet_workflow_operations": [row["operation"] for row in workflow_rows],
2069
        "branch_fleet_setup_operations": [row["operation"] for row in setup_rows],
2070
        "branch_fleet_setup_excluded_from_workflow": True,
2071
        "branch_fleet_workflow_elapsed_ms": round(
2072
            sum(float(row.get("elapsed_ms") or 0.0) for row in workflow_rows),
2073
            3,
2074
        ),
2075
        "branch_fleet_setup_elapsed_ms": round(
2076
            sum(float(row.get("elapsed_ms") or 0.0) for row in setup_rows),
2077
            3,
2078
        ),
2079
        "branch_fleet_workflow_failure_count": len(workflow_failures),
2080
        "branch_fleet_workflow_skip_count": len(workflow_skips),
2081
        "branch_fleet_workflow_returncode": workflow_returncode,
2082
        "branch_fleet_workflow_tool_call_count": sum(tool_counts) if tool_counts else None,
2083
    }
2084
2085
2086
def branch_fleet_metric_fields(
2087
    *,
2088
    branch_count: int,
2089
    reviewed: Optional[list[dict[str, Any]]] = None,
2090
    plan: Optional[dict[str, Any]] = None,
2091
    apply_result: Optional[dict[str, Any]] = None,
2092
    oracle: Optional[dict[str, Any]] = None,
2093
) -> dict[str, Any]:
2094
    reviewed = reviewed or []
2095
    counts = ((plan or {}).get("detected_counts") or {})
2096
    if not counts and reviewed:
2097
        counts = {"clean": 0, "conflicting": 0, "unknown": 0}
2098
        for branch in reviewed:
2099
            if branch.get("mergeable") is True:
2100
                counts["clean"] += 1
2101
            elif branch.get("mergeable") is False:
2102
                counts["conflicting"] += 1
2103
            else:
2104
                counts["unknown"] += 1
2105
    merged = list((apply_result or {}).get("merged_branches") or [])
2106
    survivors = list((apply_result or {}).get("final_survivors") or [])
2107
    return {
2108
        "branch_count": branch_count,
2109
        "branches_seeded": branch_count,
2110
        "branches_reviewed": len(reviewed),
2111
        "branches_merged": len(merged),
2112
        "branches_left_open": len(survivors),
2113
        "branches_closed": len(merged),
2114
        "clean_detected_count": int(counts.get("clean", 0)),
2115
        "conflict_detected_count": int(counts.get("conflicting", 0)),
2116
        "unknown_detected_count": int(counts.get("unknown", 0)),
2117
        "merge_order_correct": (oracle or {}).get("merge_order_correct"),
2118
    }
2119
2120
2121
def branch_fleet_row(
2122
    meta: dict[str, Any],
2123
    scenario_name: str,
2124
    operation: str,
2125
    run_index: int,
2126
    elapsed_ms: float,
2127
    returncode: int = 0,
2128
    *,
2129
    provider: str,
2130
    command: Optional[list[Any]] = None,
2131
    **fields: Any,
2132
) -> dict[str, Any]:
2133
    row = {
2134
        **meta,
2135
        "scenario": scenario_name,
2136
        "run": run_index,
2137
        "operation": operation,
2138
        "elapsed_ms": round(elapsed_ms, 3),
2139
        "returncode": returncode,
2140
        "command": command or [],
2141
        "branch_fleet_provider": provider,
2142
        **fields,
2143
    }
2144
    if provider in (BRANCH_FLEET_PROVIDER_GITHUB_API, BRANCH_FLEET_PROVIDER_FAKE):
2145
        row.update(null_token_fields() if provider == BRANCH_FLEET_PROVIDER_GITHUB_API else fake_provider_token_fields())
2146
        row.setdefault("tool_call_count", None if provider == BRANCH_FLEET_PROVIDER_GITHUB_API else 0)
2147
    return row
2148
2149
2150
def run_branch_fleet_fake_scenario(
2151
    scenario: dict[str, Any],
2152
    meta: dict[str, Any],
2153
    run_index: int,
2154
) -> list[dict[str, Any]]:
2155
    scenario_name = str(scenario["name"])
2156
    operations = [str(op) for op in scenario.get("operations") or []]
2157
    count = branch_fleet_count(scenario)
2158
    specs = branch_fleet_specs(
2159
        bench_id=str(meta.get("bench_id") or "bench"),
2160
        scenario_name=scenario_name,
2161
        run_index=run_index,
2162
        branch_count=count,
2163
    )
2164
    rows: list[dict[str, Any]] = []
2165
    reviewed: list[dict[str, Any]] = []
2166
    plan: dict[str, Any] = {}
2167
    apply_result: dict[str, Any] = {
2168
        "apply_succeeded": False,
2169
        "merged_branches": [],
2170
        "final_survivors": [],
2171
    }
2172
    oracle: dict[str, Any] = {}
2173
    total_start = platform_clock.monotonic_ms()
2174
2175
    for operation in operations:
2176
        start = platform_clock.monotonic_ms()
2177
        if operation == "fleet.seed":
2178
            rows.append(
2179
                branch_fleet_row(
2180
                    meta, scenario_name, operation, run_index,
2181
                    platform_clock.monotonic_ms() - start,
2182
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2183
                    seeded_branch_classes=[spec["kind"] for spec in specs],
2184
                    **branch_fleet_metric_fields(branch_count=count),
2185
                )
2186
            )
2187
        elif operation == "fleet.classify":
2188
            reviewed = [
2189
                {"name": spec["name"], "mergeable": spec["kind"] == "clean"}
2190
                for spec in specs
2191
            ]
2192
            rows.append(
2193
                branch_fleet_row(
2194
                    meta, scenario_name, operation, run_index,
2195
                    platform_clock.monotonic_ms() - start,
2196
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2197
                    reviewed_branches=[row["name"] for row in reviewed],
2198
                    **branch_fleet_metric_fields(branch_count=count, reviewed=reviewed),
2199
                )
2200
            )
2201
        elif operation == "fleet.plan":
2202
            plan = branch_fleet_plan(reviewed)
2203
            rows.append(
2204
                branch_fleet_row(
2205
                    meta, scenario_name, operation, run_index,
2206
                    platform_clock.monotonic_ms() - start,
2207
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2208
                    action_plan=plan["actions"],
2209
                    merge_order=plan["merge_order"],
2210
                    **branch_fleet_metric_fields(branch_count=count, reviewed=reviewed, plan=plan),
2211
                )
2212
            )
2213
        elif operation == "fleet.apply":
2214
            apply_result = {
2215
                "apply_succeeded": True,
2216
                "merged_branches": list(plan.get("merge_order") or []),
2217
                "final_survivors": [
2218
                    action["branch"]
2219
                    for action in plan.get("actions") or []
2220
                    if action["action"] == "leave_conflict_open"
2221
                ],
2222
            }
2223
            rows.append(
2224
                branch_fleet_row(
2225
                    meta, scenario_name, operation, run_index,
2226
                    platform_clock.monotonic_ms() - start,
2227
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2228
                    **apply_result,
2229
                    **branch_fleet_metric_fields(
2230
                        branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
2231
                    ),
2232
                )
2233
            )
2234
        elif operation == "fleet.sync":
2235
            rows.append(
2236
                branch_fleet_row(
2237
                    meta, scenario_name, operation, run_index,
2238
                    platform_clock.monotonic_ms() - start,
2239
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2240
                    note="fake provider has no local checkout; final state already materialized in memory",
2241
                    **branch_fleet_metric_fields(
2242
                        branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
2243
                    ),
2244
                )
2245
            )
2246
        elif operation == "fleet.oracle":
2247
            oracle = branch_fleet_oracle(specs, plan, apply_result)
2248
            ok = all(
2249
                bool(oracle[key])
2250
                for key in (
2251
                    "apply_succeeded",
2252
                    "action_plan_correct",
2253
                    "merge_order_correct",
2254
                    "final_survivors_correct",
2255
                )
2256
            )
2257
            fields = {
2258
                **oracle,
2259
                **branch_fleet_metric_fields(
2260
                    branch_count=count,
2261
                    reviewed=reviewed,
2262
                    plan=plan,
2263
                    apply_result=apply_result,
2264
                    oracle=oracle,
2265
                ),
2266
            }
2267
            rows.append(
2268
                branch_fleet_row(
2269
                    meta, scenario_name, operation, run_index,
2270
                    platform_clock.monotonic_ms() - start,
2271
                    returncode=0 if ok else 1,
2272
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2273
                    **fields,
2274
                )
2275
            )
2276
        elif operation == "fleet.total":
2277
            failures = [
2278
                row for row in rows
2279
                if row_returncode(row) not in (0, SKIP_RETURNCODE)
2280
            ]
2281
            rows.append(
2282
                branch_fleet_row(
2283
                    meta, scenario_name, operation, run_index,
2284
                    platform_clock.monotonic_ms() - total_start,
2285
                    returncode=1 if failures else 0,
2286
                    provider=BRANCH_FLEET_PROVIDER_FAKE,
2287
                    summarized_operations=[row["operation"] for row in rows],
2288
                    **branch_fleet_workflow_summary_fields(rows),
2289
                    **branch_fleet_metric_fields(
2290
                        branch_count=count,
2291
                        reviewed=reviewed,
2292
                        plan=plan,
2293
                        apply_result=apply_result,
2294
                        oracle=oracle,
2295
                    ),
2296
                )
2297
            )
2298
        else:
2299
            rows.append(skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented"))
2300
    return rows
2301
2302
2303
def run_github_api_branch_fleet_scenario(
2304
    args: argparse.Namespace,
2305
    scenario: dict[str, Any],
2306
    meta: dict[str, Any],
2307
    run_index: int,
2308
    settings: Settings,
2309
    *,
2310
    client: Optional[GitHubClient] = None,
2311
    clock: Callable[[], float] = time.monotonic,
2312
    sleep: Callable[[float], None] = time.sleep,
2313
) -> list[dict[str, Any]]:
2314
    scenario_name = str(scenario["name"])
2315
    operations = [str(op) for op in scenario.get("operations") or []]
2316
    count = branch_fleet_count(scenario)
2317
    specs = branch_fleet_specs(
2318
        bench_id=str(meta.get("bench_id") or "bench"),
2319
        scenario_name=scenario_name,
2320
        run_index=run_index,
2321
        branch_count=count,
2322
    )
2323
    repo = args.repo
2324
    if client is None:
2325
        client = GitHubClient(token=os.environ.get(args.token_env))
2326
2327
    state: dict[str, Any] = {"prs": {}, "heads": {}}
2328
    reviewed: list[dict[str, Any]] = []
2329
    plan: dict[str, Any] = {}
2330
    apply_result: dict[str, Any] = {
2331
        "apply_succeeded": False,
2332
        "merged_branches": [],
2333
        "final_survivors": [],
2334
    }
2335
    oracle: dict[str, Any] = {}
2336
    total_start = platform_clock.monotonic_ms()
2337
2338
    def seed() -> dict[str, Any]:
2339
        def do() -> None:
2340
            base_sha = client.get_main_sha(repo)
2341
            state["base_sha"] = base_sha
2342
            for spec in specs:
2343
                branch = str(spec["name"])
2344
                path = str(spec["path"])
2345
                client.create_ref(repo, f"refs/heads/{branch}", base_sha)
2346
                put = client.put_file(
2347
                    repo,
2348
                    path=path,
2349
                    message=f"bench branch fleet {meta['bench_id']} {branch}",
2350
                    content=(
2351
                        f"branch fleet {meta['bench_id']} run={run_index} "
2352
                        f"branch={branch} kind={spec['kind']}\n"
2353
                    ).encode("utf-8"),
2354
                    branch=branch,
2355
                )
2356
                commit = put.get("commit") if isinstance(put, dict) else None
2357
                if isinstance(commit, dict) and commit.get("sha"):
2358
                    state["heads"][branch] = str(commit["sha"])
2359
                pr = client.create_pr(
2360
                    repo,
2361
                    title=f"bench branch fleet {scenario_name} {branch}",
2362
                    head=branch,
2363
                    base="main",
2364
                    body="Disposable branch-fleet benchmark PR.",
2365
                )
2366
                state["prs"][branch] = int(pr["number"])
2367
            # Make conflict branches genuinely conflict with main: each conflict
2368
            # branch added the same path from the old base; now main adds that
2369
            # path with different content, producing add/add conflicts.
2370
            for spec in specs:
2371
                if spec["kind"] != "conflicting":
2372
                    continue
2373
                client.put_file(
2374
                    repo,
2375
                    path=str(spec["path"]),
2376
                    message=f"bench branch fleet conflicting main side {meta['bench_id']}",
2377
                    content=(
2378
                        f"main-side conflict {meta['bench_id']} run={run_index} "
2379
                        f"branch={spec['name']}\n"
2380
                    ).encode("utf-8"),
2381
                    branch="main",
2382
                )
2383
2384
        row = api_step_row(
2385
            meta,
2386
            scenario_name,
2387
            "fleet.seed",
2388
            run_index,
2389
            client,
2390
            do,
2391
            [
2392
                f"api:GET /repos/{repo}/git/ref/heads/main",
2393
                f"api:POST /repos/{repo}/git/refs x{count}",
2394
                f"api:PUT /repos/{repo}/contents/... x{count + branch_fleet_conflict_count(count)}",
2395
                f"api:POST /repos/{repo}/pulls x{count}",
2396
            ],
2397
        )
2398
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_GITHUB_API
2399
        row.update(branch_fleet_metric_fields(branch_count=count))
2400
        row["seeded_branch_classes"] = [spec["kind"] for spec in specs]
2401
        return row
2402
2403
    def classify() -> dict[str, Any]:
2404
        nonlocal reviewed
2405
        before = metrics_snapshot(client)
2406
        start = platform_clock.monotonic_ms()
2407
        polls = 0
2408
        settled = False
2409
        last_reviewed: list[dict[str, Any]] = []
2410
        error_text: Optional[str] = None
2411
        deadline = clock() + settings.settle_timeout_s
2412
        while True:
2413
            polls += 1
2414
            try:
2415
                current: list[dict[str, Any]] = []
2416
                all_known = True
2417
                for spec in specs:
2418
                    branch = str(spec["name"])
2419
                    pr = client.get_pr(repo, int(state["prs"][branch]))
2420
                    mergeable = pr.get("mergeable")
2421
                    if mergeable is None:
2422
                        all_known = False
2423
                    current.append(
2424
                        {
2425
                            "name": branch,
2426
                            "mergeable": mergeable,
2427
                            "pr_number": int(state["prs"][branch]),
2428
                            "expected_kind": spec["kind"],
2429
                        }
2430
                    )
2431
                last_reviewed = current
2432
                if all_known:
2433
                    settled = True
2434
                    break
2435
            except (GitHubApiError, KeyError, json.JSONDecodeError) as exc:
2436
                error_text = str(exc)[:4000]
2437
                break
2438
            if clock() >= deadline:
2439
                break
2440
            sleep(settings.interval_s)
2441
        reviewed = last_reviewed
2442
        elapsed_ms = platform_clock.monotonic_ms() - start
2443
        row = branch_fleet_row(
2444
            meta,
2445
            scenario_name,
2446
            "fleet.classify",
2447
            run_index,
2448
            elapsed_ms,
2449
            returncode=0 if settled and error_text is None else 1,
2450
            provider=BRANCH_FLEET_PROVIDER_GITHUB_API,
2451
            command=[f"api:GET /repos/{repo}/pulls/<n> mergeable x{count} (poll)"],
2452
            reviewed_branches=[branch["name"] for branch in reviewed],
2453
            settled=settled,
2454
            settle_elapsed_ms=round(elapsed_ms, 3) if settled else None,
2455
            observed_wait_ms=round(elapsed_ms, 3),
2456
            poll_iterations=polls,
2457
            poll_interval_s=settings.interval_s,
2458
            poll_quantization_ms=settings.interval_s * 1000.0,
2459
            api_metrics=metrics_delta(before, metrics_snapshot(client)),
2460
            **branch_fleet_metric_fields(branch_count=count, reviewed=reviewed),
2461
        )
2462
        if error_text is not None:
2463
            row["stderr"] = error_text
2464
        return row
2465
2466
    def plan_row() -> dict[str, Any]:
2467
        nonlocal plan
2468
        start = platform_clock.monotonic_ms()
2469
        plan = branch_fleet_plan(reviewed)
2470
        return branch_fleet_row(
2471
            meta,
2472
            scenario_name,
2473
            "fleet.plan",
2474
            run_index,
2475
            platform_clock.monotonic_ms() - start,
2476
            provider=BRANCH_FLEET_PROVIDER_GITHUB_API,
2477
            action_plan=plan["actions"],
2478
            merge_order=plan["merge_order"],
2479
            **branch_fleet_metric_fields(branch_count=count, reviewed=reviewed, plan=plan),
2480
        )
2481
2482
    def apply() -> dict[str, Any]:
2483
        nonlocal apply_result
2484
2485
        def do() -> None:
2486
            merged: list[str] = []
2487
            survivors: list[str] = []
2488
            for action in plan.get("actions") or []:
2489
                branch = str(action["branch"])
2490
                if action["action"] == "merge":
2491
                    client.merge_pr(repo, int(state["prs"][branch]))
2492
                    merged.append(branch)
2493
                elif action["action"] == "leave_conflict_open":
2494
                    survivors.append(branch)
2495
            apply_result = {
2496
                "apply_succeeded": True,
2497
                "merged_branches": merged,
2498
                "final_survivors": survivors,
2499
            }
2500
            state["apply_result"] = apply_result
2501
2502
        row = api_step_row(
2503
            meta,
2504
            scenario_name,
2505
            "fleet.apply",
2506
            run_index,
2507
            client,
2508
            do,
2509
            [f"api:PUT /repos/{repo}/pulls/<n>/merge x{len(plan.get('merge_order') or [])}"],
2510
        )
2511
        # api_step_row cannot update this local binding through nested scope on
2512
        # older Python without nonlocal assignment inside do(); read the state
2513
        # value populated above.
2514
        apply_result = dict(
2515
            state.get("apply_result")
2516
            or {"apply_succeeded": False, "merged_branches": [], "final_survivors": []}
2517
        )
2518
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_GITHUB_API
2519
        row.update(apply_result)
2520
        row.update(
2521
            branch_fleet_metric_fields(
2522
                branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
2523
            )
2524
        )
2525
        return row
2526
2527
    def sync() -> dict[str, Any]:
2528
        row = api_step_row(
2529
            meta,
2530
            scenario_name,
2531
            "fleet.sync",
2532
            run_index,
2533
            client,
2534
            lambda: client.get_main_sha(repo),
2535
            [f"api:GET /repos/{repo}/git/ref/heads/main"],
2536
        )
2537
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_GITHUB_API
2538
        row["note"] = (
2539
            "harness-api driver has no local checkout; sync observes the final main ref."
2540
        )
2541
        row.update(
2542
            branch_fleet_metric_fields(
2543
                branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
2544
            )
2545
        )
2546
        return row
2547
2548
    def oracle_row() -> dict[str, Any]:
2549
        nonlocal oracle
2550
        start = platform_clock.monotonic_ms()
2551
        oracle = branch_fleet_oracle(specs, plan, apply_result)
2552
        ok = all(
2553
            bool(oracle[key])
2554
            for key in (
2555
                "apply_succeeded",
2556
                "action_plan_correct",
2557
                "merge_order_correct",
2558
                "final_survivors_correct",
2559
            )
2560
        )
2561
        fields = {
2562
            **oracle,
2563
            **branch_fleet_metric_fields(
2564
                branch_count=count,
2565
                reviewed=reviewed,
2566
                plan=plan,
2567
                apply_result=apply_result,
2568
                oracle=oracle,
2569
            ),
2570
        }
2571
        return branch_fleet_row(
2572
            meta,
2573
            scenario_name,
2574
            "fleet.oracle",
2575
            run_index,
2576
            platform_clock.monotonic_ms() - start,
2577
            returncode=0 if ok else 1,
2578
            provider=BRANCH_FLEET_PROVIDER_GITHUB_API,
2579
            **fields,
2580
        )
2581
2582
    def total() -> dict[str, Any]:
2583
        failures = [
2584
            row for row in rows
2585
            if row_returncode(row) not in (0, SKIP_RETURNCODE)
2586
        ]
2587
        row = branch_fleet_row(
2588
            meta,
2589
            scenario_name,
2590
            "fleet.total",
2591
            run_index,
2592
            platform_clock.monotonic_ms() - total_start,
2593
            returncode=1 if failures else 0,
2594
            provider=BRANCH_FLEET_PROVIDER_GITHUB_API,
2595
            summarized_operations=[row["operation"] for row in rows],
2596
            **branch_fleet_workflow_summary_fields(rows),
2597
            **branch_fleet_metric_fields(
2598
                branch_count=count,
2599
                reviewed=reviewed,
2600
                plan=plan,
2601
                apply_result=apply_result,
2602
                oracle=oracle,
2603
            ),
2604
        )
2605
        deltas = [
2606
            r["api_metrics"] for r in rows if isinstance(r.get("api_metrics"), dict)
2607
        ]
2608
        row["api_metrics"] = {
2609
            "request_count": sum(delta["request_count"] for delta in deltas),
2610
            "request_bytes": sum(delta["request_bytes"] for delta in deltas),
2611
            "response_bytes": sum(delta["response_bytes"] for delta in deltas),
2612
            "rate_limit_remaining": deltas[-1]["rate_limit_remaining"] if deltas else None,
2613
        }
2614
        return row
2615
2616
    handlers: dict[str, Callable[[], dict[str, Any]]] = {
2617
        "fleet.seed": seed,
2618
        "fleet.classify": classify,
2619
        "fleet.plan": plan_row,
2620
        "fleet.apply": apply,
2621
        "fleet.sync": sync,
2622
        "fleet.oracle": oracle_row,
2623
        "fleet.total": total,
2624
    }
2625
2626
    rows: list[dict[str, Any]] = []
2627
    aborted_at: Optional[str] = None
2628
    for operation in operations:
2629
        if aborted_at is not None:
2630
            rows.append(skip_row(meta, scenario_name, operation, run_index, f"prior_step_failed:{aborted_at}"))
2631
            continue
2632
        handler = handlers.get(operation)
2633
        if handler is None:
2634
            rows.append(skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented"))
2635
            continue
2636
        row = handler()
2637
        rows.append(row)
2638
        if row_returncode(row) not in (0, SKIP_RETURNCODE):
2639
            aborted_at = operation
2640
2641
    for spec in specs:
2642
        try:
2643
            client.delete_ref(repo, f"heads/{spec['name']}")
2644
        except GitHubApiError:
2645
            pass
2646
    return rows
2647
2648
2649
def oak_branch_fleet_mergeable(row: dict[str, Any]) -> Optional[bool]:
2650
    if row.get("merge_allowed") is True or row.get("recommended_action") in {
2651
        "merge",
2652
        "validate_then_merge",
2653
    }:
2654
        return True
2655
    if row.get("vcs_merge_safe") is False or row.get("recommended_action") in {
2656
        "resolve",
2657
        "keep_open_manual_resolution",
2658
    }:
2659
        return False
2660
    mergeability = str(row.get("mergeability") or "").lower()
2661
    if mergeability in {"clean", "mergeable"}:
2662
        return True
2663
    if mergeability in {"conflict", "conflicts", "conflicting"}:
2664
        return False
2665
    return None
2666
2667
2668
def oak_branch_fleet_cleanup_commands(
2669
    specs: list[dict[str, Any]], advancers: list[str]
2670
) -> list[list[str]]:
2671
    branches = [str(spec["name"]) for spec in specs] + list(advancers)
2672
    return oak_branch_fleet_cleanup_commands_for_branches(branches)
2673
2674
2675
def oak_branch_fleet_cleanup_commands_for_branches(branches: list[str]) -> list[list[str]]:
2676
    return [
2677
        ["oak", "close", "--remote", "--reason", "stale", "--json", branch]
2678
        for branch in branches
2679
    ]
2680
2681
2682
def oak_branch_fleet_apply_commands(merge_order: list[str]) -> list[list[str]]:
2683
    return [["oak", "merge", branch] for branch in merge_order]
2684
2685
2686
def oak_branch_fleet_cleanup_fields(failures: list[dict[str, Any]]) -> dict[str, Any]:
2687
    fields: dict[str, Any] = {"cleanup_failure_count": len(failures)}
2688
    if failures:
2689
        fields["cleanup_failures"] = failures
2690
    return fields
2691
2692
2693
@dataclass(frozen=True)
2694
class OakBranchFleetSeedStep:
2695
    branch: str
2696
    phase: str
2697
    phase_index: int
2698
    phase_total: int
2699
    commands: list[list[str]]
2700
    cleanup_after_command_index: int
2701
2702
2703
def oak_branch_fleet_write_command(rel_path: Path, content: str) -> list[str]:
2704
    return [
2705
        "/bin/zsh",
2706
        "-c",
2707
        "mkdir -p "
2708
        + shlex.quote(str(rel_path.parent))
2709
        + " && printf %s "
2710
        + shlex.quote(content)
2711
        + " > "
2712
        + shlex.quote(str(rel_path)),
2713
    ]
2714
2715
2716
def oak_branch_fleet_seed_steps(
2717
    specs: list[dict[str, Any]],
2718
    meta: dict[str, Any],
2719
    run_index: int,
2720
) -> tuple[list[OakBranchFleetSeedStep], list[str]]:
2721
    steps: list[OakBranchFleetSeedStep] = []
2722
    branch_total = len(specs)
2723
    for index, spec in enumerate(specs, start=1):
2724
        branch = str(spec["name"])
2725
        rel_path = Path(str(spec["path"]))
2726
        content = (
2727
            f"branch fleet {meta['bench_id']} run={run_index} "
2728
            f"branch={branch} kind={spec['kind']}\n"
2729
        )
2730
        steps.append(
2731
            OakBranchFleetSeedStep(
2732
                branch=branch,
2733
                phase="branch",
2734
                phase_index=index,
2735
                phase_total=branch_total,
2736
                commands=[
2737
                    ["oak", "switch", "--clean", "-c", branch],
2738
                    oak_branch_fleet_write_command(rel_path, content),
2739
                    ["oak", "commit", "--no-verify", "--push"],
2740
                ],
2741
                cleanup_after_command_index=2,
2742
            )
2743
        )
2744
2745
    conflict_specs = [spec for spec in specs if spec["kind"] == "conflicting"]
2746
    advancers: list[str] = []
2747
    conflict_total = len(conflict_specs)
2748
    for index, spec in enumerate(conflict_specs, start=1):
2749
        advancer = f"{spec['name']}-main-side"
2750
        advancers.append(advancer)
2751
        rel_path = Path(str(spec["path"]))
2752
        content = (
2753
            f"main-side conflict {meta['bench_id']} run={run_index} "
2754
            f"branch={spec['name']}\n"
2755
        )
2756
        steps.append(
2757
            OakBranchFleetSeedStep(
2758
                branch=advancer,
2759
                phase="conflict",
2760
                phase_index=index,
2761
                phase_total=conflict_total,
2762
                commands=[
2763
                    ["oak", "switch", "--clean", "-c", advancer],
2764
                    oak_branch_fleet_write_command(rel_path, content),
2765
                    ["oak", "commit", "--no-verify", "--push"],
2766
                    ["oak", "merge"],
2767
                ],
2768
                cleanup_after_command_index=2,
2769
            )
2770
        )
2771
    return steps, advancers
2772
2773
2774
def oak_branch_fleet_seed_diagnostics(
2775
    *,
2776
    branch_count: int,
2777
    conflict_count: int,
2778
    command_count: int,
2779
    completed_count: int,
2780
    completed_branch_count: int,
2781
    completed_conflict_count: int,
2782
    completed_command_count: int,
2783
    last_completed_branch: Optional[str],
2784
    failed_command_index: Optional[int],
2785
    failed_branch: Optional[str],
2786
) -> dict[str, Any]:
2787
    return {
2788
        "seed_branch_count": branch_count,
2789
        "seed_conflict_count": conflict_count,
2790
        "seed_command_count": command_count,
2791
        "seed_completed_count": completed_count,
2792
        "seed_completed_branch_count": completed_branch_count,
2793
        "seed_completed_conflict_count": completed_conflict_count,
2794
        "seed_completed_command_count": completed_command_count,
2795
        "last_completed_branch": last_completed_branch,
2796
        "failed_command_index": failed_command_index,
2797
        "failed_branch": failed_branch,
2798
    }
2799
2800
2801
def oak_branch_fleet_seed_text(value: Any) -> str:
2802
    if value is None:
2803
        return ""
2804
    if isinstance(value, bytes):
2805
        return value.decode("utf-8", errors="replace")
2806
    return str(value)
2807
2808
2809
def oak_branch_fleet_seed_exception_result(
2810
    command: list[str], exc: BaseException
2811
) -> subprocess.CompletedProcess[str]:
2812
    if isinstance(exc, subprocess.TimeoutExpired):
2813
        stdout = oak_branch_fleet_seed_text(exc.stdout)
2814
        stderr = oak_branch_fleet_seed_text(exc.stderr)
2815
        timeout = "" if exc.timeout is None else f":{exc.timeout}s"
2816
        message = f"seed_command_timeout{timeout}"
2817
        return subprocess.CompletedProcess(
2818
            command,
2819
            BRANCH_FLEET_SEED_TIMEOUT_RETCODE,
2820
            stdout,
2821
            "\n".join(part for part in (message, stderr) if part),
2822
        )
2823
    return subprocess.CompletedProcess(
2824
        command,
2825
        1,
2826
        "",
2827
        f"seed_command_exception:{type(exc).__name__}:{exc}",
2828
    )
2829
2830
2831
def oak_branch_fleet_seed_transient_failure(proc: subprocess.CompletedProcess[str]) -> bool:
2832
    text = f"{proc.stderr}\n{proc.stdout}".lower()
2833
    return any(
2834
        marker in text
2835
        for marker in (
2836
            "http 502",
2837
            "502 bad gateway",
2838
            "http 503",
2839
            "503 service unavailable",
2840
            "http 504",
2841
            "504 gateway timeout",
2842
        )
2843
    )
2844
2845
2846
def oak_branch_fleet_seed_progress(step: OakBranchFleetSeedStep, done: bool) -> str:
2847
    if step.phase == "conflict":
2848
        verb = "advanced conflicts" if done else "advancing conflict"
2849
        return f"[branch-fleet seed] {verb} {step.phase_index}/{step.phase_total} branch={step.branch}"
2850
    verb = "seeded" if done else "seeding"
2851
    return f"[branch-fleet seed] {verb} {step.phase_index}/{step.phase_total} branch={step.branch}"
2852
2853
2854
def oak_branch_fleet_seed_row(
2855
    meta: dict[str, Any],
2856
    scenario_name: str,
2857
    seed_steps: list[OakBranchFleetSeedStep],
2858
    cwd: Path,
2859
    run_index: int,
2860
    env: dict[str, str],
2861
    admitted: int,
2862
    branch_count: int,
2863
    conflict_count: int,
2864
    *,
2865
    progress: Optional[Callable[[str], None]] = None,
2866
    cleanup_branches: Optional[list[str]] = None,
2867
    runner: Optional[
2868
        Callable[[list[str], Optional[Path], dict[str, str]], subprocess.CompletedProcess[str]]
2869
    ] = None,
2870
    retry_limit: int = BRANCH_FLEET_SEED_TRANSIENT_RETRIES,
2871
    retry_delay_s: float = BRANCH_FLEET_SEED_RETRY_DELAY_S,
2872
    retry_sleep: Callable[[float], None] = time.sleep,
2873
    command_index_offset: int = 0,
2874
) -> dict[str, Any]:
2875
    if runner is None:
2876
        runner = run_untimed
2877
    emit = progress or (lambda message: print(message, file=sys.stderr, flush=True))
2878
    planned_commands = [command for step in seed_steps for command in step.commands]
2879
    command_count = len(planned_commands)
2880
    start = platform_clock.monotonic_ms()
2881
    stdout_parts: list[str] = []
2882
    stderr_parts: list[str] = []
2883
    attempted_commands: list[list[str]] = []
2884
    completed_count = 0
2885
    last_completed_branch: Optional[str] = None
2886
    failed_command_index: Optional[int] = None
2887
    failed_branch: Optional[str] = None
2888
    failed_command: Optional[list[str]] = None
2889
    failed_proc: Optional[subprocess.CompletedProcess[str]] = None
2890
    completed_branch_count = 0
2891
    completed_conflict_count = 0
2892
    retry_count = 0
2893
    retry_records: list[dict[str, Any]] = []
2894
    planned_command_index = 0
2895
2896
    for step in seed_steps:
2897
        emit(oak_branch_fleet_seed_progress(step, done=False))
2898
        step_failed = False
2899
        for step_command_index, command in enumerate(step.commands):
2900
            command_index = command_index_offset + planned_command_index
2901
            attempt = 0
2902
            while True:
2903
                try:
2904
                    proc = runner(command, cwd, env)
2905
                except Exception as exc:
2906
                    proc = oak_branch_fleet_seed_exception_result(command, exc)
2907
                attempted_commands.append(command)
2908
                stdout_parts.append(proc.stdout)
2909
                stderr_parts.append(proc.stderr)
2910
                if proc.returncode == 0:
2911
                    break
2912
                if attempt < retry_limit and oak_branch_fleet_seed_transient_failure(proc):
2913
                    attempt += 1
2914
                    retry_count += 1
2915
                    retry_records.append(
2916
                        {
2917
                            "branch": step.branch,
2918
                            "command_index": command_index,
2919
                            "attempt": attempt,
2920
                            "returncode": proc.returncode,
2921
                            "stderr": proc.stderr[-1000:],
2922
                        }
2923
                    )
2924
                    emit(
2925
                        "[branch-fleet seed] retrying transient failure "
2926
                        f"{attempt}/{retry_limit} branch={step.branch} "
2927
                        f"command_index={command_index}"
2928
                    )
2929
                    retry_sleep(retry_delay_s)
2930
                    continue
2931
                failed_command_index = command_index
2932
                failed_branch = step.branch
2933
                failed_command = command
2934
                failed_proc = proc
2935
                step_failed = True
2936
                break
2937
            planned_command_index += 1
2938
            if step_failed:
2939
                break
2940
            if (
2941
                cleanup_branches is not None
2942
                and step_command_index == step.cleanup_after_command_index
2943
                and step.branch not in cleanup_branches
2944
            ):
2945
                cleanup_branches.append(step.branch)
2946
        if step_failed:
2947
            break
2948
        completed_count += 1
2949
        if step.phase == "conflict":
2950
            completed_conflict_count += 1
2951
        else:
2952
            completed_branch_count += 1
2953
        last_completed_branch = step.branch
2954
        emit(oak_branch_fleet_seed_progress(step, done=True))
2955
2956
    elapsed_ms = platform_clock.monotonic_ms() - start
2957
    stdout = "\n".join(part for part in stdout_parts if part)
2958
    stderr = "\n".join(part for part in stderr_parts if part)
2959
    command_text = "\n".join(command_display(command) for command in planned_commands)
2960
    row = {
2961
        **meta,
2962
        "scenario": scenario_name,
2963
        "run": run_index,
2964
        "operation": "fleet.seed",
2965
        "elapsed_ms": round(elapsed_ms, 3),
2966
        "returncode": 0 if failed_proc is None else int(failed_proc.returncode),
2967
        "command": planned_commands,
2968
        "tool_call_count": len(attempted_commands),
2969
        "terminal_tool_call_count": len(attempted_commands),
2970
        "branch_fleet_provider": BRANCH_FLEET_PROVIDER_OAK_CLI,
2971
        "seed_retry_count": retry_count,
2972
        **oakbench_tokens.interaction_token_fields(
2973
            command_text,
2974
            stdout[:admitted],
2975
            stderr[:admitted],
2976
            len(stdout.encode("utf-8")),
2977
            len(stderr.encode("utf-8")),
2978
            len(stdout) > admitted,
2979
            len(stderr) > admitted,
2980
        ),
2981
        "_stdout": stdout,
2982
        **oak_branch_fleet_seed_diagnostics(
2983
            branch_count=branch_count,
2984
            conflict_count=conflict_count,
2985
            command_count=command_count,
2986
            completed_count=completed_count,
2987
            completed_branch_count=completed_branch_count,
2988
            completed_conflict_count=completed_conflict_count,
2989
            completed_command_count=len(attempted_commands),
2990
            last_completed_branch=last_completed_branch,
2991
            failed_command_index=failed_command_index,
2992
            failed_branch=failed_branch,
2993
        ),
2994
    }
2995
    if retry_records:
2996
        row["seed_retries"] = retry_records
2997
    if failed_proc is not None:
2998
        row["failure_reason"] = "seed_command_failed"
2999
        row["failed_command"] = failed_command
3000
        row["failed_command_stdout"] = failed_proc.stdout[-1000:]
3001
        row["failed_command_stderr"] = failed_proc.stderr[-1000:]
3002
        row["stderr"] = stderr[-4000:] or failed_proc.stdout[-4000:]
3003
    return row
3004
3005
3006
def oak_branch_fleet_seed_step_chunks(
3007
    seed_steps: list[OakBranchFleetSeedStep],
3008
    worker_count: int,
3009
) -> list[tuple[int, int, list[OakBranchFleetSeedStep]]]:
3010
    indexed_branch_steps = [
3011
        (index, step) for index, step in enumerate(seed_steps) if step.phase == "branch"
3012
    ]
3013
    if not indexed_branch_steps:
3014
        return []
3015
    bounded_workers = max(1, min(worker_count, len(indexed_branch_steps)))
3016
    base_size, remainder = divmod(len(indexed_branch_steps), bounded_workers)
3017
    chunks: list[tuple[int, int, list[OakBranchFleetSeedStep]]] = []
3018
    start = 0
3019
    for worker_id in range(bounded_workers):
3020
        size = base_size + (1 if worker_id < remainder else 0)
3021
        pairs = indexed_branch_steps[start:start + size]
3022
        if not pairs:
3023
            continue
3024
        first_step_index = pairs[0][0]
3025
        command_offset = sum(len(step.commands) for step in seed_steps[:first_step_index])
3026
        chunks.append((worker_id, command_offset, [step for _index, step in pairs]))
3027
        start += size
3028
    return chunks
3029
3030
3031
def oak_branch_fleet_seed_clone_failure_row(
3032
    meta: dict[str, Any],
3033
    scenario_name: str,
3034
    run_index: int,
3035
    *,
3036
    worker_id: int,
3037
    command: list[str],
3038
    proc: subprocess.CompletedProcess[str],
3039
    elapsed_ms: float,
3040
    admitted: int,
3041
    command_index_offset: int,
3042
    failed_branch: Optional[str],
3043
    branch_count: int,
3044
    command_count: int,
3045
) -> dict[str, Any]:
3046
    stdout = proc.stdout or ""
3047
    stderr = proc.stderr or ""
3048
    return {
3049
        **meta,
3050
        "scenario": scenario_name,
3051
        "run": run_index,
3052
        "operation": "fleet.seed",
3053
        "elapsed_ms": round(elapsed_ms, 3),
3054
        "returncode": int(proc.returncode),
3055
        "command": [command],
3056
        "tool_call_count": 1,
3057
        "terminal_tool_call_count": 1,
3058
        "branch_fleet_provider": BRANCH_FLEET_PROVIDER_OAK_CLI,
3059
        "seed_worker_id": worker_id,
3060
        "seed_retry_count": 0,
3061
        "failure_reason": "seed_worker_clone_failed",
3062
        "failed_command": command,
3063
        "failed_command_stdout": stdout[-1000:],
3064
        "failed_command_stderr": stderr[-1000:],
3065
        "stderr": stderr[-4000:] or stdout[-4000:],
3066
        **oakbench_tokens.interaction_token_fields(
3067
            command_display(command),
3068
            stdout[:admitted],
3069
            stderr[:admitted],
3070
            len(stdout.encode("utf-8")),
3071
            len(stderr.encode("utf-8")),
3072
            len(stdout) > admitted,
3073
            len(stderr) > admitted,
3074
        ),
3075
        **oak_branch_fleet_seed_diagnostics(
3076
            branch_count=branch_count,
3077
            conflict_count=0,
3078
            command_count=command_count,
3079
            completed_count=0,
3080
            completed_branch_count=0,
3081
            completed_conflict_count=0,
3082
            completed_command_count=0,
3083
            last_completed_branch=None,
3084
            failed_command_index=command_index_offset,
3085
            failed_branch=failed_branch,
3086
        ),
3087
    }
3088
3089
3090
def oak_branch_fleet_aggregate_seed_rows(
3091
    meta: dict[str, Any],
3092
    scenario_name: str,
3093
    run_index: int,
3094
    *,
3095
    seed_rows: list[dict[str, Any]],
3096
    elapsed_ms: float,
3097
    planned_commands: list[list[str]],
3098
    branch_count: int,
3099
    conflict_count: int,
3100
    worker_count: int,
3101
    worker_clone_count: int,
3102
) -> dict[str, Any]:
3103
    failed_rows = [
3104
        row for row in seed_rows
3105
        if row_returncode(row) not in (0, SKIP_RETURNCODE)
3106
    ]
3107
    worker_rows = [
3108
        row for row in seed_rows
3109
        if isinstance(row.get("seed_worker_id"), int)
3110
    ]
3111
    failed_worker_rows = [
3112
        row for row in worker_rows
3113
        if row_returncode(row) not in (0, SKIP_RETURNCODE)
3114
    ]
3115
    failed_rows.sort(
3116
        key=lambda row: (
3117
            row.get("failed_command_index")
3118
            if row.get("failed_command_index") is not None
3119
            else 1_000_000_000
3120
        )
3121
    )
3122
    failed = failed_rows[0] if failed_rows else None
3123
    retry_records: list[dict[str, Any]] = []
3124
    for row in seed_rows:
3125
        retry_records.extend(row.get("seed_retries") or [])
3126
    last_completed_branch: Optional[str] = None
3127
    for row in seed_rows:
3128
        if row.get("last_completed_branch") is not None:
3129
            last_completed_branch = str(row["last_completed_branch"])
3130
    command_count = len(planned_commands)
3131
    row = {
3132
        **meta,
3133
        "scenario": scenario_name,
3134
        "run": run_index,
3135
        "operation": "fleet.seed",
3136
        "elapsed_ms": round(elapsed_ms, 3),
3137
        "returncode": 0 if failed is None else row_returncode(failed),
3138
        "command": planned_commands,
3139
        "tool_call_count": sum(int(child.get("tool_call_count") or 0) for child in seed_rows),
3140
        "terminal_tool_call_count": sum(
3141
            int(child.get("terminal_tool_call_count") or 0) for child in seed_rows
3142
        ),
3143
        "branch_fleet_provider": BRANCH_FLEET_PROVIDER_OAK_CLI,
3144
        "seed_parallel": worker_count > 1,
3145
        "seed_worker_count": worker_count,
3146
        "seed_worker_clone_count": worker_clone_count,
3147
        "seed_worker_success_count": len(worker_rows) - len(failed_worker_rows),
3148
        "seed_conflict_serial": True,
3149
        "seed_retry_count": sum(int(child.get("seed_retry_count") or 0) for child in seed_rows),
3150
        **oakbench_tokens.summed_token_fields(
3151
            seed_rows,
3152
            "sum_of_branch_fleet_seed_worker_rows_command_plus_admitted_output_chars_div_4",
3153
        ),
3154
        **oak_branch_fleet_seed_diagnostics(
3155
            branch_count=branch_count,
3156
            conflict_count=conflict_count,
3157
            command_count=command_count,
3158
            completed_count=sum(int(child.get("seed_completed_count") or 0) for child in seed_rows),
3159
            completed_branch_count=sum(
3160
                int(child.get("seed_completed_branch_count") or 0) for child in seed_rows
3161
            ),
3162
            completed_conflict_count=sum(
3163
                int(child.get("seed_completed_conflict_count") or 0) for child in seed_rows
3164
            ),
3165
            completed_command_count=sum(
3166
                int(child.get("seed_completed_command_count") or 0) for child in seed_rows
3167
            ),
3168
            last_completed_branch=last_completed_branch,
3169
            failed_command_index=None if failed is None else failed.get("failed_command_index"),
3170
            failed_branch=None if failed is None else failed.get("failed_branch"),
3171
        ),
3172
        "_stdout": "\n".join(str(child.get("_stdout") or "") for child in seed_rows),
3173
    }
3174
    if retry_records:
3175
        row["seed_retries"] = retry_records
3176
    if failed is not None:
3177
        row["failure_reason"] = failed.get("failure_reason") or "seed_command_failed"
3178
        row["failed_command"] = failed.get("failed_command")
3179
        row["failed_command_stdout"] = failed.get("failed_command_stdout", "")
3180
        row["failed_command_stderr"] = failed.get("failed_command_stderr", "")
3181
        row["stderr"] = failed.get("stderr") or row.get("failed_command_stderr", "")
3182
    return row
3183
3184
3185
def oak_branch_fleet_run_cleanup(
3186
    cleanup_commands: list[list[str]],
3187
    workdirs: list[Path],
3188
    env: dict[str, str],
3189
    worker_count: int,
3190
    runner: Optional[
3191
        Callable[[list[str], Optional[Path], dict[str, str]], subprocess.CompletedProcess[str]]
3192
    ] = None,
3193
) -> list[dict[str, Any]]:
3194
    if runner is None:
3195
        runner = run_untimed
3196
    if not cleanup_commands:
3197
        return []
3198
    existing_workdirs = [workdir for workdir in workdirs if workdir.exists()]
3199
    if not existing_workdirs:
3200
        return []
3201
    bounded_workers = max(1, min(worker_count, len(existing_workdirs), len(cleanup_commands)))
3202
    base_size, remainder = divmod(len(cleanup_commands), bounded_workers)
3203
    command_chunks: list[tuple[int, Path, list[tuple[int, list[str]]]]] = []
3204
    start = 0
3205
    for worker_id in range(bounded_workers):
3206
        size = base_size + (1 if worker_id < remainder else 0)
3207
        indexed = list(enumerate(cleanup_commands[start:start + size], start=start))
3208
        command_chunks.append((worker_id, existing_workdirs[worker_id], indexed))
3209
        start += size
3210
3211
    def run_chunk(
3212
        worker_id: int, cwd: Path, indexed_commands: list[tuple[int, list[str]]]
3213
    ) -> list[dict[str, Any]]:
3214
        failures: list[dict[str, Any]] = []
3215
        for command_index, command in indexed_commands:
3216
            try:
3217
                proc = runner(command, cwd, env)
3218
            except subprocess.TimeoutExpired as exc:
3219
                failures.append(
3220
                    {
3221
                        "branch": command[-1],
3222
                        "command": command,
3223
                        "command_index": command_index,
3224
                        "worker_id": worker_id,
3225
                        "cwd": str(cwd),
3226
                        "returncode": 124,
3227
                        "stdout": oak_branch_fleet_seed_text(exc.stdout)[-1000:],
3228
                        "stderr": f"cleanup_command_timeout:{exc.timeout}s",
3229
                        "failure_reason": "cleanup_command_failed",
3230
                    }
3231
                )
3232
                continue
3233
            except Exception as exc:
3234
                failures.append(
3235
                    {
3236
                        "branch": command[-1],
3237
                        "command": command,
3238
                        "command_index": command_index,
3239
                        "worker_id": worker_id,
3240
                        "cwd": str(cwd),
3241
                        "returncode": 1,
3242
                        "stdout": "",
3243
                        "stderr": f"cleanup_command_exception:{type(exc).__name__}:{exc}",
3244
                        "failure_reason": "cleanup_command_failed",
3245
                    }
3246
                )
3247
                continue
3248
            if proc.returncode != 0:
3249
                failures.append(
3250
                    {
3251
                        "branch": command[-1],
3252
                        "command": command,
3253
                        "command_index": command_index,
3254
                        "worker_id": worker_id,
3255
                        "cwd": str(cwd),
3256
                        "returncode": proc.returncode,
3257
                        "stdout": proc.stdout[-1000:],
3258
                        "stderr": proc.stderr[-1000:],
3259
                    }
3260
                )
3261
        return failures
3262
3263
    if bounded_workers == 1:
3264
        return run_chunk(command_chunks[0][0], command_chunks[0][1], command_chunks[0][2])
3265
3266
    failures: list[dict[str, Any]] = []
3267
    with concurrent.futures.ThreadPoolExecutor(max_workers=bounded_workers) as pool:
3268
        futures = [
3269
            pool.submit(run_chunk, worker_id, cwd, indexed_commands)
3270
            for worker_id, cwd, indexed_commands in command_chunks
3271
        ]
3272
        for future in concurrent.futures.as_completed(futures):
3273
            failures.extend(future.result())
3274
    failures.sort(key=lambda failure: int(failure.get("command_index") or 0))
3275
    return failures
3276
3277
3278
def run_oak_cli_branch_fleet_scenario(
3279
    args: argparse.Namespace,
3280
    scenario: dict[str, Any],
3281
    meta: dict[str, Any],
3282
    run_index: int,
3283
    run_root: Path,
3284
    settings: Settings,
3285
) -> list[dict[str, Any]]:
3286
    scenario_name = str(scenario["name"])
3287
    operations = [str(op) for op in scenario.get("operations") or []]
3288
    count = branch_fleet_count(scenario)
3289
    specs = branch_fleet_specs(
3290
        bench_id=str(meta.get("bench_id") or "bench"),
3291
        scenario_name=scenario_name,
3292
        run_index=run_index,
3293
        branch_count=count,
3294
    )
3295
    env = cli_env(args)
3296
    workdir = run_root / f"oak-branch-fleet-r{run_index}"
3297
    reviewed: list[dict[str, Any]] = []
3298
    plan: dict[str, Any] = {}
3299
    apply_result: dict[str, Any] = {
3300
        "apply_succeeded": False,
3301
        "merged_branches": [],
3302
        "final_survivors": [],
3303
    }
3304
    oracle: dict[str, Any] = {}
3305
    total_start = platform_clock.monotonic_ms()
3306
    cleanup_branches: list[str] = []
3307
    seed_workdirs: list[Path] = []
3308
3309
    def seed() -> dict[str, Any]:
3310
        nonlocal seed_workdirs
3311
        start = platform_clock.monotonic_ms()
3312
        seed_steps, _advancers = oak_branch_fleet_seed_steps(specs, meta, run_index)
3313
        planned_commands = [command for step in seed_steps for command in step.commands]
3314
        seed_command_count = sum(len(step.commands) for step in seed_steps)
3315
        conflict_count = len(_advancers)
3316
        clone = run_untimed(["oak", "clone", args.repo, str(workdir)], run_root, env)
3317
        if clone.returncode != 0:
3318
            row = branch_fleet_row(
3319
                meta,
3320
                scenario_name,
3321
                "fleet.seed",
3322
                run_index,
3323
                platform_clock.monotonic_ms() - start,
3324
                returncode=clone.returncode,
3325
                provider=BRANCH_FLEET_PROVIDER_OAK_CLI,
3326
                command=[["oak", "clone", args.repo, str(workdir)]],
3327
                stderr=clone.stderr[-4000:],
3328
                tool_call_count=1,
3329
                terminal_tool_call_count=1,
3330
                **oak_branch_fleet_seed_diagnostics(
3331
                    branch_count=count,
3332
                    conflict_count=conflict_count,
3333
                    command_count=seed_command_count,
3334
                    completed_count=0,
3335
                    completed_branch_count=0,
3336
                    completed_conflict_count=0,
3337
                    completed_command_count=0,
3338
                    last_completed_branch=None,
3339
                    failed_command_index=None,
3340
                    failed_branch=None,
3341
                ),
3342
                seed_parallel=False,
3343
                seed_worker_count=0,
3344
                seed_worker_clone_count=1,
3345
                seed_conflict_serial=True,
3346
                **branch_fleet_metric_fields(branch_count=count),
3347
            )
3348
            row["branches_seeded"] = 0
3349
            return row
3350
3351
        branch_steps = [step for step in seed_steps if step.phase == "branch"]
3352
        conflict_steps = [step for step in seed_steps if step.phase == "conflict"]
3353
        worker_count = branch_fleet_seed_worker_count(len(branch_steps), env)
3354
        branch_chunks = oak_branch_fleet_seed_step_chunks(seed_steps, worker_count)
3355
        progress_lock = threading.Lock()
3356
3357
        def emit_progress(message: str) -> None:
3358
            with progress_lock:
3359
                print(message, file=sys.stderr, flush=True)
3360
3361
        def worker_seed(
3362
            worker_id: int,
3363
            command_index_offset: int,
3364
            worker_steps: list[OakBranchFleetSeedStep],
3365
        ) -> tuple[int, dict[str, Any], list[str], Path]:
3366
            worker_dir = run_root / f"oak-branch-fleet-r{run_index}-seed-w{worker_id}"
3367
            worker_start = platform_clock.monotonic_ms()
3368
            worker_clone = run_untimed(["oak", "clone", args.repo, str(worker_dir)], run_root, env)
3369
            if worker_clone.returncode != 0:
3370
                return (
3371
                    worker_id,
3372
                    oak_branch_fleet_seed_clone_failure_row(
3373
                        meta,
3374
                        scenario_name,
3375
                        run_index,
3376
                        worker_id=worker_id,
3377
                        command=["oak", "clone", args.repo, str(worker_dir)],
3378
                        proc=worker_clone,
3379
                        elapsed_ms=platform_clock.monotonic_ms() - worker_start,
3380
                        admitted=settings.admitted,
3381
                        command_index_offset=command_index_offset,
3382
                        failed_branch=worker_steps[0].branch if worker_steps else None,
3383
                        branch_count=len(worker_steps),
3384
                        command_count=sum(len(step.commands) for step in worker_steps),
3385
                    ),
3386
                    [],
3387
                    worker_dir,
3388
                )
3389
            local_cleanup: list[str] = []
3390
            row = oak_branch_fleet_seed_row(
3391
                meta,
3392
                scenario_name,
3393
                worker_steps,
3394
                worker_dir,
3395
                run_index,
3396
                env,
3397
                settings.admitted,
3398
                len(worker_steps),
3399
                0,
3400
                progress=emit_progress,
3401
                cleanup_branches=local_cleanup,
3402
                command_index_offset=command_index_offset,
3403
            )
3404
            row["seed_worker_id"] = worker_id
3405
            row["seed_worker_clone_count"] = 1
3406
            row["elapsed_ms"] = round(platform_clock.monotonic_ms() - worker_start, 3)
3407
            return worker_id, row, local_cleanup, worker_dir
3408
3409
        branch_seed_results: list[tuple[int, dict[str, Any], list[str], Path]] = []
3410
        if branch_chunks:
3411
            with concurrent.futures.ThreadPoolExecutor(max_workers=len(branch_chunks)) as pool:
3412
                futures = [
3413
                    pool.submit(worker_seed, worker_id, command_offset, worker_steps)
3414
                    for worker_id, command_offset, worker_steps in branch_chunks
3415
                ]
3416
                for future in concurrent.futures.as_completed(futures):
3417
                    branch_seed_results.append(future.result())
3418
            branch_seed_results.sort(key=lambda result: result[0])
3419
3420
        seed_rows = [result[1] for result in branch_seed_results]
3421
        for _worker_id, _row, worker_cleanup, worker_dir in branch_seed_results:
3422
            cleanup_branches.extend(worker_cleanup)
3423
            seed_workdirs.append(worker_dir)
3424
3425
        branch_seed_failed = any(row_returncode(row) not in (0, SKIP_RETURNCODE) for row in seed_rows)
3426
        if not branch_seed_failed and conflict_steps:
3427
            conflict_cleanup: list[str] = []
3428
            conflict_row = oak_branch_fleet_seed_row(
3429
                meta,
3430
                scenario_name,
3431
                conflict_steps,
3432
                workdir,
3433
                run_index,
3434
                env,
3435
                settings.admitted,
3436
                0,
3437
                conflict_count,
3438
                progress=emit_progress,
3439
                cleanup_branches=conflict_cleanup,
3440
                command_index_offset=sum(len(step.commands) for step in branch_steps),
3441
            )
3442
            conflict_row["seed_worker_id"] = "conflict-serial"
3443
            conflict_row["seed_worker_clone_count"] = 0
3444
            seed_rows.append(conflict_row)
3445
            cleanup_branches.extend(conflict_cleanup)
3446
3447
        row = oak_branch_fleet_aggregate_seed_rows(
3448
            meta,
3449
            scenario_name,
3450
            run_index,
3451
            seed_rows=seed_rows,
3452
            elapsed_ms=platform_clock.monotonic_ms() - start,
3453
            planned_commands=planned_commands,
3454
            branch_count=count,
3455
            conflict_count=conflict_count,
3456
            worker_count=len(branch_chunks),
3457
            worker_clone_count=len(branch_seed_results) + 1,
3458
        )
3459
        row["seeded_branch_classes"] = [spec["kind"] for spec in specs]
3460
        row.update(branch_fleet_metric_fields(branch_count=count))
3461
        row["branches_seeded"] = int(row.get("seed_completed_branch_count") or 0)
3462
        return row
3463
3464
    def classify() -> dict[str, Any]:
3465
        nonlocal reviewed
3466
        row = cli_step_row(
3467
            meta,
3468
            scenario_name,
3469
            "fleet.classify",
3470
            [
3471
                "oak",
3472
                "branch",
3473
                "triage",
3474
                "--remote",
3475
                "--against",
3476
                "main",
3477
                "--status",
3478
                "open",
3479
                "--analysis-depth",
3480
                "manifest",
3481
                "--json",
3482
            ],
3483
            workdir,
3484
            run_index,
3485
            env,
3486
            settings.admitted,
3487
            full_stdout_bytes=BRANCH_FLEET_CLASSIFY_PARSE_STDOUT_BYTES,
3488
        )
3489
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_OAK_CLI
3490
        rows_payload: list[Any] = []
3491
        if row["returncode"] == 0:
3492
            if int(row.get("stdout_bytes") or 0) > BRANCH_FLEET_CLASSIFY_PARSE_STDOUT_BYTES:
3493
                row["returncode"] = 1
3494
                row["stderr"] = (
3495
                    "oak_branch_triage_stdout_exceeded_parse_limit:"
3496
                    f"{BRANCH_FLEET_CLASSIFY_PARSE_STDOUT_BYTES}"
3497
                )
3498
            else:
3499
                try:
3500
                    payload = json.loads(row.get("_stdout") or "{}")
3501
                    rows_payload = payload.get("branches") or payload.get("rows") or []
3502
                except json.JSONDecodeError as exc:
3503
                    row["returncode"] = 1
3504
                    row["stderr"] = f"oak_branch_triage_invalid_json:{exc}"
3505
        by_name = {
3506
            str(item.get("branch") or item.get("name")): item
3507
            for item in rows_payload
3508
            if isinstance(item, dict)
3509
        }
3510
        reviewed = []
3511
        for spec in specs:
3512
            actual = by_name.get(str(spec["name"])) or {}
3513
            reviewed.append(
3514
                {
3515
                    "name": str(spec["name"]),
3516
                    "mergeable": oak_branch_fleet_mergeable(actual),
3517
                    "expected_kind": spec["kind"],
3518
                    "raw": actual,
3519
                }
3520
            )
3521
        row["reviewed_branches"] = [branch["name"] for branch in reviewed]
3522
        row.update(branch_fleet_metric_fields(branch_count=count, reviewed=reviewed))
3523
        return row
3524
3525
    def plan_row() -> dict[str, Any]:
3526
        nonlocal plan
3527
        start = platform_clock.monotonic_ms()
3528
        plan = branch_fleet_plan(reviewed)
3529
        return branch_fleet_row(
3530
            meta,
3531
            scenario_name,
3532
            "fleet.plan",
3533
            run_index,
3534
            platform_clock.monotonic_ms() - start,
3535
            provider=BRANCH_FLEET_PROVIDER_OAK_CLI,
3536
            action_plan=plan["actions"],
3537
            merge_order=plan["merge_order"],
3538
            tool_call_count=0,
3539
            terminal_tool_call_count=0,
3540
            **branch_fleet_metric_fields(branch_count=count, reviewed=reviewed, plan=plan),
3541
        )
3542
3543
    def apply() -> dict[str, Any]:
3544
        nonlocal apply_result
3545
        merge_order = list(plan.get("merge_order") or [])
3546
        commands = oak_branch_fleet_apply_commands([str(branch) for branch in merge_order])
3547
        row = cli_sequence_row(
3548
            meta, scenario_name, "fleet.apply", commands, workdir, run_index, env, settings.admitted
3549
        )
3550
        survivors = [
3551
            action["branch"]
3552
            for action in plan.get("actions") or []
3553
            if action["action"] == "leave_conflict_open"
3554
        ]
3555
        apply_result = {
3556
            "apply_succeeded": row["returncode"] == 0,
3557
            "merged_branches": merge_order if row["returncode"] == 0 else [],
3558
            "final_survivors": survivors if row["returncode"] == 0 else [],
3559
        }
3560
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_OAK_CLI
3561
        row.update(apply_result)
3562
        row.update(
3563
            branch_fleet_metric_fields(
3564
                branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
3565
            )
3566
        )
3567
        return row
3568
3569
    def sync() -> dict[str, Any]:
3570
        nonlocal apply_result
3571
        row = cli_sequence_row(
3572
            meta,
3573
            scenario_name,
3574
            "fleet.sync",
3575
            [["oak", "branch", "list", "--remote", "--status", "open", "--json"]],
3576
            workdir,
3577
            run_index,
3578
            env,
3579
            settings.admitted,
3580
        )
3581
        row["branch_fleet_provider"] = BRANCH_FLEET_PROVIDER_OAK_CLI
3582
        if row["returncode"] == 0:
3583
            try:
3584
                final_survivors = branch_fleet_survivors_from_sync_stdout(
3585
                    str(row.get("_stdout") or ""), specs
3586
                )
3587
            except json.JSONDecodeError as exc:
3588
                row["returncode"] = 1
3589
                row["stderr"] = f"oak_branch_list_invalid_json:{exc}"
3590
            except ValueError as exc:
3591
                row["returncode"] = 1
3592
                row["stderr"] = f"oak_branch_list_schema_error:{exc}"
3593
            else:
3594
                apply_result = {
3595
                    **apply_result,
3596
                    "final_survivors": final_survivors,
3597
                }
3598
                row["final_survivors"] = final_survivors
3599
        row.update(
3600
            branch_fleet_metric_fields(
3601
                branch_count=count, reviewed=reviewed, plan=plan, apply_result=apply_result
3602
            )
3603
        )
3604
        return row
3605
3606
    def oracle_row() -> dict[str, Any]:
3607
        nonlocal oracle
3608
        start = platform_clock.monotonic_ms()
3609
        oracle = branch_fleet_oracle(specs, plan, apply_result)
3610
        ok = all(
3611
            bool(oracle[key])
3612
            for key in (
3613
                "apply_succeeded",
3614
                "action_plan_correct",
3615
                "merge_order_correct",
3616
                "final_survivors_correct",
3617
            )
3618
        )
3619
        fields = {
3620
            **oracle,
3621
            **branch_fleet_metric_fields(
3622
                branch_count=count,
3623
                reviewed=reviewed,
3624
                plan=plan,
3625
                apply_result=apply_result,
3626
                oracle=oracle,
3627
            ),
3628
        }
3629
        return branch_fleet_row(
3630
            meta,
3631
            scenario_name,
3632
            "fleet.oracle",
3633
            run_index,
3634
            platform_clock.monotonic_ms() - start,
3635
            returncode=0 if ok else 1,
3636
            provider=BRANCH_FLEET_PROVIDER_OAK_CLI,
3637
            tool_call_count=0,
3638
            terminal_tool_call_count=0,
3639
            **fields,
3640
        )
3641
3642
    def total() -> dict[str, Any]:
3643
        failures = [
3644
            row for row in rows
3645
            if row_returncode(row) not in (0, SKIP_RETURNCODE)
3646
        ]
3647
        row = branch_fleet_row(
3648
            meta,
3649
            scenario_name,
3650
            "fleet.total",
3651
            run_index,
3652
            platform_clock.monotonic_ms() - total_start,
3653
            returncode=1 if failures else 0,
3654
            provider=BRANCH_FLEET_PROVIDER_OAK_CLI,
3655
            summarized_operations=[row["operation"] for row in rows],
3656
            **branch_fleet_workflow_summary_fields(rows),
3657
            **branch_fleet_metric_fields(
3658
                branch_count=count,
3659
                reviewed=reviewed,
3660
                plan=plan,
3661
                apply_result=apply_result,
3662
                oracle=oracle,
3663
            ),
3664
        )
3665
        row["tool_call_count"] = sum(int(r.get("tool_call_count") or 0) for r in rows)
3666
        row.update(
3667
            oakbench_tokens.summed_token_fields(
3668
                rows, "sum_of_branch_fleet_steps_command_plus_admitted_output_chars_div_4"
3669
            )
3670
        )
3671
        return row
3672
3673
    handlers: dict[str, Callable[[], dict[str, Any]]] = {
3674
        "fleet.seed": seed,
3675
        "fleet.classify": classify,
3676
        "fleet.plan": plan_row,
3677
        "fleet.apply": apply,
3678
        "fleet.sync": sync,
3679
        "fleet.oracle": oracle_row,
3680
        "fleet.total": total,
3681
    }
3682
    rows: list[dict[str, Any]] = []
3683
    aborted_at: Optional[str] = None
3684
    for operation in operations:
3685
        if operation == "fleet.total":
3686
            row = total()
3687
            row.pop("_stdout", None)
3688
            rows.append(row)
3689
            continue
3690
        if aborted_at is not None:
3691
            rows.append(skip_row(meta, scenario_name, operation, run_index, f"prior_step_failed:{aborted_at}"))
3692
            continue
3693
        handler = handlers.get(operation)
3694
        if handler is None:
3695
            rows.append(skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented"))
3696
            continue
3697
        row = handler()
3698
        row.pop("_stdout", None)
3699
        rows.append(row)
3700
        if row_returncode(row) not in (0, SKIP_RETURNCODE):
3701
            aborted_at = operation
3702
3703
    cleanup_commands = oak_branch_fleet_cleanup_commands_for_branches(cleanup_branches)
3704
    cleanup_failures = oak_branch_fleet_run_cleanup(
3705
        cleanup_commands,
3706
        [workdir] + seed_workdirs,
3707
        env,
3708
        branch_fleet_cleanup_worker_count(len(cleanup_commands), env),
3709
    )
3710
    cleanup_fields = oak_branch_fleet_cleanup_fields(cleanup_failures)
3711
    for row in rows:
3712
        if row.get("operation") == "fleet.total":
3713
            row.update(cleanup_fields)
3714
            break
3715
    return rows
3716
3717
3718
# --------------------------------------------------------------------------
3719
# branch_triage_nN: seeded remote branch/PR review classification.
3720
#
3721
# The real provider path is intentionally skip-only for now. The fake provider
3722
# exercises the decision/oracle logic deterministically: N seeded branches cycle
3723
# through clean/stale/duplicate/conflicting, the plan decides merge/refresh/
3724
# close/leave-open actions, and the oracle checks both the action plan and the
3725
# final survivors.
3726
# --------------------------------------------------------------------------
3727
3728
3729
def is_branch_triage_scenario(scenario: dict[str, Any]) -> bool:
3730
    return BRANCH_TRIAGE_SCENARIO_RE.match(str(scenario.get("name", ""))) is not None
3731
3732
3733
def branch_triage_count(scenario: dict[str, Any]) -> int:
3734
    if scenario.get("branch_count") is not None:
3735
        return int(scenario["branch_count"])
3736
    match = BRANCH_TRIAGE_SCENARIO_RE.match(str(scenario.get("name", "")))
3737
    return int(match.group(1)) if match else 4
3738
3739
3740
def seeded_branch_triage_branches(branch_count: int) -> list[dict[str, Any]]:
3741
    branches: list[dict[str, Any]] = []
3742
    clean_patch_id = "patch-clean-0"
3743
    for index in range(branch_count):
3744
        expected_class = BRANCH_TRIAGE_CLASSES[index % len(BRANCH_TRIAGE_CLASSES)]
3745
        name = f"triage-{expected_class}-{index}"
3746
        branch: dict[str, Any] = {
3747
            "name": name,
3748
            "expected_class": expected_class,
3749
            "expected_action": BRANCH_TRIAGE_ACTIONS[expected_class],
3750
            "base_sha": "main-sha-0",
3751
            "head_sha": f"head-{index}",
3752
            "behind_by": 0,
3753
            "mergeable": True,
3754
            "conflict_paths": [],
3755
            "patch_id": f"patch-{expected_class}-{index}",
3756
        }
3757
        if expected_class == "clean":
3758
            branch["patch_id"] = clean_patch_id if index == 0 else f"patch-clean-{index}"
3759
        elif expected_class == "stale":
3760
            branch["behind_by"] = 2
3761
            branch["base_sha"] = "old-main-sha"
3762
        elif expected_class == "duplicate":
3763
            branch["patch_id"] = clean_patch_id
3764
            branch["duplicate_of"] = "triage-clean-0"
3765
        elif expected_class == "conflicting":
3766
            branch["mergeable"] = False
3767
            branch["conflict_paths"] = [f"src/conflict-{index}.txt"]
3768
        branches.append(branch)
3769
    return branches
3770
3771
3772
def classify_triage_branch(branch: dict[str, Any], seen_patch_ids: set[str]) -> str:
3773
    if branch.get("conflict_paths") or branch.get("mergeable") is False:
3774
        return "conflicting"
3775
    patch_id = str(branch.get("patch_id") or "")
3776
    if patch_id and patch_id in seen_patch_ids:
3777
        return "duplicate"
3778
    if int(branch.get("behind_by") or 0) > 0:
3779
        if patch_id:
3780
            seen_patch_ids.add(patch_id)
3781
        return "stale"
3782
    if patch_id:
3783
        seen_patch_ids.add(patch_id)
3784
    return "clean"
3785
3786
3787
def branch_triage_plan(reviewed: list[dict[str, Any]]) -> dict[str, Any]:
3788
    seen_patch_ids: set[str] = set()
3789
    actions: list[dict[str, str]] = []
3790
    merge_order: list[str] = []
3791
    detected_counts = {key: 0 for key in BRANCH_TRIAGE_CLASSES}
3792
    for branch in reviewed:
3793
        classification = classify_triage_branch(branch, seen_patch_ids)
3794
        detected_counts[classification] += 1
3795
        action = BRANCH_TRIAGE_ACTIONS[classification]
3796
        name = str(branch["name"])
3797
        actions.append({"branch": name, "classification": classification, "action": action})
3798
        if action in ("merge", "refresh_then_merge"):
3799
            merge_order.append(name)
3800
    return {
3801
        "actions": actions,
3802
        "merge_order": merge_order,
3803
        "detected_counts": detected_counts,
3804
    }
3805
3806
3807
def expected_triage_plan(branches: list[dict[str, Any]]) -> dict[str, Any]:
3808
    counts = {key: 0 for key in BRANCH_TRIAGE_CLASSES}
3809
    actions: list[dict[str, str]] = []
3810
    merge_order: list[str] = []
3811
    for branch in branches:
3812
        classification = str(branch["expected_class"])
3813
        action = str(branch["expected_action"])
3814
        counts[classification] += 1
3815
        name = str(branch["name"])
3816
        actions.append({"branch": name, "classification": classification, "action": action})
3817
        if action in ("merge", "refresh_then_merge"):
3818
            merge_order.append(name)
3819
    return {"actions": actions, "merge_order": merge_order, "detected_counts": counts}
3820
3821
3822
class FakeBranchTriageProvider:
3823
    def __init__(self, branch_count: int) -> None:
3824
        self._branches = seeded_branch_triage_branches(branch_count)
3825
        self._open = {str(branch["name"]) for branch in self._branches}
3826
        self.closed: list[str] = []
3827
3828
    def seed(self) -> list[dict[str, Any]]:
3829
        return [dict(branch) for branch in self._branches]
3830
3831
    def review(self) -> list[dict[str, Any]]:
3832
        return [dict(branch) for branch in self._branches if str(branch["name"]) in self._open]
3833
3834
    def apply(self, plan: dict[str, Any]) -> dict[str, Any]:
3835
        for action in plan.get("actions") or []:
3836
            branch = str(action["branch"])
3837
            decision = str(action["action"])
3838
            if decision in ("merge", "refresh_then_merge", "close_duplicate") and branch in self._open:
3839
                self._open.remove(branch)
3840
                self.closed.append(branch)
3841
        return {"branches_closed": len(self.closed), "final_survivors": sorted(self._open)}
3842
3843
3844
def branch_triage_metric_fields(
3845
    *,
3846
    branches_reviewed: int,
3847
    branches_closed: int,
3848
    plan: Optional[dict[str, Any]] = None,
3849
    oracle: Optional[dict[str, Any]] = None,
3850
) -> dict[str, Any]:
3851
    counts = ((plan or {}).get("detected_counts") or {})
3852
    return {
3853
        "branches_reviewed": branches_reviewed,
3854
        "branches_closed": branches_closed,
3855
        "merge_order_correct": (oracle or {}).get("merge_order_correct"),
3856
        "clean_detected_count": int(counts.get("clean", 0)),
3857
        "stale_detected_count": int(counts.get("stale", 0)),
3858
        "duplicate_detected_count": int(counts.get("duplicate", 0)),
3859
        "conflict_detected_count": int(counts.get("conflicting", 0)),
3860
    }
3861
3862
3863
def branch_triage_oracle(
3864
    seeded: list[dict[str, Any]],
3865
    plan: dict[str, Any],
3866
    apply_result: dict[str, Any],
3867
) -> dict[str, Any]:
3868
    expected = expected_triage_plan(seeded)
3869
    expected_survivors = sorted(
3870
        str(branch["name"])
3871
        for branch in seeded
3872
        if branch["expected_action"] == "keep_open_manual_resolution"
3873
    )
3874
    final_survivors = sorted(str(name) for name in apply_result.get("final_survivors") or [])
3875
    return {
3876
        "action_plan_correct": plan.get("actions") == expected["actions"],
3877
        "merge_order_correct": plan.get("merge_order") == expected["merge_order"],
3878
        "final_survivors_correct": final_survivors == expected_survivors,
3879
        "expected_action_plan": expected["actions"],
3880
        "expected_merge_order": expected["merge_order"],
3881
        "expected_final_survivors": expected_survivors,
3882
        "final_survivors": final_survivors,
3883
    }
3884
3885
3886
def fake_branch_triage_row(
3887
    meta: dict[str, Any],
3888
    scenario_name: str,
3889
    operation: str,
3890
    run_index: int,
3891
    elapsed_ms: float,
3892
    returncode: int = 0,
3893
    **fields: Any,
3894
) -> dict[str, Any]:
3895
    return {
3896
        **meta,
3897
        "scenario": scenario_name,
3898
        "run": run_index,
3899
        "operation": operation,
3900
        "elapsed_ms": round(elapsed_ms, 3),
3901
        "returncode": returncode,
3902
        "command": [],
3903
        "tool_call_count": 0,
3904
        "branch_triage_provider": "fake",
3905
        **fake_provider_token_fields(),
3906
        **fields,
3907
    }
3908
3909
3910
def run_branch_triage_fake_scenario(
3911
    scenario: dict[str, Any],
3912
    meta: dict[str, Any],
3913
    run_index: int,
3914
) -> list[dict[str, Any]]:
3915
    scenario_name = str(scenario["name"])
3916
    operations = [str(op) for op in scenario.get("operations") or []]
3917
    provider = FakeBranchTriageProvider(branch_triage_count(scenario))
3918
    rows: list[dict[str, Any]] = []
3919
    seeded: list[dict[str, Any]] = []
3920
    reviewed: list[dict[str, Any]] = []
3921
    plan: dict[str, Any] = {}
3922
    apply_result: dict[str, Any] = {"branches_closed": 0, "final_survivors": []}
3923
    oracle: dict[str, Any] = {}
3924
    total_start = platform_clock.monotonic_ms()
3925
3926
    for operation in operations:
3927
        start = platform_clock.monotonic_ms()
3928
        if operation == "triage.seed":
3929
            seeded = provider.seed()
3930
            rows.append(
3931
                fake_branch_triage_row(
3932
                    meta, scenario_name, operation, run_index,
3933
                    platform_clock.monotonic_ms() - start,
3934
                    branches_seeded=len(seeded),
3935
                    seeded_branch_classes=[branch["expected_class"] for branch in seeded],
3936
                    **branch_triage_metric_fields(branches_reviewed=0, branches_closed=0),
3937
                )
3938
            )
3939
        elif operation == "triage.review":
3940
            reviewed = provider.review()
3941
            rows.append(
3942
                fake_branch_triage_row(
3943
                    meta, scenario_name, operation, run_index,
3944
                    platform_clock.monotonic_ms() - start,
3945
                    branches_reviewed=len(reviewed),
3946
                    branches_closed=0,
3947
                    reviewed_branches=[branch["name"] for branch in reviewed],
3948
                )
3949
            )
3950
        elif operation == "triage.plan":
3951
            plan = branch_triage_plan(reviewed)
3952
            rows.append(
3953
                fake_branch_triage_row(
3954
                    meta, scenario_name, operation, run_index,
3955
                    platform_clock.monotonic_ms() - start,
3956
                    action_plan=plan["actions"],
3957
                    merge_order=plan["merge_order"],
3958
                    **branch_triage_metric_fields(
3959
                        branches_reviewed=len(reviewed), branches_closed=0, plan=plan
3960
                    ),
3961
                )
3962
            )
3963
        elif operation == "triage.apply":
3964
            apply_result = provider.apply(plan)
3965
            rows.append(
3966
                fake_branch_triage_row(
3967
                    meta, scenario_name, operation, run_index,
3968
                    platform_clock.monotonic_ms() - start,
3969
                    final_survivors=apply_result["final_survivors"],
3970
                    **branch_triage_metric_fields(
3971
                        branches_reviewed=len(reviewed),
3972
                        branches_closed=int(apply_result["branches_closed"]),
3973
                        plan=plan,
3974
                    ),
3975
                )
3976
            )
3977
        elif operation == "triage.oracle":
3978
            oracle = branch_triage_oracle(seeded, plan, apply_result)
3979
            ok = all(
3980
                bool(oracle[key])
3981
                for key in ("action_plan_correct", "merge_order_correct", "final_survivors_correct")
3982
            )
3983
            rows.append(
3984
                fake_branch_triage_row(
3985
                    meta, scenario_name, operation, run_index,
3986
                    platform_clock.monotonic_ms() - start,
3987
                    returncode=0 if ok else 1,
3988
                    **{
3989
                        **oracle,
3990
                        **branch_triage_metric_fields(
3991
                            branches_reviewed=len(reviewed),
3992
                            branches_closed=int(apply_result["branches_closed"]),
3993
                            plan=plan,
3994
                            oracle=oracle,
3995
                        ),
3996
                    },
3997
                )
3998
            )
3999
        elif operation == "triage.total":
4000
            failures = [
4001
                row for row in rows
4002
                if row_returncode(row) not in (0, SKIP_RETURNCODE)
4003
            ]
4004
            rows.append(
4005
                fake_branch_triage_row(
4006
                    meta, scenario_name, operation, run_index,
4007
                    platform_clock.monotonic_ms() - total_start,
4008
                    returncode=1 if failures else 0,
4009
                    summarized_operations=[row["operation"] for row in rows],
4010
                    final_survivors=apply_result.get("final_survivors", []),
4011
                    **branch_triage_metric_fields(
4012
                        branches_reviewed=len(reviewed),
4013
                        branches_closed=int(apply_result["branches_closed"]),
4014
                        plan=plan,
4015
                        oracle=oracle,
4016
                    ),
4017
                )
4018
            )
4019
        else:
4020
            rows.append(skip_row(meta, scenario_name, operation, run_index, "operation_not_implemented"))
4021
    return rows
4022
4023
4024
def run_branch_triage_real_skip(
4025
    args: argparse.Namespace,
4026
    scenario: dict[str, Any],
4027
    meta: dict[str, Any],
4028
    run_index: int,
4029
) -> list[dict[str, Any]]:
4030
    scenario_name = str(scenario["name"])
4031
    reason = f"branch_triage_real_provider_not_implemented:{args.platform}:{args.driver}"
4032
    return [
4033
        skip_row(meta, scenario_name, str(operation), run_index, reason)
4034
        for operation in scenario.get("operations") or []
4035
    ]
4036
4037
4038
def run_scenario_with_credentials(
4039
    args: argparse.Namespace,
4040
    scenario: dict[str, Any],
4041
    meta: dict[str, Any],
4042
    run_index: int,
4043
    run_root: Path,
4044
    settings: Settings,
4045
) -> list[dict[str, Any]]:
4046
    if scenario.get("name") == "platform_capability_probe":
4047
        return run_capability_probe(args, meta, run_index, settings)
4048
    if is_branch_fleet_scenario(scenario):
4049
        live_skip_reason = branch_fleet_live_skip_reason(scenario, args.driver)
4050
        if live_skip_reason is not None:
4051
            scenario_name = str(scenario["name"])
4052
            return [
4053
                skip_row(meta, scenario_name, str(operation), run_index, live_skip_reason)
4054
                for operation in scenario.get("operations") or []
4055
            ]
4056
        if args.driver == BRANCH_TRIAGE_FAKE_DRIVER:
4057
            return run_branch_fleet_fake_scenario(scenario, meta, run_index)
4058
        if args.platform == "github" and args.driver == "harness-api":
4059
            return run_github_api_branch_fleet_scenario(args, scenario, meta, run_index, settings)
4060
        if args.platform == "oak" and args.driver == "cli":
4061
            capability_skip = oak_cli_commit_push_skip_reason(cli_env(args))
4062
            if capability_skip is not None:
4063
                scenario_name = str(scenario["name"])
4064
                return [
4065
                    skip_row(meta, scenario_name, str(operation), run_index, capability_skip)
4066
                    for operation in scenario.get("operations") or []
4067
                ]
4068
            return run_oak_cli_branch_fleet_scenario(args, scenario, meta, run_index, run_root, settings)
4069
        scenario_name = str(scenario["name"])
4070
        reason = f"branch_fleet_provider_not_implemented:{args.platform}:{args.driver}"
4071
        return [
4072
            skip_row(meta, scenario_name, str(operation), run_index, reason)
4073
            for operation in scenario.get("operations") or []
4074
        ]
4075
    if is_branch_triage_scenario(scenario):
4076
        if args.driver == BRANCH_TRIAGE_FAKE_DRIVER:
4077
            return run_branch_triage_fake_scenario(scenario, meta, run_index)
4078
        return run_branch_triage_real_skip(args, scenario, meta, run_index)
4079
    if is_race_scenario(scenario):
4080
        return run_race_with_credentials(args, scenario, meta, run_index, run_root, settings)
4081
    if args.platform == "github" and args.driver == "harness-api":
4082
        return run_github_api_scenario(args, scenario, meta, run_index, settings)
4083
    if args.platform == "github":
4084
        return run_github_cli_scenario(args, scenario, meta, run_index, run_root, settings)
4085
    return run_oak_cli_scenario(args, scenario, meta, run_index, run_root, settings)
4086
4087
4088
# --------------------------------------------------------------------------
4089
# Summary + main
4090
# --------------------------------------------------------------------------
4091
4092
4093
def summary_text(rows: list[dict[str, Any]]) -> str:
4094
    lines = [
4095
        "# Platform Lane Summary",
4096
        "",
4097
        "Hosted integration workflow: task branch exists locally -> change merged",
4098
        "in main, present in local checkout. Round trips, bytes, polls, and tokens",
4099
        "are always comparable across platforms; wall clock only with per-platform",
4100
        "RTT context and an explicit caveat. Settle durations are bounded by their",
4101
        "poll quantization. This lane is excluded from devloop.",
4102
        "",
4103
        "| Platform | Driver | Scenario | Run | Operation | Wall ms | Polls | rc |",
4104
        "| --- | --- | --- | ---: | --- | ---: | ---: | ---: |",
4105
    ]
4106
    for row in rows:
4107
        if row.get("skipped"):
4108
            continue
4109
        lines.append(
4110
            "| {} | {} | {} | {} | `{}` | {} | {} | {} |".format(
4111
                row.get("platform"), row.get("driver"), row.get("scenario"), row.get("run"),
4112
                row.get("operation"), row.get("elapsed_ms"),
4113
                row.get("poll_iterations", ""), row.get("returncode"),
4114
            )
4115
        )
4116
    skips = [row for row in rows if row.get("skipped")]
4117
    if skips:
4118
        lines += ["", "## Skips", ""]
4119
        reasons: dict[str, int] = {}
4120
        for row in skips:
4121
            reasons[str(row.get("skip_reason"))] = reasons.get(str(row.get("skip_reason")), 0) + 1
4122
        for reason, count in sorted(reasons.items()):
4123
            lines.append(f"- `{reason}`: {count} row(s)")
4124
    return "\n".join(lines) + "\n"
4125
4126
4127
def main(argv: Optional[list[str]] = None) -> int:
4128
    args = parse_args(argv)
4129
    spec = load_platform_spec(args.spec)
4130
    semantics = load_semantics(args.semantics_config)
4131
    scenario = scenario_by_name(spec, args.scenario)
4132
    if scenario is None:
4133
        names = ", ".join(str(s.get("name")) for s in spec.get("scenarios") or [] if isinstance(s, dict))
4134
        print(f"unknown scenario {args.scenario!r}; known: {names}", file=sys.stderr)
4135
        return 2
4136
    declared_variants = semantics.get("protection_variants") or []
4137
    if args.protection_variant not in declared_variants:
4138
        print(
4139
            f"unknown protection variant {args.protection_variant!r}; declared: {', '.join(declared_variants)}",
4140
            file=sys.stderr,
4141
        )
4142
        return 2
4143
4144
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
4145
    settings = scenario_settings(spec, scenario, args.admitted_output_chars)
4146
    runner = runner_fields()
4147
    meta: dict[str, Any] = {
4148
        "bench_id": timestamp,
4149
        "profile": "platform",
4150
        "benchmark_track": "platform-hosted",
4151
        "timestamp_utc": timestamp,
4152
        "host": platform_module.node(),
4153
        "os_platform": platform_module.platform(),
4154
        "machine": platform_module.machine(),
4155
        "python": platform_module.python_version(),
4156
        "env_isolation_version": ENV_ISOLATION_VERSION,
4157
        "platform": args.platform,
4158
        "driver": args.driver,
4159
        "platform_semantics_class": args.semantics_class,
4160
        "protection_variant": args.protection_variant,
4161
        "platform_semantics_version": semantics.get("semantics_version"),
4162
        "platform_comparison_key": platform_comparison_key(
4163
            semantics.get("semantics_version"),
4164
            args.semantics_class,
4165
            args.protection_variant,
4166
        ),
4167
        # The contract only matches when the variant is declared+implemented
4168
        # AND this driver actually executes it (check-instant needs the
4169
        # status API, which the cli driver does not have).
4170
        "semantic_contract_match": (
4171
            compute_semantic_contract_match(
4172
                semantics, args.semantics_class, args.protection_variant
4173
            )
4174
            and protection_variant_executable(
4175
                args.platform, args.driver, args.protection_variant
4176
            )
4177
        ),
4178
        "semantics_version": semantics.get("semantics_version"),
4179
        "semantics_schema_version": semantics.get("schema_version"),
4180
        "repo": args.repo or None,
4181
        "token_env": args.token_env,
4182
        "excluded_from_devloop": True,
4183
        "clock": "time.monotonic (single local monotonic clock for all headline durations)",
4184
    }
4185
4186
    skip_reason = readiness_skip_reason(
4187
        args.platform,
4188
        args.driver,
4189
        args.repo,
4190
        args.token_env,
4191
        requires_remote=bool(scenario.get("requires_remote")),
4192
    )
4193
    if skip_reason is None and args.protection_variant not in (
4194
        semantics.get("implemented_protection_variants") or []
4195
    ):
4196
        skip_reason = f"protection_variant_not_implemented:{args.protection_variant}"
4197
4198
    operations = [str(op) for op in scenario.get("operations") or []]
4199
    scenario_name = str(scenario.get("name"))
4200
    rows: list[dict[str, Any]] = []
4201
    run_root = args.workdir / "runs" / timestamp
4202
4203
    with measurement_lock("platform_lifecycle") as lock_info:
4204
        meta["measurement_lock_wait_ms"] = lock_info.wait_ms
4205
        meta["measurement_lock"] = "held" if lock_info.enabled else "disabled"
4206
        for run_index in range(max(1, args.runs)):
4207
            if skip_reason is not None:
4208
                rows.extend(
4209
                    skip_row(
4210
                        meta,
4211
                        scenario_name,
4212
                        operation,
4213
                        run_index,
4214
                        skip_reason,
4215
                        settings=settings,
4216
                    )
4217
                    for operation in operations
4218
                )
4219
                continue
4220
            run_root.mkdir(parents=True, exist_ok=True)
4221
            print(f"[run] {scenario_name} run={run_index} platform={args.platform} driver={args.driver}", flush=True)
4222
            rows.extend(
4223
                run_scenario_with_credentials(args, scenario, meta, run_index, run_root, settings)
4224
            )
4225
4226
    for row in rows:
4227
        row.pop("_stdout", None)
4228
    stamped = [stamp_row(row, runner) for row in rows]
4229
    store = ResultsStore(args.results, lane="platform", filename_suffix="platform")
4230
    raw_path, summary_path = store.write(timestamp, stamped, summary_text(stamped))
4231
    print(f"[result] {raw_path}")
4232
    print(f"[summary] {summary_path}")
4233
    if skip_reason is None and not args.keep_workdirs:
4234
        shutil.rmtree(run_root, ignore_errors=True)
4235
4236
    if stamped and all(row.get("skipped") for row in stamped):
4237
        printed_skip_reason = skip_reason
4238
        if printed_skip_reason is None:
4239
            row_reasons = sorted(
4240
                {
4241
                    str(row.get("skip_reason"))
4242
                    for row in stamped
4243
                    if row.get("skip_reason")
4244
                }
4245
            )
4246
            printed_skip_reason = ", ".join(row_reasons) if row_reasons else None
4247
        print(f"[skip] all rows skipped: {printed_skip_reason}", file=sys.stderr)
4248
        return EXIT_ALL_SKIPPED
4249
    failures = [
4250
        row for row in stamped if row_returncode(row) not in (0, SKIP_RETURNCODE)
4251
    ]
4252
    return 1 if failures else 0
4253
4254
4255
if __name__ == "__main__":
4256
    raise SystemExit(main())