Viewing files at commit dc75e12cb299Commit detailsView latest

Browse files
scripts/oakbench/command_semantics.py 92 lines · 3.7 KB · python
1
"""The command-semantics contract, loaded from config/command_semantics.json.
2
3
This is the fairness guarantee of the whole suite: which exact command each
4
subject kind runs per operation and track. It is data, not code, so outsiders
5
can audit it by reading one JSON file, and a fairness decision (for example
6
adopting `oak diff --stat` as agent-default once published agent docs do) is a
7
one-line data edit plus a version bump that every row records (ADR-0005).
8
"""
9
10
from __future__ import annotations
11
12
import json
13
from pathlib import Path
14
from typing import Any
15
16
ROOT = Path(__file__).resolve().parents[2]
17
DEFAULT_CONFIG = ROOT / "config" / "command_semantics.json"
18
TRACKS = ("agent-default", "core-equivalent")
19
20
21
class CommandSemantics:
22
    def __init__(self, config_path: Path = DEFAULT_CONFIG) -> None:
23
        data = json.loads(config_path.read_text())
24
        self.version: str = str(data["version"])
25
        self._tracks: dict[str, Any] = data["harness_tracks"]
26
        self._agent_native_json_probes: dict[str, Any] = data.get("agent_native_json_probes") or {}
27
28
    def _kind(self, kind: str) -> dict[str, Any]:
29
        if kind not in self._tracks:
30
            raise ValueError(f"unknown subject kind for command semantics: {kind!r}")
31
        return self._tracks[kind]
32
33
    def init_args(self, kind: str) -> list[str]:
34
        return list(self._kind(kind)["init"])
35
36
    def stage_args(self, kind: str) -> list[str] | None:
37
        value = self._kind(kind)["stage"]
38
        return list(value) if value is not None else None
39
40
    def commit_args(self, kind: str, message: str) -> list[str]:
41
        return [part.replace("{message}", message) for part in self._kind(kind)["commit"]]
42
43
    def branch_create_args(self, kind: str) -> list[str]:
44
        return list(self._kind(kind)["branch_create"])
45
46
    def status_args(self, kind: str, track: str) -> list[str]:
47
        return list(self._kind(kind)["status"][track])
48
49
    def diff_args(self, kind: str, track: str) -> list[str]:
50
        return list(self._kind(kind)["diff"][track])
51
52
    def agent_native_json_probes(self, kind: str, branch_name: str) -> dict[str, dict[str, Any]]:
53
        self._kind(kind)
54
        probes = self._agent_native_json_probes.get(kind) or {}
55
        result: dict[str, dict[str, Any]] = {}
56
        for operation, spec in probes.items():
57
            result[str(operation)] = {
58
                "args": [
59
                    str(part).replace("{branch}", branch_name)
60
                    for part in spec.get("args", [])
61
                ],
62
                "help_args": [str(part) for part in spec.get("help_args", [])],
63
                "required_fields": [str(part) for part in spec.get("required_fields", [])],
64
            }
65
            if spec.get("minimum_oak_version") is not None:
66
                result[str(operation)]["minimum_oak_version"] = str(spec["minimum_oak_version"])
67
        return result
68
69
    def snapshot_commands(self, bin_path: str, kind: str, message: str) -> list[list[str]]:
70
        """Full snapshot sequence: git pays stage+commit as two calls, oak one."""
71
        commands: list[list[str]] = []
72
        stage = self.stage_args(kind)
73
        if stage is not None:
74
            commands.append([bin_path, *stage])
75
        commands.append([bin_path, *self.commit_args(kind, message)])
76
        return commands
77
78
    def status_display(self, kind: str, track: str) -> str:
79
        return " ".join([kind, *self.status_args(kind, track)])
80
81
    def diff_display(self, kind: str, track: str) -> str:
82
        return " ".join([kind, *self.diff_args(kind, track)])
83
84
85
_semantics: CommandSemantics | None = None
86
87
88
def semantics() -> CommandSemantics:
89
    global _semantics
90
    if _semantics is None:
91
        _semantics = CommandSemantics()
92
    return _semantics