Viewing files at commit d234051be762Commit detailsView latest

Browse files
scripts/task_loop.py 2688 lines · 108.6 KB · python
1
#!/usr/bin/env python3
2
"""The full task loop, end to end — the product's signature motion.
3
4
oak:  mount -> edit -> commit -> push -> desc -> end, then REMOUNT for a
5
      follow-up task (the warm-cache story) and repeat edit/commit/push/end.
6
git:  worktree add -> branch edit -> add/commit -> push -> worktree remove,
7
      then a second worktree for the follow-up. The task description lives in
8
      the commit message git already paid for; oak pays a separate `desc`
9
      call. That asymmetry is the measurement, not a harness gap
10
      (docs/command-semantics.md): this is agent-default-track, whole-task
11
      evidence, never a step-by-step equivalence claim.
12
13
Transport honesty: oak rows always cross the network to a real oak server.
14
git runs against GitHub (--git-remote github, transport-comparable) or a
15
local bare repo (--git-remote local, the core-equivalent track). Rows carry
16
remote_transport/remote_server; latency comparisons across transports are
17
forbidden — cross-subject evidence at unlike transports is tool calls and
18
tokens only.
19
20
Setup:
21
  OAK_BENCH_SYNC_REPO=oak/bench-sync-tmp \\
22
  [email protected]:oakdotspace/bench-task-loop-tmp.git \\
23
  python3 scripts/task_loop.py --runs 3
24
25
Unconfigured remotes emit skip rows (returncode 77), never silent absence.
26
"""
27
28
from __future__ import annotations
29
30
import argparse
31
import hashlib
32
import json
33
import os
34
import platform
35
import random
36
import re
37
import shutil
38
import socket
39
import sqlite3
40
import stat
41
import subprocess
42
import tempfile
43
import threading
44
import time
45
import urllib.error
46
import urllib.parse
47
import urllib.request
48
from datetime import datetime, timezone
49
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
50
from pathlib import Path
51
from typing import Any
52
53
from oakbench import remotes as oakbench_remotes
54
from oakbench import tokens as oakbench_tokens
55
from oakbench.byteproxy import CountingProxy
56
from oakbench.diskprobe import bounded_tree_usage
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, stop_owned_process
60
from oakbench.results import ResultsStore
61
from oakbench.rows import row_returncode
62
from oakbench.runlock import measurement_lock
63
from oakbench.subjects import (
64
    DEFAULT_OAK_REPO,
65
    Subject,
66
    binary_sha256,
67
    load_subjects,
68
    source_metadata,
69
    subject_details,
70
    subject_version,
71
    subject_versions,
72
)
73
74
ROOT = Path(__file__).resolve().parents[1]
75
DEFAULT_WORKDIR = Path(tempfile.gettempdir()) / "oak-task-loop"
76
SKIP_RETURNCODE = 77
77
VIRTUAL_BRANCH_RE = re.compile(r"virtual branch ([^\s)]+)")
78
PHASES = ("task", "followup")
79
TASK_LOOP_SCENARIO = "task_loop"
80
AGENT_TASK_SCENARIO = "agent_task_baseline_v1"
81
AGENT_TASK_OAK_LOCAL_SCENARIO = "agent_task_oak_local_v1"
82
INTERRUPTED_PUBLICATION_SCENARIO = "interrupted_publication_after_request_v1"
83
LEGACY_SELECTED_MATERIALIZATION_SCENARIO = "legacy_selected_clone_materialization_v1"
84
INTERRUPTED_PUBLICATION_REQUEST_TIMEOUT_S = 2.0
85
INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S = 30.0
86
INTERRUPTED_PUBLICATION_MAX_REQUEST_BYTES = 64 * 1024 * 1024
87
LOCAL_OAK_SERVER_READY_TIMEOUT_S = 10.0
88
89
90
def legacy_selected_receipt_evidence(receipt: dict[str, Any]) -> dict[str, Any]:
91
    result = receipt.get("result") if isinstance(receipt.get("result"), dict) else {}
92
    observed = receipt.get("observed") if isinstance(receipt.get("observed"), dict) else {}
93
    phases = observed.get("pull_phases") if isinstance(observed.get("pull_phases"), list) else []
94
    phase_names = [phase.get("name") for phase in phases if isinstance(phase, dict)]
95
    materialization = (
96
        observed.get("materialization")
97
        if isinstance(observed.get("materialization"), dict)
98
        else {}
99
    )
100
    return {
101
        "legacy_route_observed": phase_names == ["initial", "legacy_selected_branch"],
102
        "pull_phase_names": phase_names,
103
        "pull_phases": phases,
104
        "materialization_passes_observed": materialization.get("passes_observed"),
105
        "written_paths": materialization.get("written_paths"),
106
        "snapshot_bound": result.get("snapshot_bound"),
107
        "head": result.get("head"),
108
        "manifest": result.get("manifest"),
109
    }
110
111
112
def legacy_selected_receipt_contract(evidence: dict[str, Any]) -> dict[str, Any]:
113
    phases = []
114
    for phase in evidence.get("pull_phases", []):
115
        if not isinstance(phase, dict):
116
            continue
117
        phases.append(
118
            {
119
                key: value
120
                for key, value in phase.items()
121
                if key not in {"materialization_passes"}
122
            }
123
        )
124
    return {
125
        "legacy_route_observed": evidence.get("legacy_route_observed"),
126
        "pull_phases": phases,
127
        "snapshot_bound": evidence.get("snapshot_bound"),
128
        "head": evidence.get("head"),
129
        "manifest": evidence.get("manifest"),
130
        "written_paths": evidence.get("written_paths"),
131
    }
132
133
134
def normalized_cache_contract(snapshot: dict[str, Any] | None) -> list[dict[str, Any]] | None:
135
    if not isinstance(snapshot, dict) or not isinstance(snapshot.get("rows"), list):
136
        return None
137
    return [
138
        {key: row.get(key) for key in ("path", "size", "blob_hash")}
139
        for row in snapshot["rows"]
140
        if isinstance(row, dict)
141
    ]
142
143
144
def normalized_status(value: dict[str, Any] | list[Any] | None) -> dict[str, Any] | None:
145
    if not isinstance(value, dict):
146
        return None
147
    return {
148
        key: value.get(key)
149
        for key in ("branch", "parent", "branch_status", "head", "working_changes", "branch_changes")
150
    }
151
152
153
def materialization_timing_controls_met(
154
    metadata: dict[str, Any], runs: int, randomize_order: bool, controls_requested: bool
155
) -> bool:
156
    return bool(
157
        controls_requested
158
        and metadata.get("measurement_lock") == "held"
159
        and randomize_order
160
        and runs >= 10
161
    )
162
163
164
def isolated_oak_fixture_env(task_config_root: Path) -> dict[str, str]:
165
    task_config_root.mkdir(mode=0o700, parents=True, exist_ok=True)
166
    retained = {
167
        "PATH",
168
        "TMPDIR",
169
        "TMP",
170
        "TEMP",
171
        "HOME",
172
        "USER",
173
        "USERNAME",
174
        "SHELL",
175
        "SYSTEMROOT",
176
        "WINDIR",
177
        "COMSPEC",
178
        "PATHEXT",
179
        "SSL_CERT_FILE",
180
        "SSL_CERT_DIR",
181
    }
182
    env = {key: value for key, value in os.environ.items() if key in retained}
183
    env.update(
184
        {
185
            "GIT_AUTHOR_NAME": "Oak Materialization Bench",
186
            "GIT_AUTHOR_EMAIL": "[email protected]",
187
            "GIT_COMMITTER_NAME": "Oak Materialization Bench",
188
            "GIT_COMMITTER_EMAIL": "[email protected]",
189
            "GIT_CONFIG_GLOBAL": os.devnull,
190
            "GIT_CONFIG_NOSYSTEM": "1",
191
            "OAK_AUTHOR": "oak-materialization-bench",
192
            "OAK_NO_UPDATE_CHECK": "1",
193
            "OAK_BENCH_ENV_ISOLATION_VERSION": ENV_ISOLATION_VERSION,
194
            "NO_COLOR": "1",
195
            "CLICOLOR": "0",
196
            "LC_ALL": "C",
197
            "LANG": "C",
198
            "TZ": "UTC",
199
            "GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z",
200
            "GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z",
201
        }
202
    )
203
    return env
204
205
206
def normalized_worktree_snapshot(root: Path) -> list[dict[str, Any]]:
207
    rows: list[dict[str, Any]] = []
208
    for path in sorted(root.rglob("*")):
209
        relative = path.relative_to(root)
210
        if relative.parts and relative.parts[0] in {".oak", ".git"}:
211
            continue
212
        info = path.lstat()
213
        if stat.S_ISDIR(info.st_mode):
214
            continue
215
        row: dict[str, Any] = {
216
            "path": relative.as_posix(),
217
            "mode": stat.S_IMODE(info.st_mode),
218
        }
219
        if stat.S_ISLNK(info.st_mode):
220
            row.update({"kind": "symlink", "target": os.readlink(path)})
221
        else:
222
            row.update({"kind": "file", "sha256": hashlib.sha256(path.read_bytes()).hexdigest()})
223
        rows.append(row)
224
    return rows
225
226
227
def json_command(
228
    command: list[str], cwd: Path, env: dict[str, str]
229
) -> dict[str, Any] | list[Any] | None:
230
    result = run_bounded_harness_command(command, cwd, env=env)
231
    if result.returncode != 0:
232
        return None
233
    try:
234
        value = json.loads(result.stdout)
235
    except (TypeError, ValueError):
236
        return None
237
    return value if isinstance(value, (dict, list)) else None
238
239
240
def normalized_history(value: dict[str, Any] | list[Any] | None) -> list[dict[str, Any]] | None:
241
    if not isinstance(value, list):
242
        return None
243
    rows: list[dict[str, Any]] = []
244
    for item in value:
245
        if not isinstance(item, dict):
246
            return None
247
        rows.append(
248
            {
249
                key: item.get(key)
250
                for key in (
251
                    "hash",
252
                    "timestamp",
253
                    "branch",
254
                    "description_or_subject",
255
                    "files_changed",
256
                )
257
            }
258
        )
259
    return rows
260
261
262
def normalized_oak_artifacts(root: Path) -> list[str] | None:
263
    oak_dir = root / ".oak"
264
    if not oak_dir.is_dir():
265
        return None
266
    return sorted(
267
        path.relative_to(oak_dir).as_posix() + ("/" if path.is_dir() else "")
268
        for path in oak_dir.rglob("*")
269
    )
270
271
272
def stat_cache_snapshot(root: Path) -> dict[str, Any] | None:
273
    db = root / ".oak/oak.db"
274
    if not db.is_file():
275
        return {"rows": None, "all_metadata_valid": False, "error": "oak_db_missing"}
276
    try:
277
        # Read-only URI: never create -wal/-journal side files in the subject's
278
        # repository (an empty -wal would block the independent reader).
279
        connection = sqlite3.connect(db.resolve().as_uri() + "?mode=ro", uri=True)
280
        connection.execute("PRAGMA query_only = ON")
281
        raw_rows = connection.execute(
282
            "SELECT path, mtime_ns, ctime_ns, size, blob_hash FROM stat_cache ORDER BY path"
283
        ).fetchall()
284
    except sqlite3.Error as error:
285
        return {"rows": None, "all_metadata_valid": False, "error": str(error)}
286
    finally:
287
        if "connection" in locals():
288
            connection.close()
289
    rows: list[dict[str, Any]] = []
290
    valid = True
291
    for path, mtime_ns, ctime_ns, size, blob_hash in raw_rows:
292
        file_path = root / path
293
        try:
294
            info = file_path.stat()
295
        except OSError:
296
            valid = False
297
            continue
298
        expected_ctime = info.st_ctime_ns if os.name == "posix" else 0
299
        metadata_valid = (
300
            int(size) == info.st_size
301
            and int(mtime_ns) == info.st_mtime_ns
302
            and int(ctime_ns) == expected_ctime
303
        )
304
        valid = valid and metadata_valid
305
        rows.append(
306
            {
307
                "path": str(path),
308
                "size": int(size),
309
                "blob_hash": str(blob_hash),
310
                "metadata_valid": metadata_valid,
311
            }
312
        )
313
    return {"rows": rows, "all_metadata_valid": valid}
314
315
316
def base_env() -> dict[str, str]:
317
    return oakbench_base_env(
318
        author_name="Oak Task Loop",
319
        author_email="[email protected]",
320
        oak_author="oak-task-loop",
321
    )
322
323
324
def parse_args() -> argparse.Namespace:
325
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
326
    parser.add_argument("--subjects", help="Comma-separated subject names from config/subjects.toml")
327
    parser.add_argument("--config", type=Path, default=ROOT / "config" / "subjects.toml")
328
    parser.add_argument("--git-bin", type=Path)
329
    parser.add_argument("--oak-installed-bin", type=Path)
330
    parser.add_argument("--oak-local-bin", type=Path)
331
    parser.add_argument(
332
        "--oak-serve-bin",
333
        type=Path,
334
        help="Immutable Oak binary used only as the fixed loopback server and fixture writer.",
335
    )
336
    parser.add_argument(
337
        "--oak-serve-source",
338
        type=Path,
339
        help="Read-only source checkout that produced --oak-serve-bin.",
340
    )
341
    parser.add_argument(
342
        "--oak-serve-source-head",
343
        help="Exact 64-hex Oak source head that produced --oak-serve-bin.",
344
    )
345
    parser.add_argument(
346
        "--materialization-baseline-subject",
347
        help="Named control subject required by the legacy materialization scenario.",
348
    )
349
    parser.add_argument(
350
        "--materialization-timing-controls-met",
351
        action="store_true",
352
        help=(
353
            "Record that harness-verifiable controls are met only with a held measurement lock, "
354
            "randomized subject order, and at least 10 runs. This is not a speedup claim: "
355
            "separate A/A noise and externally verified quiet-host evidence remain required."
356
        ),
357
    )
358
    parser.add_argument("--runs", type=int, default=3)
359
    parser.add_argument(
360
        "--scenarios",
361
        default=TASK_LOOP_SCENARIO,
362
        help=(
363
            "Comma-separated scenarios: task_loop, agent_task_baseline_v1, "
364
            "agent_task_oak_local_v1, legacy_selected_clone_materialization_v1, "
365
            "and/or interrupted_publication_after_request_v1. "
366
            "The interruption "
367
            "scenario is a loopback-only Oak fixture and never contacts a remote service."
368
        ),
369
    )
