Viewing files at commit 21316fe59317Commit detailsView latest

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