370
    parser.add_argument(
371
        "--git-remote",
372
        choices=("github", "local"),
373
        default="github",
374
        help=(
375
            "github: agent-default track, real push to GIT_BENCH_REMOTE "
376
            "(transport-comparable to oak). local: core-equivalent track, "
377
            "local bare remote (no network in git's loop)."
378
        ),
379
    )
380
    parser.add_argument("--workdir", type=Path, default=DEFAULT_WORKDIR)
381
    parser.add_argument("--results", type=Path, default=ROOT / "results" / "task-loop")
382
    parser.add_argument("--oak-repo", type=Path, default=DEFAULT_OAK_REPO)
383
    parser.add_argument("--admitted-output-chars", type=int, default=20_000)
384
    parser.add_argument("--keep-workdirs", action="store_true")
385
    parser.add_argument(
386
        "--randomize-subject-order",
387
        action="store_true",
388
        help="Shuffle subject order independently in each run using --random-seed.",
389
    )
390
    parser.add_argument("--random-seed", type=int, default=20260908)
391
    return parser.parse_args()
392
393
394
def run_untimed(command: list[str], cwd: Path | None, timeout: float = 300.0) -> subprocess.CompletedProcess[str]:
395
    return subprocess.run(
396
        command,
397
        cwd=cwd,
398
        env=base_env(),
399
        text=True,
400
        stdout=subprocess.PIPE,
401
        stderr=subprocess.PIPE,
402
        timeout=timeout,
403
        check=False,
404
    )
405
406
407
def step_row(
408
    subject: Subject,
409
    scenario: str,
410
    operation: str,
411
    command: list[str],
412
    cwd: Path,
413
    run_index: int,
414
    metadata: dict[str, Any],
415
    admitted_output_chars: int,
416
    *,
417
    kind: str = "vcs",
418
    expected_returncodes: tuple[int, ...] = (0,),
419
    preserve_stdout: bool = False,
420
    preserve_stderr: bool = False,
421
    env: dict[str, str] | None = None,
422
    timeout_seconds: float | None = None,
423
) -> dict[str, Any]:
424
    capture = run_timed(
425
        command,
426
        cwd,
427
        env or base_env(),
428
        admitted_output_chars,
429
        timeout_seconds=timeout_seconds,
430
    )
431
    expected = not capture.timed_out and capture.returncode in expected_returncodes
432
    is_vcs = 1 if kind == "vcs" else 0
433
    row = {
434
        **metadata,
435
        "subject": subject.name,
436
        "subject_kind": subject.kind,
437
        "subject_label": subject.label,
438
        "scenario": scenario,
439
        "run": run_index,
440
        "operation": operation,
441
        "step_kind": kind,
442
        "elapsed_ms": round(capture.elapsed_ms, 3),
443
        "returncode": 0 if expected else (capture.returncode or 1),
444
        "process_returncode": capture.returncode,
445
        "timed_out": capture.timed_out,
446
        "expected_returncodes": list(expected_returncodes),
447
        "command": command,
448
        "tool_call_count": 1,
449
        "terminal_tool_call_count": 1,
450
        "vcs_tool_call_count": is_vcs,
451
        **oakbench_tokens.interaction_token_fields(
452
            command_display(command),
453
            capture.stdout_text,
454
            capture.stderr_text,
455
            capture.stdout_bytes,
456
            capture.stderr_bytes,
457
            capture.stdout_truncated,
458
            capture.stderr_truncated,
459
        ),
460
    }
461
    if not expected:
462
        row["stderr"] = capture.stderr_text[-4000:]
463
    if preserve_stdout:
464
        row["stdout"] = capture.stdout_text
465
    if preserve_stderr:
466
        row["stderr"] = capture.stderr_text
467
    return row
468
469
470
def skip_row(
471
    subject: Subject,
472
    scenario: str,
473
    run_index: int,
474
    metadata: dict[str, Any],
475
    reason: str,
476
) -> dict[str, Any]:
477
    return {
478
        **metadata,
479
        "subject": subject.name,
480
        "subject_kind": subject.kind,
481
        "subject_label": subject.label,
482
        "scenario": scenario,
483
        "run": run_index,
484
        "operation": "task_loop.skipped",
485
        "step_kind": "summary",
486
        "elapsed_ms": 0.0,
487
        "returncode": SKIP_RETURNCODE,
488
        "command": [],
489
        "skipped": True,
490
        "skip_reason": reason,
491
        "tool_call_count": 0,
492
        "terminal_tool_call_count": 0,
493
        "vcs_tool_call_count": 0,
494
    }
495
496
497
def unmeasured_step_row(
498
    subject: Subject,
499
    scenario: str,
500
    operation: str,
501
    run_index: int,
502
    metadata: dict[str, Any],
503
    reason: str,
504
) -> dict[str, Any]:
505
    row = skip_row(subject, scenario, run_index, metadata, reason)
506
    row["operation"] = operation
507
    return row
508
509
510
def harness_step_row(
511
    subject: Subject,
512
    scenario: str,
513
    operation: str,
514
    run_index: int,
515
    metadata: dict[str, Any],
516
    elapsed_ms: float,
517
    *,
518
    returncode: int = 0,
519
    harness_command_count: int = 0,
520
) -> dict[str, Any]:
521
    return {
522
        **metadata,
523
        "subject": subject.name,
524
        "subject_kind": subject.kind,
525
        "subject_label": subject.label,
526
        "scenario": scenario,
527
        "run": run_index,
528
        "operation": operation,
529
        "step_kind": "harness",
530
        "elapsed_ms": round(elapsed_ms, 3),
531
        "returncode": returncode,
532
        "command": [],
533
        "tool_call_count": 0,
534
        "terminal_tool_call_count": 0,
535
        "vcs_tool_call_count": 0,
536
        "harness_command_count": harness_command_count,
537
        "stdout_bytes": 0,
538
        "stderr_bytes": 0,
539
        "retry_count": 0,
540
        "human_action_count": 0,
541
    }
542
543
544
def phase_total_row(
545
    rows: list[dict[str, Any]],
546
    subject: Subject,
547
    scenario: str,
548
    run_index: int,
549
    metadata: dict[str, Any],
550
    operation: str,
551
) -> dict[str, Any]:
552
    failures = [row for row in rows if row_returncode(row) not in (0, SKIP_RETURNCODE)]
553
    unmeasured = [
554
        str(row["operation"])
555
        for row in rows
556
        if row_returncode(row) == SKIP_RETURNCODE
557
    ]
558
    phase_elapsed_ms: dict[str, float] = {}
559
    for row in rows:
560
        phase = str(row.get("operation", "unknown")).split(".", 1)[0]
561
        phase_elapsed_ms[phase] = phase_elapsed_ms.get(phase, 0.0) + float(
562
            row.get("elapsed_ms", 0.0)
563
        )
564
    return {
565
        **metadata,
566
        "subject": subject.name,
567
        "subject_kind": subject.kind,
568
        "subject_label": subject.label,
569
        "scenario": scenario,
570
        "run": run_index,
571
        "operation": operation,
572
        "step_kind": "summary",
573
        "elapsed_ms": round(sum(float(row.get("elapsed_ms", 0)) for row in rows), 3),
574
        "returncode": 1 if failures else 0,
575
        "coverage_complete": not unmeasured,
576
        "unmeasured_operations": unmeasured,
577
        "command": [row["command"] for row in rows],
578
        "tool_call_count": sum(oakbench_tokens.int_or_zero(row.get("tool_call_count")) for row in rows),
579
        "terminal_tool_call_count": sum(
580
            oakbench_tokens.int_or_zero(row.get("terminal_tool_call_count")) for row in rows
581
        ),
582
        "vcs_tool_call_count": sum(oakbench_tokens.int_or_zero(row.get("vcs_tool_call_count")) for row in rows),
583
        "agent_tool_call_count": sum(
584
            oakbench_tokens.int_or_zero(row.get("tool_call_count")) for row in rows
585
        ),
586
        "harness_command_count": sum(
587
            oakbench_tokens.int_or_zero(row.get("harness_command_count")) for row in rows
588
        ),
589
        "retry_count": sum(oakbench_tokens.int_or_zero(row.get("retry_count")) for row in rows),
590
        "human_action_count": sum(
591
            oakbench_tokens.int_or_zero(row.get("human_action_count")) for row in rows
592
        ),
593
        "phase_elapsed_ms": {
594
            phase: round(elapsed, 3) for phase, elapsed in sorted(phase_elapsed_ms.items())
595
        },
596
        **oakbench_tokens.summed_token_fields(
597
            rows, "sum_of_task_loop_steps_command_plus_admitted_output_chars_div_4"
598
        ),
599
        "summarized_operations": [row["operation"] for row in rows],
600
    }
601
602
603
def has_lane_failures(rows: list[dict[str, Any]]) -> bool:
604
    return any(row_returncode(row) not in (0, SKIP_RETURNCODE) for row in rows)
605
606
607
def enforce_oracle_result(total: dict[str, Any], oracle_ok: bool | None) -> dict[str, Any]:
608
    if oracle_ok is False:
609
        total["returncode"] = 1
610
        total["stderr"] = "oracle_payload_missing_from_remote_branch"
611
    return total
612
613
614
def interrupted_publication_fields(
615
    *,
616
    request_count: int,
617
    request_bytes: int | None,
618
    declared_request_bytes: int | None,
619
    request_framing_valid: bool,
620
    request_body_timed_out: bool,
621
    local_head_before: str | None,
622
    local_head_after: str | None,
623
    status_after: dict[str, Any] | None,
624
    worktree_payload_before: str | None,
625
    worktree_payload_after: str | None,
626
    cli_observation: dict[str, str],
627
) -> dict[str, Any]:
628
    """Evidence fields for a single interrupted mutation attempt.
629
630
    The fixture's receipt of a full request is ground truth for the harness,
631
    not a server durability receipt available to the CLI.  A disconnect after
632
    that point is therefore unknown to the client and must never imply replay.
633
    """
634
    request_complete = bool(
635
        request_count == 1
636
        and request_framing_valid
637
        and declared_request_bytes is not None
638
        and request_bytes == declared_request_bytes
639
    )
640
    if request_count == 0:
641
        delivery_state = "no_request_observed"
642
        fixture_outcome = "not_sent_observed"
643
    elif not request_framing_valid:
644
        delivery_state = "invalid_request_framing"
645
        fixture_outcome = "indeterminate_after_invalid_request_disconnect"
646
    elif request_complete:
647
        delivery_state = "complete_request_observed"
648
        fixture_outcome = "unknown_after_complete_request_disconnect"
649
    else:
650
        delivery_state = "partial_request_observed"
651
        fixture_outcome = "indeterminate_after_partial_request_disconnect"
652
    payload_preserved = bool(
653
        worktree_payload_before
654
        and worktree_payload_before == worktree_payload_after
655
    )
656
    unpublished_state_preserved = bool(status_after and status_after.get("needs_push"))
657
    return {
658
        "publication_outcome": cli_observation["reported_outcome"],
659
        "cli_observation": cli_observation["observation"],
660
        "fixture_delivery_state": delivery_state,
661
        "fixture_outcome_knowledge": fixture_outcome,
662
        "fixture_received_complete_request_before_disconnect": request_complete,
663
        "fixture_request_framing_valid": request_framing_valid,
664
        "fixture_request_body_timed_out": request_body_timed_out,
665
        "fixture_declared_request_bytes": declared_request_bytes,
666
        "mutation_requests_observed": request_count,
667
        "blind_replay_requests": max(0, request_count - 1),
668
        "network_request_bytes_observed": request_bytes,
669
        "local_head_preserved": bool(local_head_before) and local_head_before == local_head_after,
670
        "worktree_payload_preserved": payload_preserved,
671
        "unpublished_state_preserved": unpublished_state_preserved,
672
        "local_work_preserved": payload_preserved and unpublished_state_preserved,
673
        "working_tree_dirty_after": None if status_after is None else status_after.get("dirty"),
674
    }
675
676
677
def cli_publication_observation(row: dict[str, Any], *, json_requested: bool) -> dict[str, str]:
678
    """Classify only what the CLI reported, never what the fault fixture knows."""
679
    if row.get("timed_out"):
680
        return {"reported_outcome": "unreported", "observation": "process_timeout"}
681
    stdout = str(row.get("stdout") or "")
682
    stderr = str(row.get("stderr") or "")
683
    value = _parse_json_object(stdout) if json_requested else None
684
    if value is not None and row.get("process_returncode") == 0 and value.get("published") is True:
685
        return {"reported_outcome": "published", "observation": "json_success_receipt"}
686
    if value is not None and isinstance(value.get("error"), dict):
687
        message = str(value["error"].get("message") or "").lower()
688
        if any(marker in message for marker in ("unconfirmed", "unknown", "may have")):
689
            return {"reported_outcome": "unknown", "observation": "json_error_unknown"}
690
        return {
691
            "reported_outcome": "unreported",
692
            "observation": "json_error_without_publication_outcome",
693
        }
694
    message = f"{stdout}\n{stderr}".lower()
695
    if any(marker in message for marker in ("unconfirmed", "unknown", "may have")):
696
        return {"reported_outcome": "unknown", "observation": "text_error_unknown"}
697
    return {"reported_outcome": "unreported", "observation": "text_error_without_publication_outcome"}
698
699
700
def file_sha256(path: Path) -> str | None:
701
    try:
702
        return hashlib.sha256(path.read_bytes()).hexdigest()
703
    except OSError:
704
        return None
705
706
707
class _DisconnectAfterPushHandler(BaseHTTPRequestHandler):
708
    """Minimal loopback fixture for a complete request with a lost reply."""
709
710
    server_version = "oakbench-interruption/1"
711
712
    def log_message(self, _format: str, *_args: object) -> None:
713
        return
714
715
    def _json(self, status: int, payload: dict[str, Any]) -> None:
716
        encoded = json.dumps(payload).encode()
717
        self.send_response(status)
718
        self.send_header("Content-Type", "application/json")
719
        self.send_header("Content-Length", str(len(encoded)))
720
        self.end_headers()
721
        self.wfile.write(encoded)
722
723
    def do_GET(self) -> None:  # noqa: N802 - BaseHTTPRequestHandler API
724
        self._json(404, {"error": "absent synthetic repo"})
725
726
    def do_POST(self) -> None:  # noqa: N802 - BaseHTTPRequestHandler API
727
        declared_size: int | None = None
728
        framing_valid = False
729
        body = b""
730
        body_timed_out = False
731
        raw_sizes = self.headers.get_all("Content-Length", failobj=[])
732
        try:
733
            raw_size = raw_sizes[0].strip(" \t") if len(raw_sizes) == 1 else ""
734
            parsed_size = int(raw_size) if raw_size.isascii() and raw_size.isdecimal() else -1
735
            framing_valid = (
736
                0 <= parsed_size <= INTERRUPTED_PUBLICATION_MAX_REQUEST_BYTES
737
                and self.headers.get("Transfer-Encoding") is None
738
            )
739
            if framing_valid:
740
                declared_size = parsed_size
741
                self.connection.settimeout(INTERRUPTED_PUBLICATION_REQUEST_TIMEOUT_S)
742
                while len(body) < parsed_size:
743
                    try:
744
                        chunk = self.rfile.read1(parsed_size - len(body))
745
                    except (socket.timeout, TimeoutError):
746
                        body_timed_out = True
747
                        break
748
                    except OSError:
749
                        break
750
                    if not chunk:
751
                        break
752
                    body += chunk
753
        except ValueError:
754
            framing_valid = False
755
        server = self.server
756
        if self.path == "/api/repos":
757
            self._json(200, {"name": "repo"})
758
            return
759
        if self.path == "/api/oak/repo/push":
760
            with server.receipt_lock:  # type: ignore[attr-defined]
761
                server.push_request_count += 1  # type: ignore[attr-defined]
762
                server.push_request_bytes += len(body)  # type: ignore[attr-defined]
763
                server.push_declared_request_bytes = declared_size  # type: ignore[attr-defined]
764
                server.push_request_framing_valid = framing_valid  # type: ignore[attr-defined]
765
                server.push_request_body_timed_out = body_timed_out  # type: ignore[attr-defined]
766
                server.request_complete_before_disconnect = (  # type: ignore[attr-defined]
767
                    framing_valid and len(body) == declared_size
768
                )
769
            self.close_connection = True
770
            try:
771
                self.connection.shutdown(socket.SHUT_RDWR)
772
            except OSError:
773
                pass
774
            self.connection.close()
775
            return
776
        self._json(404, {"error": "unexpected synthetic path"})
777
778
779
def start_interrupted_publication_server() -> tuple[ThreadingHTTPServer, threading.Thread]:
780
    server = ThreadingHTTPServer(("127.0.0.1", 0), _DisconnectAfterPushHandler)
781
    server.receipt_lock = threading.Lock()  # type: ignore[attr-defined]
782
    server.push_request_count = 0  # type: ignore[attr-defined]
783
    server.push_request_bytes = 0  # type: ignore[attr-defined]
784
    server.push_declared_request_bytes = None  # type: ignore[attr-defined]
785
    server.push_request_framing_valid = False  # type: ignore[attr-defined]
786
    server.push_request_body_timed_out = False  # type: ignore[attr-defined]
787
    server.request_complete_before_disconnect = False  # type: ignore[attr-defined]
788
    thread = threading.Thread(target=server.serve_forever, name="oakbench-interrupted-push", daemon=True)
789
    thread.start()
790
    return server, thread
791
792
793
def _parse_json_object(text: str | None) -> dict[str, Any] | None:
794
    try:
795
        value = json.loads(text or "")
796
    except (TypeError, json.JSONDecodeError):
797
        return None
798
    return value if isinstance(value, dict) else None
799
800
801
def run_interrupted_publication(
802
    subject: Subject,
803
    run_root: Path,
804
    run_index: int,
805
    metadata: dict[str, Any],
806
    admitted: int,
807
) -> list[dict[str, Any]]:
808
    """Run one real Oak push against a loopback server that drops its reply.
809
810
    The fixture observes request delivery but deliberately has no durable
811
    remote state. That makes the client's outcome unknown and lets the lane
812
    prove work preservation and absence of blind replay without production
813
    mutations or a fake success claim.
814
    """
815
    scenario = INTERRUPTED_PUBLICATION_SCENARIO
816
    metadata = {
817
        **metadata,
818
        "benchmark_track": "failure-evidence",
819
        "remote_transport": "loopback_fault_fixture",
820
        "remote_server": "127.0.0.1",
821
    }
822
    if subject.kind != "oak":
823
        return [
824
            skip_row(
825
                subject,
826
                scenario,
827
                run_index,
828
                metadata,
829
                "interrupted publication fixture currently exercises the actual Oak HTTP client only",
830
            )
831
        ]
832
833
    workspace = run_root / f"interrupted-{subject.name}-r{run_index}"
834
    workspace.mkdir(parents=True)
835
    rows: list[dict[str, Any]] = []
836
    vcs = str(subject.bin)
837
    rows.append(
838
        step_row(subject, scenario, "setup.init", [vcs, "init", "."], workspace, run_index, metadata, admitted)
839
    )
840
    if row_returncode(rows[-1]) != 0:
841
        return rows
842
    rows.append(
843
        edit_step(
844
            subject,
845
            scenario,
846
            "setup.edit",
847
            workspace,
848
            f"interrupted publication run={run_index}",
849
            run_index,
850
            metadata,
851
            admitted,
852
        )
853
    )
854
    rows.append(
855
        step_row(subject, scenario, "setup.checkpoint", [vcs, "commit"], workspace, run_index, metadata, admitted)
856
    )
857
    if row_returncode(rows[-1]) != 0:
858
        return rows
859
860
    before = run_untimed([vcs, "hash"], workspace)
861
    head_before = before.stdout.strip() if before.returncode == 0 else None
862
    payload_path = workspace / "task-notes.md"
863
    payload_before = file_sha256(payload_path)
864
    before_usage = bounded_tree_usage(workspace)
865
    push_help = run_untimed([vcs, "push", "--help"], workspace)
866
    push_supports_json = "--json" in (push_help.stdout + push_help.stderr)
867
    server, thread = start_interrupted_publication_server()
868
    host, port = server.server_address[:2]
869
    remote = f"http://{host}:{port}"
870
    fixture_env = base_env()
871
    fixture_env.pop("OAK_API_KEY", None)
872
    fixture_env.pop("OAK_REMOTE", None)
873
    fixture_env["OAK_NO_UPDATE_CHECK"] = "1"
874
    try:
875
        push_command = [vcs, "push", "--repo", "oak/repo", "--remote", remote]
876
        if push_supports_json:
877
            push_command.append("--json")
878
        publication_attempt = step_row(
879
            subject,
880
            scenario,
881
            "publication.attempt",
882
            push_command,
883
            workspace,
884
            run_index,
885
            metadata,
886
            admitted,
887
            expected_returncodes=tuple(range(1, 128)),
888
            preserve_stdout=True,
889
            preserve_stderr=True,
890
            env=fixture_env,
891
            timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
892
        )
893
        rows.append(publication_attempt)
894
    finally:
895
        server.shutdown()
896
        server.server_close()
897
        thread.join(timeout=2.0)
898
899
    state_row = step_row(
900
        subject,
901
        scenario,
902
        "reconcile.local_state",
903
        [vcs, "agent", "state", "--json", "--compact"],
904
        workspace,
905
        run_index,
906
        metadata,
907
        admitted,
908
        preserve_stdout=True,
909
        env=fixture_env,
910
    )
911
    rows.append(state_row)
912
    after = run_untimed([vcs, "hash"], workspace)
913
    head_after = after.stdout.strip() if after.returncode == 0 else None
914
    payload_after = file_sha256(payload_path)
915
    status_after = _parse_json_object(state_row.get("stdout"))
916
    with server.receipt_lock:  # type: ignore[attr-defined]
917
        request_count = int(server.push_request_count)  # type: ignore[attr-defined]
918
        request_bytes = int(server.push_request_bytes)  # type: ignore[attr-defined]
919
        declared_request_bytes = server.push_declared_request_bytes  # type: ignore[attr-defined]
920
        request_framing_valid = bool(server.push_request_framing_valid)  # type: ignore[attr-defined]
921
        request_body_timed_out = bool(server.push_request_body_timed_out)  # type: ignore[attr-defined]
922
    cli_observation = cli_publication_observation(
923
        publication_attempt,
924
        json_requested=push_supports_json,
925
    )
926
    evidence = interrupted_publication_fields(
927
        request_count=request_count,
928
        request_bytes=request_bytes if request_count else None,
929
        declared_request_bytes=declared_request_bytes,
930
        request_framing_valid=request_framing_valid,
931
        request_body_timed_out=request_body_timed_out,
932
        local_head_before=head_before,
933
        local_head_after=head_after,
934
        status_after=status_after,
935
        worktree_payload_before=payload_before,
936
        worktree_payload_after=payload_after,
937
        cli_observation=cli_observation,
938
    )
939
    evidence["workspace_usage_before"] = before_usage
940
    evidence["workspace_usage_after"] = bounded_tree_usage(workspace)
941
    evidence["remote_transport"] = "loopback_fault_fixture"
942
    evidence["remote_server"] = str(host)
943
    evidence["human_action_count"] = 0
944
    evidence["compatibility_probe_calls"] = 1
945
    evidence["harness_probe_calls"] = 3
946
    evidence["push_json_supported"] = push_supports_json
947
    total = phase_total_row(rows, subject, scenario, run_index, metadata, "interrupted_publication.total")
948
    evidence["harness_command_count"] = total["harness_command_count"] + 3
949
    total.update(evidence)
950
    if not (
951
        evidence["fixture_received_complete_request_before_disconnect"]
952
        and request_count == 1
953
        and evidence["local_head_preserved"]
954
        and evidence["local_work_preserved"]
955
        and not publication_attempt["timed_out"]
956
        and row_returncode(rows[-1]) == 0
957
    ):
958
        total["returncode"] = 1
959
        total["stderr"] = "interrupted_publication_evidence_incomplete"
960
    rows.append(total)
961
    return rows
962
963
964
def edit_step(
965
    subject: Subject,
966
    scenario: str,
967
    operation: str,
968
    workspace: Path,
969
    payload: str,
970
    run_index: int,
971
    metadata: dict[str, Any],
972
    admitted: int,
973
) -> dict[str, Any]:
974
    command = ["/bin/zsh", "-c", f"echo {payload!r} >> task-notes.md"]
975
    return step_row(
976
        subject, scenario, operation, command, workspace, run_index, metadata, admitted, kind="edit"
977
    )
978
979
980
def oak_virtual_branch(subject: Subject, mount_dir: Path) -> str | None:
981
    proc = run_untimed([str(subject.bin), "status"], mount_dir)
982
    match = VIRTUAL_BRANCH_RE.search(proc.stdout + proc.stderr)
983
    return match.group(1) if match else None
984
985
986
def run_oak_loop(
987
    subject: Subject,
988
    remote: oakbench_remotes.RemoteResolution,
989
    run_root: Path,
990
    run_index: int,
991
    metadata: dict[str, Any],
992
    admitted: int,
993
) -> list[dict[str, Any]]:
994
    scenario = "task_loop"
995
    meta = {**metadata, **remote.row_fields()}
996
    rows: list[dict[str, Any]] = []
997
    branches: dict[str, str | None] = {}
998
    payloads: dict[str, str] = {}
999
    mounts: list[Path] = []
1000
    vcs = str(subject.bin)
1001
    try:
1002
        for phase in PHASES:
1003
            workspace = run_root / f"tl-{subject.name}-r{run_index}-{phase}"
1004
            payload = f"payload {phase} run={run_index} bench={metadata['bench_id']}"
1005
            payloads[phase] = payload
1006
            acquire = step_row(
1007
                subject, scenario, f"{phase}.acquire", [vcs, "mount", remote.repo or "", str(workspace)],
1008
                run_root, run_index, meta, admitted,
1009
            )
1010
            rows.append(acquire)
1011
            if int(acquire["returncode"]) != 0:
1012
                return rows
1013
            mounts.append(workspace)
1014
            branches[phase] = oak_virtual_branch(subject, workspace)
1015
            rows.append(edit_step(subject, scenario, f"{phase}.edit", workspace, payload, run_index, meta, admitted))
1016
            rows.append(step_row(subject, scenario, f"{phase}.snapshot", [vcs, "commit", "--no-verify"], workspace, run_index, meta, admitted))
1017
            rows.append(step_row(subject, scenario, f"{phase}.publish", [vcs, "push"], workspace, run_index, meta, admitted))
1018
            if phase == "task":
1019
                rows.append(
1020
                    step_row(
1021
                        subject, scenario, "task.describe",
1022
                        [vcs, "desc", f"bench task loop {metadata['bench_id']} r{run_index}"],
1023
                        workspace, run_index, meta, admitted,
1024
                    )
1025
                )
1026
            end = step_row(
1027
                subject, scenario, f"{phase}.end", [vcs, "mount", "end", str(workspace)],
1028
                run_root, run_index, meta, admitted,
1029
            )
1030
            rows.append(end)
1031
            if int(end["returncode"]) == 0 and workspace in mounts:
1032
                mounts.remove(workspace)
1033
1034
        # Untimed oracle: each phase's payload must be on its pushed branch.
1035
        verifier = run_root / f"tl-verify-{subject.name}-r{run_index}"
1036
        clone = run_untimed([vcs, "clone", remote.repo or "", str(verifier)], run_root)
1037
        oracle_ok: bool | None = None
1038
        committed_unmeasured = False
1039
        if clone.returncode == 0:
1040
            oracle_ok = True
1041
            for phase in PHASES:
1042
                branch = branches.get(phase)
1043
                if not branch:
1044
                    oracle_ok = None
1045
                    break
1046
                switch = run_untimed([vcs, "switch", branch], verifier)
1047
                content = (verifier / "task-notes.md")
1048
                seen = content.read_text() if content.exists() else ""
1049
                committed = independent_committed_text(verifier, "task-notes.md")
1050
                if switch.returncode != 0 or payloads[phase] not in seen:
1051
                    oracle_ok = False
1052
                elif committed is None:
1053
                    # Independent reader unavailable: never a worktree pass.
1054
                    committed_unmeasured = True
1055
                elif payloads[phase] not in committed:
1056
                    # Committed HEAD read without the Oak binary must agree.
1057
                    oracle_ok = False
1058
            if oracle_ok is True and committed_unmeasured:
1059
                oracle_ok = None
1060
        total = {
1061
            **phase_total_row(rows, subject, scenario, run_index, meta, "task_loop.total"),
1062
            "oracle_payload_on_remote_branch": oracle_ok,
1063
            "workspace_branches": branches,
1064
            "measurement_source": ("verifier_clone_switch_grep+independent_oak_reader; "
1065
                                   "null oracle means branch name unparsed"),
1066
        }
1067
        rows.append(enforce_oracle_result(total, oracle_ok))
1068
        return rows
1069
    finally:
1070
        for mount_dir in mounts:
1071
            run_untimed([vcs, "mount", "end", str(mount_dir), "-f"], run_root)
1072
1073
1074
def ensure_git_remote_seeded(subject: Subject, remote: oakbench_remotes.RemoteResolution, scratch: Path) -> str | None:
1075
    """Untimed: make sure the git remote has a main with a seed commit."""
1076
    vcs = str(subject.bin)
1077
    probe = run_untimed([vcs, "ls-remote", str(remote.repo), "refs/heads/main"], None)
1078
    if probe.returncode == 0 and probe.stdout.strip():
1079
        if remote.transport == oakbench_remotes.TRANSPORT_LOCAL_FILE:
1080
            head = run_untimed([vcs, "symbolic-ref", "HEAD", "refs/heads/main"], Path(str(remote.repo)))
1081
            if head.returncode != 0:
1082
                return f"git remote HEAD setup failed: {head.stderr.strip()[:200]}"
1083
        return None
1084
    seed = scratch / "git-seed"
1085
    if seed.exists():
1086
        shutil.rmtree(seed)
1087
    seed.mkdir(parents=True)
1088
    (seed / "README.md").write_text("Task-loop benchmark repo (disposable).\n")
1089
    (seed / "task-notes.md").write_text("# task notes\n")
1090
    for command in (
1091
        [vcs, "init", "-q", "-b", "main"],
1092
        [vcs, "add", "."],
1093
        [vcs, "commit", "-q", "-m", "seed"],
1094
        [vcs, "push", "-q", str(remote.repo), "main:main"],
1095
    ):
1096
        proc = run_untimed(command, seed)
1097
        if proc.returncode != 0:
1098
            return f"git remote seed `{' '.join(command[1:3])}` failed: {proc.stderr.strip()[:200]}"
1099
    if remote.transport == oakbench_remotes.TRANSPORT_LOCAL_FILE:
1100
        head = run_untimed([vcs, "symbolic-ref", "HEAD", "refs/heads/main"], Path(str(remote.repo)))
1101
        if head.returncode != 0:
1102
            return f"git remote HEAD setup failed: {head.stderr.strip()[:200]}"
1103
    return None
1104
1105
1106
def run_git_agent_task_baseline(
1107
    subject: Subject,
1108
    remote: oakbench_remotes.RemoteResolution,
1109
    run_root: Path,
1110
    run_index: int,
1111
    metadata: dict[str, Any],
1112
    admitted: int,
1113
    remote_create_ms: float = 0.0,
1114
) -> list[dict[str, Any]]:
1115
    """Measure a cold clone and a warm isolated worktree on one local remote.
1116
1117
    This is the core-equivalent, local-file cell. Hosted CI is deliberately a
1118
    skip row: a local commit oracle is not relabelled as provider CI.
1119
    """
1120
    scenario = AGENT_TASK_SCENARIO
1121
    meta = {
1122
        **metadata,
1123
        **remote.row_fields(),
1124
        "benchmark_track": (
1125
            "core-equivalent"
1126
            if remote.transport == oakbench_remotes.TRANSPORT_LOCAL_FILE
1127
            else "agent-default"
1128
        ),
1129
    }
1130
    vcs = str(subject.bin)
1131
    rows: list[dict[str, Any]] = [
1132
        harness_step_row(
1133
            subject,
1134
            scenario,
1135
            "setup.remote_container",
1136
            run_index,
1137
            meta,
1138
            remote_create_ms,
1139
            harness_command_count=1,
1140
        )
1141
    ]
1142
    setup_start = time.perf_counter()
1143
    seed_error = ensure_git_remote_seeded(subject, remote, run_root)
1144
    rows.append(
1145
        harness_step_row(
1146
            subject,
1147
            scenario,
1148
            "setup.remote_seed",
1149
            run_index,
1150
            meta,
1151
            (time.perf_counter() - setup_start) * 1000,
1152
            returncode=1 if seed_error else 0,
1153
            harness_command_count=6,
1154
        )
1155
    )
1156
    if seed_error:
1157
        rows[-1]["stderr"] = seed_error
1158
        return rows
1159
1160
    base = run_root / f"agent-task-{subject.name}-r{run_index}-cold"
1161
    cold_branch = oakbench_remotes.disposable_branch(
1162
        str(metadata["bench_id"]), "agent-task-cold", subject.name, run_index
1163
    )
1164
    warm_branch = oakbench_remotes.disposable_branch(
1165
        str(metadata["bench_id"]), "agent-task-warm", subject.name, run_index
1166
    )
1167
    cold_payload = f"cold task run={run_index} bench={metadata['bench_id']}"
1168
    warm_payload = f"warm task run={run_index} bench={metadata['bench_id']}"
1169
1170
    rows.append(
1171
        step_row(
1172
            subject,
1173
            scenario,
1174
            "cold.acquire",
1175
            [vcs, "clone", "-q", str(remote.repo), str(base)],
1176
            run_root,
1177
            run_index,
1178
            meta,
1179
            admitted,
1180
        )
1181
    )
1182
    if row_returncode(rows[-1]) != 0:
1183
        return rows
1184
    rows.append(
1185
        step_row(
1186
            subject, scenario, "cold.branch", [vcs, "switch", "-q", "-c", cold_branch],
1187
            base, run_index, meta, admitted,
1188
        )
1189
    )
1190
    rows.append(
1191
        step_row(
1192
            subject, scenario, "cold.inspect", [vcs, "status", "--porcelain=v1"],
1193
            base, run_index, meta, admitted,
1194
        )
1195
    )
1196
    rows.append(edit_step(subject, scenario, "cold.edit", base, cold_payload, run_index, meta, admitted))
1197
    rows.append(step_row(subject, scenario, "cold.checkpoint.add", [vcs, "add", "."], base, run_index, meta, admitted))
1198
    rows.append(
1199
        step_row(
1200
            subject, scenario, "cold.checkpoint.commit",
1201
            [vcs, "commit", "-q", "-m", f"agent task cold {metadata['bench_id']} r{run_index}"],
1202
            base, run_index, meta, admitted,
1203
        )
1204
    )
1205
    rows.append(
1206
        step_row(
1207
            subject, scenario, "cold.publish", [vcs, "push", "-q", "-u", "origin", cold_branch],
1208
            base, run_index, meta, admitted,
1209
        )
1210
    )
1211
    rows.append(
1212
        unmeasured_step_row(
1213
            subject, scenario, "cold.ci.observe", run_index, meta,
1214
            "local-file transport has no hosted CI provider; exact CI observation is unmeasured",
1215
        )
1216
    )
1217
    rows.append(
1218
        step_row(
1219
            subject, scenario, "cold.review_handoff",
1220
            [vcs, "show", "--format=%H%n%s", "--stat", "--oneline", "HEAD"],
1221
            base, run_index, meta, admitted,
1222
        )
1223
    )
1224
1225
    warm = run_root / f"agent-task-{subject.name}-r{run_index}-warm"
1226
    rows.append(
1227
        step_row(
1228
            subject, scenario, "warm.acquire",
1229
            [vcs, "worktree", "add", "-q", str(warm), "-b", warm_branch, "origin/main"],
1230
            base, run_index, meta, admitted,
1231
        )
1232
    )
1233
    if row_returncode(rows[-1]) == 0:
1234
        rows.append(
1235
            step_row(
1236
                subject, scenario, "warm.inspect", [vcs, "status", "--porcelain=v1"],
1237
                warm, run_index, meta, admitted,
1238
            )
1239
        )
1240
        rows.append(edit_step(subject, scenario, "warm.edit", warm, warm_payload, run_index, meta, admitted))
1241
        rows.append(step_row(subject, scenario, "warm.checkpoint.add", [vcs, "add", "."], warm, run_index, meta, admitted))
1242
        rows.append(
1243
            step_row(
1244
                subject, scenario, "warm.checkpoint.commit",
1245
                [vcs, "commit", "-q", "-m", f"agent task warm {metadata['bench_id']} r{run_index}"],
1246
                warm, run_index, meta, admitted,
1247
            )
1248
        )
1249
        rows.append(
1250
            step_row(
1251
                subject, scenario, "warm.publish", [vcs, "push", "-q", "-u", "origin", warm_branch],
1252
                warm, run_index, meta, admitted,
1253
            )
1254
        )
1255
        rows.append(
1256
            unmeasured_step_row(
1257
                subject, scenario, "warm.ci.observe", run_index, meta,
1258
                "local-file transport has no hosted CI provider; exact CI observation is unmeasured",
1259
            )
1260
        )
1261
        rows.append(
1262
            step_row(
1263
                subject, scenario, "warm.review_handoff",
1264
                [vcs, "show", "--format=%H%n%s", "--stat", "--oneline", "HEAD"],
1265
                warm, run_index, meta, admitted,
1266
            )
1267
        )
1268
1269
    usage_before_cleanup = {
1270
        "cold": bounded_tree_usage(base),
1271
        "warm": bounded_tree_usage(warm),
1272
    }
1273
    oracle_ok = True
1274
    for branch, payload in ((cold_branch, cold_payload), (warm_branch, warm_payload)):
1275
        show = run_untimed([vcs, "--git-dir", str(remote.repo), "show", f"{branch}:task-notes.md"], run_root)
1276
        if show.returncode != 0 or payload not in show.stdout:
1277
            oracle_ok = False
1278
1279
    if warm.exists():
1280
        rows.append(
1281
            step_row(
1282
                subject, scenario, "cleanup.warm",
1283
                [vcs, "worktree", "remove", str(warm)], base, run_index, meta, admitted,
1284
            )
1285
        )
1286
    cleanup_start = time.perf_counter()
1287
    shutil.rmtree(base, ignore_errors=True)
1288
    rows.append(
1289
        harness_step_row(
1290
            subject, scenario, "cleanup.cold", run_index, meta,
1291
            (time.perf_counter() - cleanup_start) * 1000,
1292
        )
1293
    )
1294
1295
    total = phase_total_row(rows, subject, scenario, run_index, meta, "agent_task.total")
1296
    total.update(
1297
        {
1298
            "oracle_payload_on_remote_branch": oracle_ok,
1299
            "acquisition_models": {
1300
                "cold": "fresh_full_clone",
1301
                "warm": "shared_object_worktree",
1302
            },
1303
            "repository_cache_state": {
1304
                "cold": "fresh_destination_no_shared_object_store",
1305
                "warm": "explicit_shared_object_store",
1306
            },
1307
            "os_cache_state": "unknown",
1308
            "os_cache_state_reason": "page_cache_not_purged_or_instrumented",
1309
            "workspace_usage_before_cleanup": usage_before_cleanup,
1310
            "network_bytes": None,
1311
            "network_bytes_reason": "local_file_transport_not_observed_by_byte_proxy",
1312
            "partial_clone_comparator": None,
1313
            "partial_clone_reason": "local file transport does not establish a network partial-clone comparison",
1314
            "human_action_count": 0,
1315
            "parallel_evidence_lane": "parallel_contention.py/workspace_per_task",
1316
            "oracle_probe_calls": 2,
1317
            "harness_command_count": total["harness_command_count"] + 2,
1318
        }
1319
    )
1320
    if not oracle_ok:
1321
        total["returncode"] = 1
1322
        total["stderr"] = "agent_task_remote_payload_oracle_failed"
1323
    rows.append(total)
1324
    return rows
1325
1326
1327
def independent_committed_state(repo: Path, rel_path: str) -> tuple[str | None, str | None]:
1328
    """(committed text or None, HEAD or None) via the stdlib Oak reader.
1329
1330
    Unavailable (format drift, bounds) returns (None, None): callers must then
1331
    report the oracle unmeasured, never fall back to the worktree. Positively
1332
    invalid stored state returns an empty payload so the oracle fails.
1333
    """
1334
    from oakbench import oak_sqlite_reader as reader
1335
    try:
1336
        payloads, meta = reader.committed_payloads(repo, [rel_path])
1337
    except reader.Invalid:
1338
        return "", None
1339
    except (reader.Unavailable, OSError, ValueError):
1340
        return None, None
1341
    data = payloads.get(rel_path)
1342
    return (data.decode("utf-8", "replace") if data is not None else ""), meta.get("head")
1343
1344
1345
def independent_committed_text(repo: Path, rel_path: str) -> str | None:
1346
    return independent_committed_state(repo, rel_path)[0]
1347
1348
1349
def reserve_loopback_port() -> int:
1350
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
1351
        listener.bind(("127.0.0.1", 0))
1352
        return int(listener.getsockname()[1])
1353
1354
1355
def wait_for_loopback_port(
1356
    process: subprocess.Popen[Any], port: int, timeout_s: float = LOCAL_OAK_SERVER_READY_TIMEOUT_S
1357
) -> bool:
1358
    deadline = time.monotonic() + timeout_s
1359
    while time.monotonic() < deadline:
1360
        if process.poll() is not None:
1361
            return False
1362
        try:
1363
            with socket.create_connection(("127.0.0.1", port), timeout=0.2):
1364
                return True
1365
        except OSError:
1366
            time.sleep(0.05)
1367
    return False
1368
1369
1370
def harness_command_row(
1371
    subject: Subject,
1372
    scenario: str,
1373
    operation: str,
1374
    command: list[str],
1375
    cwd: Path,
1376
    run_index: int,
1377
    metadata: dict[str, Any],
1378
) -> tuple[dict[str, Any], subprocess.CompletedProcess[str]]:
1379
    started = time.perf_counter()
1380
    result = run_bounded_harness_command(command, cwd)
1381
    row = harness_step_row(
1382
        subject,
1383
        scenario,
1384
        operation,
1385
        run_index,
1386
        metadata,
1387
        (time.perf_counter() - started) * 1000,
1388
        returncode=0 if result.returncode == 0 else result.returncode,
1389
        harness_command_count=1,
1390
    )
1391
    if result.returncode != 0:
1392
        row["stderr"] = str(result.stderr)[-4000:]
1393
    return row, result
1394
1395
1396
def run_bounded_harness_command(
1397
    command: list[str], cwd: Path, *, env: dict[str, str] | None = None
1398
) -> subprocess.CompletedProcess[str]:
1399
    capture = run_timed(
1400
        command,
1401
        cwd,
1402
        env or base_env(),
1403
        admitted_output_chars=4_000,
1404
        timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
1405
    )
1406
    return subprocess.CompletedProcess(
1407
        command,
1408
        capture.returncode,
1409
        capture.stdout_text,
1410
        capture.stderr_text,
1411
    )
1412
1413
1414
def remote_branch_head(remote: str, repo_spec: str, branch: str) -> str | None:
1415
    owner, repo = repo_spec.split("/", 1)
1416
    branch_segment = urllib.parse.quote(branch, safe="")
1417
    url = f"{remote}/api/{owner}/{repo}/branches/{branch_segment}"
1418
    try:
1419
        with urllib.request.urlopen(url, timeout=2.0) as response:
1420
            value = json.loads(response.read())
1421
    except (OSError, ValueError, urllib.error.URLError):
1422
        return None
1423
    head = value.get("head") if isinstance(value, dict) else None
1424
    return head if isinstance(head, str) else None
1425
1426
1427
def run_oak_local_agent_task_baseline(
1428
    subject: Subject,
1429
    run_root: Path,
1430
    run_index: int,
1431
    metadata: dict[str, Any],
1432
    admitted: int,
1433
) -> list[dict[str, Any]]:
1434
    """Exercise two fresh Oak checkouts against an owned loopback Oak server."""
1435
    run_root = run_root.resolve()
1436
    scenario = AGENT_TASK_OAK_LOCAL_SCENARIO
1437
    meta = {
1438
        **metadata,
1439
        "benchmark_track": "oak-local-loopback",
1440
        "remote_transport": "loopback_http",
1441
        "remote_server": "127.0.0.1",
1442
    }
1443
    rows: list[dict[str, Any]] = []
1444
    vcs = str(subject.bin)
1445
    server_root = run_root / f"oak-local-server-{subject.name}-r{run_index}"
1446
    seed = run_root / f"oak-local-seed-{subject.name}-r{run_index}"
1447
    cold = run_root / f"oak-local-cold-{subject.name}-r{run_index}"
1448
    followup = run_root / f"oak-local-followup-{subject.name}-r{run_index}"
1449
    verifier = run_root / f"oak-local-verifier-{subject.name}-r{run_index}"
1450
    repo_spec = f"qa/task-loop-{subject.name}-r{run_index}"
1451
    port = reserve_loopback_port()
1452
    remote = f"http://127.0.0.1:{port}"
1453
    server_root.mkdir(parents=True)
1454
    server_started = time.perf_counter()
1455
    server: subprocess.Popen[Any] | None = None
1456
    server_start_error: str | None = None
1457
    try:
1458
        server = subprocess.Popen(
1459
            [vcs, "serve", "--dir", str(server_root), "--host", "127.0.0.1", "--port", str(port)],
1460
            cwd=run_root,
1461
            env=base_env(),
1462
            stdout=subprocess.DEVNULL,
1463
            stderr=subprocess.DEVNULL,
1464
            start_new_session=os.name == "posix",
1465
        )
1466
    except OSError as error:
1467
        server_start_error = type(error).__name__
1468
    ready = server is not None and wait_for_loopback_port(server, port)
1469
    rows.append(
1470
        harness_step_row(
1471
            subject,
1472
            scenario,
1473
            "setup.server",
1474
            run_index,
1475
            meta,
1476
            (time.perf_counter() - server_started) * 1000,
1477
            returncode=0 if ready else 1,
1478
            harness_command_count=1,
1479
        )
1480
    )
1481
    branch: str | None = None
1482
    oracle_unmeasured: str | None = None
1483
    expected_head: str | None = None
1484
    expected_payload = "# task notes\n"
1485
    failure: str | None = None if ready else (
1486
        f"local_oak_server_start_failed:{server_start_error}"
1487
        if server_start_error else "local_oak_server_not_ready"
1488
    )
1489
    try:
1490
        if failure is None:
1491
            seed.mkdir(parents=True)
1492
            (seed / "task-notes.md").write_text(expected_payload)
1493
            for operation, command in (
1494
                ("setup.seed_init", [vcs, "init", "."]),
1495
                ("setup.seed_commit", [vcs, "commit"]),
1496
                ("setup.seed_push", [vcs, "push", "--repo", repo_spec, "--remote", remote]),
1497
            ):
1498
                row, result = harness_command_row(
1499
                    subject, scenario, operation, command, seed, run_index, meta
1500
                )
1501
                rows.append(row)
1502
                if result.returncode != 0:
1503
                    failure = f"{operation}_failed"
1504
                    break
1505
1506
        def agent_step(operation: str, command: list[str], cwd: Path, *, preserve: bool = False) -> dict[str, Any]:
1507
            row = step_row(
1508
                subject,
1509
                scenario,
1510
                operation,
1511
                command,
1512
                cwd,
1513
                run_index,
1514
                meta,
1515
                admitted,
1516
                preserve_stdout=preserve,
1517
                timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
1518
            )
1519
            rows.append(row)
1520
            return row
1521
1522
        if failure is None:
1523
            row = agent_step(
1524
                "cold.acquire",
1525
                [vcs, "clone", repo_spec, str(cold), "--remote", remote, "--allow-legacy-scope"],
1526
                run_root,
1527
            )
1528
            if row_returncode(row) != 0:
1529
                failure = "cold.acquire_failed"
1530
        if failure is None:
1531
            status = agent_step("cold.inspect", [vcs, "status", "--json"], cold, preserve=True)
1532
            status_value = _parse_json_object(status.get("stdout"))
1533
            branch_value = status_value.get("branch") if status_value else None
1534
            branch = branch_value if isinstance(branch_value, str) else None
1535
            if not branch:
1536
                failure = "cold.branch_identity_missing"
1537
        if failure is None:
1538
            cold_payload = f"cold task run={run_index} bench={metadata['bench_id']}\n"
1539
            expected_payload += cold_payload
1540
            rows.append(edit_step(subject, scenario, "cold.edit", cold, cold_payload.strip(), run_index, meta, admitted))
1541
            if row_returncode(agent_step("cold.checkpoint", [vcs, "commit"], cold)) != 0:
1542
                failure = "cold.checkpoint_failed"
1543
            elif row_returncode(
1544
                agent_step("cold.publish", [vcs, "push", "--repo", repo_spec, "--remote", remote], cold)
1545
            ) != 0:
1546
                failure = "cold.publish_failed"
1547
            rows.append(
1548
                unmeasured_step_row(
1549
                    subject, scenario, "cold.ci.observe", run_index, meta,
1550
                    "owned local oak serve has no configured CI provider",
1551
                )
1552
            )
1553
            rows.append(
1554
                unmeasured_step_row(
1555
                    subject, scenario, "cold.review_handoff", run_index, meta,
1556
                    "owned local oak serve does not provide hosted review semantics",
1557
                )
1558
            )
1559
        if failure is None and branch is not None:
1560
            row = agent_step(
1561
                "followup.acquire",
1562
                [
1563
                    vcs, "clone", repo_spec, str(followup), "--remote", remote,
1564
                    "--branch", branch, "--allow-legacy-scope",
1565
                ],
1566
                run_root,
1567
            )
1568
            if row_returncode(row) != 0:
1569
                failure = "followup.acquire_failed"
1570
        if failure is None:
1571
            agent_step("followup.inspect", [vcs, "status", "--json"], followup)
1572
            followup_payload = f"followup task run={run_index} bench={metadata['bench_id']}\n"
1573
            expected_payload += followup_payload
1574
            rows.append(
1575
                edit_step(
1576
                    subject, scenario, "followup.edit", followup,
1577
                    followup_payload.strip(), run_index, meta, admitted,
1578
                )
1579
            )
1580
            if row_returncode(agent_step("followup.checkpoint", [vcs, "commit"], followup)) != 0:
1581
                failure = "followup.checkpoint_failed"
1582
            else:
1583
                head_result = run_bounded_harness_command([vcs, "hash"], followup)
1584
                expected_head = head_result.stdout.strip() if head_result.returncode == 0 else None
1585
                if row_returncode(
1586
                    agent_step(
1587
                        "followup.publish",
1588
                        [vcs, "push", "--repo", repo_spec, "--remote", remote],
1589
                        followup,
1590
                    )
1591
                ) != 0:
1592
                    failure = "followup.publish_failed"
1593
            rows.append(
1594
                unmeasured_step_row(
1595
                    subject, scenario, "followup.ci.observe", run_index, meta,
1596
                    "owned local oak serve has no configured CI provider",
1597
                )
1598
            )
1599
            rows.append(
1600
                unmeasured_step_row(
1601
                    subject, scenario, "followup.review_handoff", run_index, meta,
1602
                    "owned local oak serve does not provide hosted review semantics",
1603
                )
1604
            )
1605
1606
        payload_ok = False
1607
        verifier_head: str | None = None
1608
        advertised_head: str | None = None
1609
        if failure is None and branch is not None:
1610
            clone = run_bounded_harness_command(
1611
                [
1612
                    vcs, "clone", repo_spec, str(verifier), "--remote", remote,
1613
                    "--branch", branch, "--allow-legacy-scope",
1614
                ],
1615
                run_root,
1616
            )
1617
            if clone.returncode == 0:
1618
                payload_path = verifier / "task-notes.md"
1619
                payload_ok = payload_path.exists() and payload_path.read_text() == expected_payload
1620
                # Committed payload and HEAD come from the independent reader
1621
                # (no Oak code); `oak hash` must agree with it.
1622
                committed_text, reader_head = independent_committed_state(verifier, "task-notes.md")
1623
                if committed_text is None:
1624
                    # Independent reader unavailable: unmeasured, never a worktree pass.
1625
                    oracle_unmeasured = "local_oak_committed_state_unmeasured:independent_reader_unavailable"
1626
                else:
1627
                    payload_ok = payload_ok and committed_text == expected_payload
1628
                verifier_result = run_bounded_harness_command([vcs, "hash"], verifier)
1629
                verifier_head = verifier_result.stdout.strip() if verifier_result.returncode == 0 else None
1630
                if reader_head is not None and verifier_head != reader_head:
1631
                    verifier_head = None
1632
                advertised_head = remote_branch_head(remote, repo_spec, branch)
1633
            else:
1634
                failure = "verifier_clone_failed"
1635
        head_ok = bool(expected_head and expected_head == verifier_head == advertised_head)
1636
        if not payload_ok:
1637
            failure = failure or "local_oak_payload_oracle_failed"
1638
        if not head_ok:
1639
            failure = failure or "local_oak_head_oracle_failed"
1640
    finally:
1641
        if server is not None:
1642
            stop_owned_process(server)
1643
        server_stopped = server is None or server.poll() is not None
1644
        cleanup_started = time.perf_counter()
1645
        for path in (seed, cold, followup, verifier, server_root):
1646
            shutil.rmtree(path, ignore_errors=True)
1647
        rows.append(
1648
            harness_step_row(
1649
                subject,
1650
                scenario,
1651
                "cleanup.local_server_and_checkouts",
1652
                run_index,
1653
                meta,
1654
                (time.perf_counter() - cleanup_started) * 1000,
1655
            )
1656
        )
1657
1658
    total = phase_total_row(rows, subject, scenario, run_index, meta, "agent_task_oak_local.total")
1659
    total.update(
1660
        {
1661
            "oracle_payload_on_remote_branch": payload_ok and head_ok,
1662
            "oracle_payload_matches": payload_ok,
1663
            "oracle_branch_head_matches": head_ok,
1664
            "server_process_stopped": server_stopped,
1665
            "acquisition_models": {
1666
                "cold": "fresh_full_clone",
1667
                "followup": "second_fresh_full_clone",
1668
            },
1669
            "repository_cache_state": {
1670
                "cold": "fresh_destination_no_shared_object_store",
1671
                "followup": "no_shared_object_store",
1672
            },
1673
            "os_cache_state": "unknown",
1674
            "os_cache_state_reason": "page_cache_not_purged_or_instrumented",
1675
            "network_bytes": None,
1676
            "network_bytes_reason": "loopback transport not observed by byte proxy",
1677
            "hosted_ci_configured": False,
1678
            "oracle_probe_calls": 4,
1679
            # The expected-head hash plus verifier clone/hash/HTTP head probe
1680
            # are oracle/setup commands, not agent-facing tool calls.
1681
            "harness_command_count": total["harness_command_count"] + 4,
1682
        }
1683
    )
1684
    if failure or not server_stopped:
1685
        total["returncode"] = 1
1686
        total["stderr"] = failure or "local_oak_server_cleanup_failed"
1687
    elif oracle_unmeasured:
1688
        total.update(returncode=77, skip_reason=oracle_unmeasured, oracle_payload_on_remote_branch=None,
1689
                     oracle_payload_matches=None)
1690
    rows.append(total)
1691
    return rows
1692
1693
1694
def run_legacy_selected_materialization_campaign(
1695
    subjects: list[Subject],
1696
    server_bin: Path | None,
1697
    server_source: Path | None,
1698
    server_source_head: str | None,
1699
    baseline_subject_name: str | None,
1700
    run_root: Path,
1701
    runs: int,
1702
    metadata: dict[str, Any],
1703
    admitted: int,
1704
    randomize_order: bool,
1705
    random_seed: int,
1706
    timing_controls_requested: bool,
1707
) -> list[dict[str, Any]]:
1708
    """Measure one legacy selected-branch clone against one fixed Serve fixture."""
1709
    scenario = LEGACY_SELECTED_MATERIALIZATION_SCENARIO
1710
    oak_subjects = [subject for subject in subjects if subject.kind == "oak"]
1711
    if not oak_subjects:
1712
        return []
1713
    if (
1714
        server_bin is None
1715
        or not server_bin.is_absolute()
1716
        or not server_bin.is_file()
1717
        or not os.access(server_bin, os.X_OK)
1718
    ):
1719
        return [
1720
            unmeasured_step_row(
1721
                subject,
1722
                scenario,
1723
                "scenario.skip",
1724
                0,
1725
                metadata,
1726
                "--oak-serve-bin must name an absolute executable used only as the fixed Serve fixture",
1727
            )
1728
            for subject in oak_subjects
1729
        ]
1730
    if server_source_head is None or re.fullmatch(r"[0-9a-f]{64}", server_source_head) is None:
1731
        return [
1732
            unmeasured_step_row(
1733
                subject,
1734
                scenario,
1735
                "scenario.skip",
1736
                0,
1737
                metadata,
1738
                "the fixed Serve binary's exact 64-hex --oak-serve-source-head is required",
1739
            )
1740
            for subject in oak_subjects
1741
        ]
1742
    baseline_subject = next(
1743
        (subject for subject in oak_subjects if subject.name == baseline_subject_name), None
1744
    )
1745
    if baseline_subject is None:
1746
        return [
1747
            unmeasured_step_row(
1748
                subject,
1749
                scenario,
1750
                "scenario.skip",
1751
                0,
1752
                metadata,
1753
                "--materialization-baseline-subject must name one enabled Oak subject",
1754
            )
1755
            for subject in oak_subjects
1756
        ]
1757
    from oakbench.subjects import which_measured_git
1758
    git_bin = which_measured_git()
1759
    if git_bin is None:
1760
        return [
1761
            unmeasured_step_row(
1762
                subject,
1763
                scenario,
1764
                "scenario.skip",
1765
                0,
1766
                metadata,
1767
                "git is required only to create the deterministic main-history fixture",
1768
            )
1769
            for subject in oak_subjects
1770
        ]
1771
1772
    server_bin = server_bin.resolve()
1773
    resolved_server_source: Path | None = None
1774
    if server_source is not None:
1775
        if not server_source.is_absolute() or not server_source.is_dir():
1776
            return [
1777
                unmeasured_step_row(
1778
                    subject,
1779
                    scenario,
1780
                    "scenario.skip",
1781
                    0,
1782
                    metadata,
1783
                    "--oak-serve-source must name an absolute read-only source checkout",
1784
                )
1785
                for subject in oak_subjects
1786
            ]
1787
        resolved_server_source = server_source.resolve()
1788
        source_head_result = run_bounded_harness_command(
1789
            [str(server_bin), "hash"],
1790
            resolved_server_source,
1791
            env=isolated_oak_fixture_env(run_root / "source-check"),
1792
        )
1793
        if source_head_result.returncode != 0 or source_head_result.stdout.strip() != server_source_head:
1794
            return [
1795
                unmeasured_step_row(
1796
                    subject,
1797
                    scenario,
1798
                    "scenario.skip",
1799
                    0,
1800
                    metadata,
1801
                    "fixed Serve source checkout does not match --oak-serve-source-head",
1802
                )
1803
                for subject in oak_subjects
1804
            ]
1805
    baseline_binary_sha256 = binary_sha256(baseline_subject.bin)
1806
    timing_controls_met = materialization_timing_controls_met(
1807
        metadata, runs, randomize_order, timing_controls_requested
1808
    )
1809
    fixture_root = run_root / "legacy-selected-materialization-fixture"
1810
    server_root = fixture_root / "server"
1811
    git_seed = fixture_root / "git-seed.git"
1812
    seed = fixture_root / "seed"
1813
    task_config_root = fixture_root / "config"
1814
    for path in (server_root, git_seed, task_config_root):
1815
        path.mkdir(parents=True, exist_ok=True)
1816
    fixture_env = isolated_oak_fixture_env(task_config_root)
1817
    fixture_meta = {
1818
        **metadata,
1819
        "benchmark_track": "oak-local-loopback-materialization",
1820
        "remote_transport": "loopback_http",
1821
        "remote_server": "127.0.0.1",
1822
        "serve_binary": str(server_bin),
1823
        "serve_binary_sha256": binary_sha256(server_bin),
1824
        "serve_source": str(resolved_server_source) if resolved_server_source else None,
1825
        "serve_source_checkout_verification": (
1826
            "exact_head_verified" if resolved_server_source else "unavailable_binary_hash_and_head_supplied"
1827
        ),
1828
        "serve_source_head": server_source_head,
1829
        "serve_version": subject_version(Subject("serve_fixture", "oak", "Serve fixture", server_bin)),
1830
        "baseline_subject": baseline_subject.name,
1831
        "baseline_binary_sha256": baseline_binary_sha256,
1832
        "fixture_id": "legacy-selected-materialization-192x16k-v1",
1833
        "fixture_regular_files": 192,
1834
        "fixture_regular_file_bytes": 16 * 1024,
1835
        "fixture_empty_files": 1,
1836
        "fixture_executable_files": 8,
1837
        "fixture_selected_only_files": 1,
1838
        "os_cache_state": "warm_or_unknown_after_required_fixture_probe",
1839
        "materialization_timing_controls_met": timing_controls_met,
1840
        "materialization_timing_controls_reason": (
1841
            "harness_controls_met_aa_and_quiet_host_evidence_still_required"
1842
            if timing_controls_met
1843
            else "correctness_only_or_harness_controls_not_met"
1844
        ),
1845
    }
1846
    rows: list[dict[str, Any]] = []
1847
    setup_subject = baseline_subject
1848
1849
    def setup_command(operation: str, command: list[str], cwd: Path) -> bool:
1850
        started = time.perf_counter()
1851
        result = run_bounded_harness_command(command, cwd, env=fixture_env)
1852
        rows.append(
1853
            harness_step_row(
1854
                setup_subject,
1855
                scenario,
1856
                operation,
1857
                0,
1858
                fixture_meta,
1859
                (time.perf_counter() - started) * 1000,
1860
                returncode=0 if result.returncode == 0 else result.returncode,
1861
                harness_command_count=1,
1862
            )
1863
        )
1864
        if result.returncode != 0:
1865
            rows[-1]["stderr"] = result.stderr[-4000:]
1866
        return result.returncode == 0
1867
1868
    server: subprocess.Popen[Any] | None = None
1869
    proxy: CountingProxy | None = None
1870
    failure: str | None = None
1871
    expected_artifacts: list[str] | None = None
1872
    baseline_cache: dict[str, Any] | None = None
1873
    baseline_receipt_contract: dict[str, Any] | None = None
1874
    baseline_network_contract: dict[str, Any] | None = None
1875
    expected_status_contract: dict[str, Any] | None = None
1876
    selected_branch = "selected-materialization"
1877
    repo_spec = "qa/materialization-bench"
1878
    try:
1879
        for index in range(192):
1880
            directory = git_seed / f"group-{index % 12:02d}"
1881
            directory.mkdir(parents=True, exist_ok=True)
1882
            digest = hashlib.sha256(f"oak-materialization-{index}".encode()).digest()
1883
            content = (digest * ((16 * 1024 + len(digest) - 1) // len(digest)))[: 16 * 1024]
1884
            path = directory / f"file-{index:03d}.bin"
1885
            path.write_bytes(content)
1886
            if index < 8:
1887
                path.chmod(0o755)
1888
        (git_seed / "empty.txt").write_bytes(b"")
1889
        for operation, command in (
1890
            ("setup.git_init", [git_bin, "init", "-q", "-b", "main"]),
1891
            ("setup.git_add", [git_bin, "add", "."]),
1892
            ("setup.git_commit", [git_bin, "commit", "-q", "-m", "fixture main"]),
1893
        ):
1894
            if not setup_command(operation, command, git_seed):
1895
                failure = f"{operation}_failed"
1896
                break
1897
1898
        port = reserve_loopback_port()
1899
        direct_remote = f"http://127.0.0.1:{port}"
1900
        if failure is None:
1901
            server = subprocess.Popen(
1902
                [
1903
                    str(server_bin),
1904
                    "serve",
1905
                    "--dir",
1906
                    str(server_root),
1907
                    "--host",
1908
                    "127.0.0.1",
1909
                    "--port",
1910
                    str(port),
1911
                ],
1912
                cwd=fixture_root,
1913
                env=fixture_env,
1914
                stdout=subprocess.DEVNULL,
1915
                stderr=subprocess.DEVNULL,
1916
                start_new_session=os.name == "posix",
1917
            )
1918
            if not wait_for_loopback_port(server, port):
1919
                failure = "setup.server_not_ready"
1920
        if failure is None:
1921
            for operation, command, cwd in (
1922
                ("setup.oak_import", [str(server_bin), "clone", str(git_seed), str(seed)], fixture_root),
1923
                ("setup.branch", [str(server_bin), "switch", "-c", selected_branch], seed),
1924
            ):
1925
                if not setup_command(operation, command, cwd):
1926
                    failure = f"{operation}_failed"
1927
                    break
1928
        if failure is None:
1929
            (seed / "selected.txt").write_text("selected branch\n")
1930
            for operation, command in (
1931
                ("setup.oak_commit", [str(server_bin), "commit"]),
1932
                (
1933
                    "setup.oak_push",
1934
                    [str(server_bin), "push", "--repo", repo_spec, "--remote", direct_remote],
1935
                ),
1936
            ):
1937
                if not setup_command(operation, command, seed):
1938
                    failure = f"{operation}_failed"
1939
                    break
1940
1941
        expected_head_result = (
1942
            run_bounded_harness_command([str(server_bin), "hash"], seed, env=fixture_env)
1943
            if failure is None
1944
            else None
1945
        )
1946
        expected_head = (
1947
            expected_head_result.stdout.strip()
1948
            if expected_head_result is not None and expected_head_result.returncode == 0
1949
            else None
1950
        )
1951
        expected_history = (
1952
            normalized_history(json_command([str(server_bin), "log", "--json"], seed, fixture_env))
1953
            if failure is None
1954
            else None
1955
        )
1956
        expected_worktree = normalized_worktree_snapshot(seed) if failure is None else None
1957
        if not expected_head or expected_history is None or expected_worktree is None:
1958
            failure = failure or "setup.fixture_oracle_failed"
1959
1960
        if failure is None:
1961
            proxy = CountingProxy("127.0.0.1", port).start()
1962
            measured_remote = f"http://127.0.0.1:{proxy.address[1]}"
1963
            probe = fixture_root / "baseline-probe"
1964
            probe_env = isolated_oak_fixture_env(fixture_root / "baseline-probe-config")
1965
            proxy.reset_counts()
1966
            result = run_bounded_harness_command(
1967
                [
1968
                    str(baseline_subject.bin),
1969
                    "clone",
1970
                    "--json",
1971
                    "--allow-legacy-scope",
1972
                    "--remote",
1973
                    measured_remote,
1974
                    "--branch",
1975
                    selected_branch,
1976
                    repo_spec,
1977
                    str(probe),
1978
                ],
1979
                fixture_root,
1980
                env=probe_env,
1981
            )
1982
            receipt = _parse_json_object(result.stdout)
1983
            evidence = legacy_selected_receipt_evidence(receipt or {})
1984
            baseline_proxy_stats = proxy.snapshot()
1985
            baseline_cache = stat_cache_snapshot(probe)
1986
            expected_artifacts = normalized_oak_artifacts(probe)
1987
            baseline_receipt_contract = legacy_selected_receipt_contract(evidence)
1988
            baseline_network_contract = {
1989
                "connections": baseline_proxy_stats.connections,
1990
                "receipt": baseline_receipt_contract,
1991
            }
1992
            expected_status_contract = normalized_status(
1993
                json_command([str(baseline_subject.bin), "status", "--json"], probe, probe_env)
1994
            )
1995
            probe_checks = {
1996
                "returncode": result.returncode == 0,
1997
                "legacy_route": evidence["legacy_route_observed"],
1998
                "passes_two": evidence["materialization_passes_observed"] == 2,
1999
                "head": evidence["head"] == expected_head,
2000
                "manifest": bool(evidence["manifest"]),
2001
                "artifacts": expected_artifacts is not None,
2002
                "cache": normalized_cache_contract(baseline_cache) is not None,
2003
                "status": expected_status_contract is not None,
2004
                "network": baseline_proxy_stats.connection_errors == 0,
2005
            }
2006
            if not all(probe_checks.values()):
2007
                failed_checks = ",".join(key for key, passed in probe_checks.items() if not passed)
2008
                failure = f"baseline_probe_failed:{failed_checks}"
2009
                if isinstance(baseline_cache, dict) and baseline_cache.get("error"):
2010
                    failure += f":{baseline_cache['error']}"
2011
            shutil.rmtree(probe, ignore_errors=True)
2012
2013
        if failure is None and proxy is not None:
2014
            measured_remote = f"http://127.0.0.1:{proxy.address[1]}"
2015
            order_rng = random.Random(random_seed)
2016
            for run_index in range(runs):
2017
                run_subjects = list(oak_subjects)
2018
                if randomize_order:
2019
                    order_rng.shuffle(run_subjects)
2020
                for subject in run_subjects:
2021
                    destination = run_root / f"materialization-{subject.name}-r{run_index}"
2022
                    subject_env = isolated_oak_fixture_env(
2023
                        run_root / f"materialization-config-{subject.name}-r{run_index}"
2024
                    )
2025
                    proxy.reset_counts()
2026
                    row = step_row(
2027
                        subject,
2028
                        scenario,
2029
                        "legacy_selected.acquire",
2030
                        [
2031
                            str(subject.bin),
2032
                            "clone",
2033
                            "--json",
2034
                            "--allow-legacy-scope",
2035
                            "--remote",
2036
                            measured_remote,
2037
                            "--branch",
2038
                            selected_branch,
2039
                            repo_spec,
2040
                            str(destination),
2041
                        ],
2042
                        fixture_root,
2043
                        run_index,
2044
                        {
2045
                            **fixture_meta,
2046
                            "run_subject_order": [item.name for item in run_subjects],
2047
                        },
2048
                        admitted,
2049
                        preserve_stdout=True,
2050
                        env=subject_env,
2051
                        timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
2052
                    )
2053
                    receipt = _parse_json_object(row.get("stdout"))
2054
                    evidence = legacy_selected_receipt_evidence(receipt or {})
2055
                    proxy_stats = proxy.snapshot()
2056
                    cache_before_status = stat_cache_snapshot(destination)
2057
                    artifacts = normalized_oak_artifacts(destination)
2058
                    trial_meta = {
2059
                        **fixture_meta,
2060
                        "run_subject_order": [item.name for item in run_subjects],
2061
                    }
2062
                    status_row = step_row(
2063
                        subject,
2064
                        scenario,
2065
                        "legacy_selected.first_status",
2066
                        [str(subject.bin), "status", "--json"],
2067
                        destination,
2068
                        run_index,
2069
                        trial_meta,
2070
                        admitted,
2071
                        preserve_stdout=True,
2072
                        env=subject_env,
2073
                        timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
2074
                    )
2075
                    status = normalized_status(_parse_json_object(status_row.get("stdout")) or {})
2076
                    commit_row = step_row(
2077
                        subject,
2078
                        scenario,
2079
                        "legacy_selected.first_noop_commit",
2080
                        [str(subject.bin), "commit"],
2081
                        destination,
2082
                        run_index,
2083
                        trial_meta,
2084
                        admitted,
2085
                        env=subject_env,
2086
                        timeout_seconds=INTERRUPTED_PUBLICATION_PROCESS_TIMEOUT_S,
2087
                    )
2088
                    history = normalized_history(
2089
                        json_command([str(subject.bin), "log", "--json"], destination, subject_env)
2090
                    )
2091
                    head_result = run_bounded_harness_command(
2092
                        [str(subject.bin), "hash"], destination, env=subject_env
2093
                    )
2094
                    local_head = head_result.stdout.strip() if head_result.returncode == 0 else None
2095
                    worktree = normalized_worktree_snapshot(destination) if destination.exists() else None
2096
                    advertised_head = remote_branch_head(direct_remote, repo_spec, selected_branch)
2097
                    subject_sha256 = binary_sha256(subject.bin)
2098
                    expected_passes = 2 if subject_sha256 == baseline_binary_sha256 else 1
2099
                    receipt_contract = legacy_selected_receipt_contract(evidence)
2100
                    network_contract = {
2101
                        "connections": proxy_stats.connections,
2102
                        "receipt": receipt_contract,
2103
                    }
2104
                    cache_contract = normalized_cache_contract(cache_before_status)
2105
                    baseline_cache_contract = normalized_cache_contract(baseline_cache)
2106
                    expected_cache_valid = subject_sha256 != baseline_binary_sha256
2107
                    cache_valid = bool(
2108
                        isinstance(cache_before_status, dict)
2109
                        and cache_before_status.get("all_metadata_valid") is expected_cache_valid
2110
                    )
2111
                    status_ok = status == expected_status_contract
2112
                    artifacts_ok = artifacts == expected_artifacts
2113
                    network_ok = network_contract == baseline_network_contract
2114
                    oracle_ok = bool(
2115
                        row_returncode(row) == 0
2116
                        and row_returncode(status_row) == 0
2117
                        and row_returncode(commit_row) == 0
2118
                        and evidence["legacy_route_observed"]
2119
                        and evidence["snapshot_bound"] is False
2120
                        and evidence["materialization_passes_observed"] == expected_passes
2121
                        and evidence["head"] == expected_head == local_head == advertised_head
2122
                        and evidence["manifest"] == baseline_receipt_contract["manifest"]
2123
                        and evidence["written_paths"] == len(expected_worktree)
2124
                        and receipt_contract == baseline_receipt_contract
2125
                        and history == expected_history
2126
                        and worktree == expected_worktree
2127
                        and status_ok
2128
                        and artifacts_ok
2129
                        and cache_contract == baseline_cache_contract
2130
                        and cache_valid
2131
                        and network_ok
2132
                        and proxy_stats.connection_errors == 0
2133
                    )
2134
                    row.update(
2135
                        {
2136
                            **evidence,
2137
                            "network_connections_observed": proxy_stats.connections,
2138
                            "network_client_to_server_bytes_observed": proxy_stats.client_to_upstream_bytes,
2139
                            "network_server_to_client_bytes_observed": proxy_stats.upstream_to_client_bytes,
2140
                            "network_connection_errors": proxy_stats.connection_errors,
2141
                            "normalized_network_work": network_contract,
2142
                            "oracle_network_work_matches_baseline": network_ok,
2143
                            "oracle_head_matches": expected_head == local_head == advertised_head,
2144
                            "oracle_manifest_matches": evidence["manifest"]
2145
                            == baseline_receipt_contract["manifest"],
2146
                            "oracle_receipt_contract_matches": receipt_contract
2147
                            == baseline_receipt_contract,
2148
                            "oracle_history_matches": history == expected_history,
2149
                            "oracle_worktree_bytes_and_modes_match": worktree == expected_worktree,
2150
                            "oracle_clean_status": status_ok,
2151
                            "oracle_local_artifact_names_match": artifacts_ok,
2152
                            "oracle_stat_cache_contract_matches": cache_contract
2153
                            == baseline_cache_contract,
2154
                            "stat_cache_metadata_valid_before_status": (
2155
                                cache_before_status.get("all_metadata_valid")
2156
                                if isinstance(cache_before_status, dict)
2157
                                else None
2158
                            ),
2159
                            "stat_cache_rows_before_status": (
2160
                                len(cache_before_status.get("rows", []))
2161
                                if isinstance(cache_before_status, dict)
2162
                                and isinstance(cache_before_status.get("rows"), list)
2163
                                else None
2164
                            ),
2165
                            "stat_cache_expected_valid_before_status": expected_cache_valid,
2166
                            "oracle_clone_residue_absent": artifacts_ok,
2167
                            "oracle_passed": oracle_ok,
2168
                            "repository_cache_state": "fresh_destination_no_shared_object_store",
2169
                        }
2170
                    )
2171
                    for followup_row in (status_row, commit_row):
2172
                        followup_row.update(
2173
                            {
2174
                                "materialization_timing_controls_met": timing_controls_met,
2175
                                "stat_cache_expected_valid_before_first_status": expected_cache_valid,
2176
                                "oracle_passed": oracle_ok,
2177
                            }
2178
                        )
2179
                    if not oracle_ok:
2180
                        row["returncode"] = 1
2181
                        row["stderr"] = "legacy_selected_materialization_oracle_failed"
2182
                        status_row["returncode"] = 1
2183
                        commit_row["returncode"] = 1
2184
                    total = phase_total_row(
2185
                        [row, status_row, commit_row],
2186
                        subject,
2187
                        scenario,
2188
                        run_index,
2189
                        trial_meta,
2190
                        "legacy_selected_clone_materialization.total",
2191
                    )
2192
                    total.update(
2193
                        {
2194
                            key: row[key]
2195
                            for key in (
2196
                                "materialization_passes_observed",
2197
                                "network_connections_observed",
2198
                                "network_client_to_server_bytes_observed",
2199
                                "network_server_to_client_bytes_observed",
2200
                                "oracle_passed",
2201
                                "oracle_head_matches",
2202
                                "oracle_manifest_matches",
2203
                                "oracle_receipt_contract_matches",
2204
                                "oracle_history_matches",
2205
                                "oracle_worktree_bytes_and_modes_match",
2206
                                "oracle_clean_status",
2207
                                "oracle_local_artifact_names_match",
2208
                                "oracle_stat_cache_contract_matches",
2209
                                "stat_cache_metadata_valid_before_status",
2210
                                "stat_cache_rows_before_status",
2211
                                "oracle_clone_residue_absent",
2212
                                "oracle_network_work_matches_baseline",
2213
                                "serve_binary_sha256",
2214
                                "serve_source_head",
2215
                                "fixture_id",
2216
                                "os_cache_state",
2217
                                "materialization_timing_controls_met",
2218
                            )
2219
                        }
2220
                    )
2221
                    total.update(
2222
                        {
2223
                            "clone_elapsed_ms": row.get("elapsed_ms"),
2224
                            "first_status_elapsed_ms": status_row.get("elapsed_ms"),
2225
                            "first_noop_commit_elapsed_ms": commit_row.get("elapsed_ms"),
2226
                        }
2227
                    )
2228
                    rows.extend([row, status_row, commit_row, total])
2229
                    shutil.rmtree(destination, ignore_errors=True)
2230
    finally:
2231
        if proxy is not None:
2232
            proxy.stop()
2233
        if server is not None:
2234
            stop_owned_process(server)
2235
        shutil.rmtree(fixture_root, ignore_errors=True)
2236
2237
    if failure is not None:
2238
        rows.append(
2239
            harness_step_row(
2240
                setup_subject,
2241
                scenario,
2242
                "scenario.fixture_failure",
2243
                0,
2244
                fixture_meta,
2245
                0.0,
2246
                returncode=1,
2247
            )
2248
        )
2249
        rows[-1]["stderr"] = failure
2250
    return rows
2251
2252
2253
def run_git_loop(
2254
    subject: Subject,
2255
    remote: oakbench_remotes.RemoteResolution,
2256
    run_root: Path,
2257
    run_index: int,
2258
    metadata: dict[str, Any],
2259
    admitted: int,
2260
) -> list[dict[str, Any]]:
2261
    scenario = "task_loop"
2262
    meta = {
2263
        **metadata,
2264
        **remote.row_fields(),
2265
        "benchmark_track": (
2266
            "core-equivalent"
2267
            if remote.transport == oakbench_remotes.TRANSPORT_LOCAL_FILE
2268
            else "agent-default"
2269
        ),
2270
    }
2271
    vcs = str(subject.bin)
2272
    rows: list[dict[str, Any]] = []
2273
2274
    seed_error = ensure_git_remote_seeded(subject, remote, run_root)
2275
    if seed_error:
2276
        return [skip_row(subject, scenario, run_index, meta, seed_error)]
2277
2278
    # Untimed: the base clone is the long-lived checkout worktrees hang off;
2279
    # an agent fleet pays it once, not per task.
2280
    base = run_root / f"tl-git-base-r{run_index}"
2281
    clone = run_untimed([vcs, "clone", "-q", str(remote.repo), str(base)], run_root)
2282
    if clone.returncode != 0:
2283
        return [skip_row(subject, scenario, run_index, meta, f"base clone failed: {clone.stderr.strip()[:200]}")]
2284
2285
    branches: dict[str, str] = {}
2286
    payloads: dict[str, str] = {}
2287
    for phase in PHASES:
2288
        workspace = run_root / f"tl-{subject.name}-r{run_index}-{phase}"
2289
        branch = oakbench_remotes.disposable_branch(
2290
            str(metadata["bench_id"]), f"taskloop-{phase}", subject.name, run_index
2291
        )
2292
        branches[phase] = branch
2293
        payload = f"payload {phase} run={run_index} bench={metadata['bench_id']}"
2294
        payloads[phase] = payload
2295
        acquire = step_row(
2296
            subject, scenario, f"{phase}.acquire",
2297
            [vcs, "worktree", "add", str(workspace), "-b", branch], base, run_index, meta, admitted,
2298
        )
2299
        rows.append(acquire)
2300
        if int(acquire["returncode"]) != 0:
2301
            return rows
2302
        rows.append(edit_step(subject, scenario, f"{phase}.edit", workspace, payload, run_index, meta, admitted))
2303
        # Agent-default snapshot: git pays stage+commit as two calls.
2304
        rows.append(step_row(subject, scenario, f"{phase}.snapshot.add", [vcs, "add", "."], workspace, run_index, meta, admitted))
2305
        rows.append(
2306
            step_row(
2307
                subject, scenario, f"{phase}.snapshot.commit",
2308
                [vcs, "commit", "-q", "-m", f"bench task loop {metadata['bench_id']} r{run_index} {phase}"],
2309
                workspace, run_index, meta, admitted,
2310
            )
2311
        )
2312
        rows.append(
2313
            step_row(
2314
                subject, scenario, f"{phase}.publish",
2315
                [vcs, "push", "-q", "-u", "origin", branch], workspace, run_index, meta, admitted,
2316
            )
2317
        )
2318
        # No task.describe: git's description is the commit message already
2319
        # paid in {phase}.snapshot.commit (asymmetry documented in the lane doc).
2320
        rows.append(
2321
            step_row(
2322
                subject, scenario, f"{phase}.end",
2323
                [vcs, "worktree", "remove", str(workspace)], base, run_index, meta, admitted,
2324
            )
2325
        )
2326
2327
    # Untimed oracle: payloads visible on the remote branches.
2328
    oracle_ok: bool | None = True
2329
    for phase in PHASES:
2330
        fetch = run_untimed([vcs, "fetch", "-q", "origin", branches[phase]], base)
2331
        show = run_untimed([vcs, "show", f"FETCH_HEAD:task-notes.md"], base)
2332
        if fetch.returncode != 0 or payloads[phase] not in show.stdout:
2333
            oracle_ok = False
2334
    rows.append(
2335
        {
2336
            **phase_total_row(rows, subject, scenario, run_index, meta, "task_loop.total"),
2337
            "oracle_payload_on_remote_branch": oracle_ok,
2338
            "workspace_branches": branches,
2339
            "measurement_source": "fetch_plus_show_grep",
2340
        }
2341
    )
2342
    return rows
2343
2344
2345
def loop_summary_fields(rows: list[dict[str, Any]]) -> dict[str, Any]:
2346
    """followup-vs-task warm deltas computed from this run's step rows."""
2347
    def op_ms(operation: str) -> float | None:
2348
        for row in rows:
2349
            if row.get("operation") == operation:
2350
                return float(row.get("elapsed_ms", 0.0))
2351
        return None
2352
2353
    def phase_ms(prefix: str) -> float:
2354
        return sum(
2355
            float(row.get("elapsed_ms", 0.0))
2356
            for row in rows
2357
            if str(row.get("operation", "")).startswith(prefix + ".")
2358
        )
2359
2360
    task_acquire = op_ms("task.acquire")
2361
    followup_acquire = op_ms("followup.acquire")
2362
    fields: dict[str, Any] = {
2363
        "task_phase_ms": round(phase_ms("task"), 3),
2364
        "followup_phase_ms": round(phase_ms("followup"), 3),
2365
    }
2366
    if task_acquire is not None and followup_acquire is not None:
2367
        fields["followup_warm_delta_ms"] = round(followup_acquire - task_acquire, 3)
2368
        fields["acquire_ms_task"] = round(task_acquire, 3)
2369
        fields["acquire_ms_followup"] = round(followup_acquire, 3)
2370
    else:
2371
        fields["followup_warm_delta_ms"] = None
2372
    return fields
2373
2374
2375
def summary_text(rows: list[dict[str, Any]]) -> str:
2376
    totals = [row for row in rows if row.get("operation") == "task_loop.total"]
2377
    lines = [
2378
        "# Task Loop Summary",
2379
        "",
2380
        "Whole-task cost of the signature motion: acquire workspace -> edit -> snapshot ->",
2381
        "publish -> describe -> end, then a follow-up task in a fresh workspace.",
2382
        "Command sequences are NOT step-equivalent across subjects",
2383
        "(oak pays desc; git's description is its commit message). Latency across unlike",
2384
        "transports is never comparable; scripted call equivalents and estimated token proxies are.",
2385
        "",
2386
        "| Subject | Run | Transport | Wall ms | Scripted call equivalents | Cost-wt tokens (env) | Warm acquire delta ms | Oracle |",
2387
        "| --- | ---: | --- | ---: | ---: | ---: | ---: | --- |",
2388
    ]
2389
    for row in totals:
2390
        oracle = row.get("oracle_payload_on_remote_branch")
2391
        lines.append(
2392
            "| `{}` | {} | {} | {} | {} | {} | {} | {} |".format(
2393
                row["subject"],
2394
                row["run"],
2395
                row.get("remote_transport", "unmeasured"),
2396
                fmt_ms(row.get("elapsed_ms")),
2397
                row.get("tool_call_count", "unmeasured"),
2398
                row.get("estimated_cost_weighted_tokens_with_envelope", "unmeasured"),
2399
                row.get("followup_warm_delta_ms", "unmeasured"),
2400
                "pass" if oracle else ("FAIL" if oracle is False else "unmeasured"),
2401
            )
2402
        )
2403
    skips = [
2404
        row for row in rows
2405
        if row.get("skipped") and row.get("scenario") == TASK_LOOP_SCENARIO
2406
    ]
2407
    for row in skips:
2408
        lines.append(f"| `{row['subject']}` | {row['run']} | skipped | | | | | {row['skip_reason'][:70]} |")
2409
2410
    agent_totals = [
2411
        row
2412
        for row in rows
2413
        if row.get("operation") in ("agent_task.total", "agent_task_oak_local.total")
2414
    ]
2415
    if agent_totals:
2416
        lines.extend(
2417
            [
2418
                "",
2419
                "## Cold clone and warm isolated task",
2420
                "",
2421
                "Local-file Git rows compare a fresh full clone with a worktree sharing",
2422
                "the first clone's object store. Oak local rows use two independent fresh",
2423
                "clones against an owned loopback server; they do not claim a shared cache.",
2424
                "OS cache and network bytes remain unknown. Hosted CI/review are skips.",
2425
                "",
2426
                "| Subject | Run | Wall ms | Scripted call equivalents | Output bytes | Coverage | Oracle |",
2427
                "| --- | ---: | ---: | ---: | ---: | --- | --- |",
2428
            ]
2429
        )
2430
        for row in agent_totals:
2431
            lines.append(
2432
                "| `{}` | {} | {} | {} | {} | {} | {} |".format(
2433
                    row["subject"],
2434
                    row["run"],
2435
                    fmt_ms(row.get("elapsed_ms")),
2436
                    row.get("tool_call_count", "unmeasured"),
2437
                    row.get("raw_output_bytes", "unmeasured"),
2438
                    "complete" if row.get("coverage_complete") else "partial",
2439
                    "pass" if row.get("oracle_payload_on_remote_branch") else "FAIL",
2440
                )
2441
            )
2442
2443
    materialization_totals = [
2444
        row
2445
        for row in rows
2446
        if row.get("operation") == "legacy_selected_clone_materialization.total"
2447
    ]
2448
    if materialization_totals:
2449
        lines.extend(
2450
            [
2451
                "",
2452
                "## Legacy selected-branch materialization",
2453
                "",
2454
                "One fixed loopback Serve and one immutable fixture are shared across subjects.",
2455
                "Fresh destinations do not imply a cold OS page cache. Timing claims require",
2456
                "separate interleaved A/A noise and A/B campaigns.",
2457
                "",
2458
                "| Subject | Run | Clone ms | First status ms | First no-op commit ms | Passes | Harness controls met | Oracle |",
2459
                "| --- | ---: | ---: | ---: | ---: | ---: | --- | --- |",
2460
            ]
2461
        )
2462
        for row in materialization_totals:
2463
            lines.append(
2464
                "| `{}` | {} | {} | {} | {} | {} | {} | {} |".format(
2465
                    row["subject"],
2466
                    row["run"],
2467
                    fmt_ms(row.get("clone_elapsed_ms")),
2468
                    fmt_ms(row.get("first_status_elapsed_ms")),
2469
                    fmt_ms(row.get("first_noop_commit_elapsed_ms")),
2470
                    row.get("materialization_passes_observed", "unmeasured"),
2471
                    "yes" if row.get("materialization_timing_controls_met") else "no",
2472
                    "pass" if row.get("oracle_passed") else "FAIL",
2473
                )
2474
            )
2475
2476
    interrupted_totals = [
2477
        row for row in rows if row.get("operation") == "interrupted_publication.total"
2478
    ]
2479
    if interrupted_totals:
2480
        lines.extend(
2481
            [
2482
                "",
2483
                "## Interrupted publication",
2484
                "",
2485
                "A loopback fixture validates the request framing and drops the response.",
2486
                "Fixture delivery knowledge is reported separately from the CLI's own outcome",
2487
                "claim. Request receipt is not durability proof; the harness never retries.",
2488
                "",
2489
                "| Subject | Run | Wall ms | Scripted call equivalents | Output bytes | CLI outcome | Fixture delivery | POSTs | Request bytes | Head/work preserved |",
2490
                "| --- | ---: | ---: | ---: | ---: | --- | --- | ---: | ---: | --- |",
2491
            ]
2492
        )
2493
        for row in interrupted_totals:
2494
            lines.append(
2495
                "| `{}` | {} | {} | {} | {} | `{}` | `{}` | {} | {} | {}/{} |".format(
2496
                    row["subject"],
2497
                    row["run"],
2498
                    fmt_ms(row.get("elapsed_ms")),
2499
                    row.get("tool_call_count", "unmeasured"),
2500
                    row.get("raw_output_bytes", "unmeasured"),
2501
                    row.get("publication_outcome", "unmeasured"),
2502
                    row.get("fixture_delivery_state", "unmeasured"),
2503
                    row.get("mutation_requests_observed", "unmeasured"),
2504
                    row.get("network_request_bytes_observed", "unmeasured"),
2505
                    "yes" if row.get("local_head_preserved") else "no",
2506
                    "yes" if row.get("local_work_preserved") else "no",
2507
                )
2508
            )
2509
    return "\n".join(lines) + "\n"
2510
2511
2512
def fmt_ms(value: Any) -> str:
2513
    try:
2514
        return f"{float(value):.0f}"
2515
    except (TypeError, ValueError):
2516
        return "unmeasured"
2517
2518
2519
def main() -> int:
2520
    args = parse_args()
2521
    scenarios = [item.strip() for item in args.scenarios.split(",") if item.strip()]
2522
    known_scenarios = {
2523
        TASK_LOOP_SCENARIO,
2524
        AGENT_TASK_SCENARIO,
2525
        AGENT_TASK_OAK_LOCAL_SCENARIO,
2526
        LEGACY_SELECTED_MATERIALIZATION_SCENARIO,
2527
        INTERRUPTED_PUBLICATION_SCENARIO,
2528
    }
2529
    unknown_scenarios = sorted(set(scenarios) - known_scenarios)
2530
    if unknown_scenarios:
2531
        raise SystemExit(
2532
            "Unknown scenarios: " + ", ".join(unknown_scenarios)
2533
            + "; valid: " + ", ".join(sorted(known_scenarios))
2534
        )
2535
    subjects = load_subjects(args)
2536
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
2537
    args.results.mkdir(parents=True, exist_ok=True)
2538
    args.workdir.mkdir(parents=True, exist_ok=True)
2539
    run_root = args.workdir / "runs" / timestamp
2540
    run_root.mkdir(parents=True, exist_ok=True)
2541
2542
    metadata = {
2543
        "bench_id": timestamp,
2544
        "profile": "task-loop",
2545
        "benchmark_track": "agent-default",
2546
        "timestamp_utc": timestamp,
2547
        "host": platform.node(),
2548
        "platform": platform.platform(),
2549
        "machine": platform.machine(),
2550
        "python": platform.python_version(),
2551
        "env_isolation_version": ENV_ISOLATION_VERSION,
2552
        "subject_versions": subject_versions(subjects),
2553
        "subject_details": subject_details(subjects),
2554
        "source": source_metadata(args.oak_repo),
2555
        "git_remote_mode": args.git_remote,
2556
        "trial_order_randomized": args.randomize_subject_order,
2557
        "trial_order_seed": args.random_seed if args.randomize_subject_order else None,
2558
    }
2559
2560
    rows: list[dict[str, Any]] = []
2561
    with measurement_lock("task_loop") as lock_info:
2562
        metadata["measurement_lock_wait_ms"] = lock_info.wait_ms
2563
        metadata["measurement_lock"] = "held" if lock_info.enabled else "disabled"
2564
        order_rng = random.Random(args.random_seed)
2565
        if LEGACY_SELECTED_MATERIALIZATION_SCENARIO in scenarios:
2566
            rows.extend(
2567
                run_legacy_selected_materialization_campaign(
2568
                    subjects,
2569
                    args.oak_serve_bin,
2570
                    args.oak_serve_source,
2571
                    args.oak_serve_source_head,
2572
                    args.materialization_baseline_subject,
2573
                    run_root,
2574
                    args.runs,
2575
                    metadata,
2576
                    args.admitted_output_chars,
2577
                    args.randomize_subject_order,
2578
                    args.random_seed,
2579
                    args.materialization_timing_controls_met,
2580
                )
2581
            )
2582
        ordinary_scenarios = [
2583
            scenario for scenario in scenarios if scenario != LEGACY_SELECTED_MATERIALIZATION_SCENARIO
2584
        ]
2585
        for run_index in range(args.runs):
2586
            run_subjects = list(subjects)
2587
            if args.randomize_subject_order:
2588
                order_rng.shuffle(run_subjects)
2589
            run_metadata = {
2590
                **metadata,
2591
                "run_subject_order": [subject.name for subject in run_subjects],
2592
            }
2593
            for subject in run_subjects:
2594
                for scenario in ordinary_scenarios:
2595
                    print(f"[run] {scenario} run={run_index} subject={subject.name}", flush=True)
2596
                    if scenario == INTERRUPTED_PUBLICATION_SCENARIO:
2597
                        rows.extend(
2598
                            run_interrupted_publication(
2599
                                subject, run_root, run_index, run_metadata, args.admitted_output_chars
2600
                            )
2601
                        )
2602
                        continue
2603
                    if scenario == AGENT_TASK_SCENARIO:
2604
                        if subject.kind != "git":
2605
                            rows.append(
2606
                                unmeasured_step_row(
2607
                                    subject,
2608
                                    scenario,
2609
                                    "agent_task.skipped",
2610
                                    run_index,
2611
                                    run_metadata,
2612
                                    "Oak end-to-end publication and exact CI require an explicitly configured disposable staging lane; production mutation is forbidden",
2613
                                )
2614
                            )
2615
                            continue
2616
                        remote_create_start = time.perf_counter()
2617
                        remote = oakbench_remotes.make_git_bare_remote(
2618
                            subject.bin, run_root, f"agent-task-remote-r{run_index}.git"
2619
                        )
2620
                        remote_create_ms = (time.perf_counter() - remote_create_start) * 1000
2621
                        rows.extend(
2622
                            run_git_agent_task_baseline(
2623
                                subject,
2624
                                remote,
2625
                                run_root,
2626
                                run_index,
2627
                                run_metadata,
2628
                                args.admitted_output_chars,
2629
                                remote_create_ms,
2630
                            )
2631
                        )
2632
                        continue
2633
                    if scenario == AGENT_TASK_OAK_LOCAL_SCENARIO:
2634
                        if subject.kind != "oak":
2635
                            rows.append(
2636
                                unmeasured_step_row(
2637
                                    subject,
2638
                                    scenario,
2639
                                    "agent_task_oak_local.skipped",
2640
                                    run_index,
2641
                                    run_metadata,
2642
                                    "owned local oak serve scenario applies only to Oak subjects",
2643
                                )
2644
                            )
2645
                            continue
2646
                        rows.extend(
2647
                            run_oak_local_agent_task_baseline(
2648
                                subject,
2649
                                run_root,
2650
                                run_index,
2651
                                run_metadata,
2652
                                args.admitted_output_chars,
2653
                            )
2654
                        )
2655
                        continue
2656
                    if subject.kind == "git":
2657
                        if args.git_remote == "github":
2658
                            remote = oakbench_remotes.resolve_git_github_remote()
2659
                        else:
2660
                            remote = oakbench_remotes.make_git_bare_remote(
2661
                                subject.bin, run_root, f"task-loop-remote-r{run_index}.git"
2662
                            )
2663
                        if not remote.resolved:
2664
                            rows.append(skip_row(subject, TASK_LOOP_SCENARIO, run_index, run_metadata, remote.skip_reason or ""))
2665
                            continue
2666
                        subject_rows = run_git_loop(subject, remote, run_root, run_index, run_metadata, args.admitted_output_chars)
2667
                    else:
2668
                        remote = oakbench_remotes.resolve_oak_remote("sync")
2669
                        if not remote.resolved:
2670
                            rows.append(skip_row(subject, TASK_LOOP_SCENARIO, run_index, run_metadata, remote.skip_reason or ""))
2671
                            continue
2672
                        subject_rows = run_oak_loop(subject, remote, run_root, run_index, run_metadata, args.admitted_output_chars)
2673
                    total = next((row for row in subject_rows if row.get("operation") == "task_loop.total"), None)
2674
                    if total is not None:
2675
                        total.update(loop_summary_fields(subject_rows))
2676
                    rows.extend(subject_rows)
2677
2678
    store = ResultsStore(args.results, lane="task-loop")
2679
    raw_path, summary_path = store.write(timestamp, rows, summary_text(rows))
2680
    if not args.keep_workdirs:
2681
        shutil.rmtree(run_root, ignore_errors=True)
2682
    print(f"[result] {raw_path}")
2683
    print(f"[summary] {summary_path}")
2684
    return 1 if has_lane_failures(rows) else 0
2685
2686
2687
if __name__ == "__main__":
2688
    raise SystemExit(main())