Viewing files at commit 085949193d78Commit detailsView latest

Browse files
scripts/oakbench/command_semantics.py 103 lines · 4.2 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._output_formats = data.get("output_formats", {})
27
        self._agent_native_json_probes: dict[str, Any] = data.get("agent_native_json_probes") or {}
28
29
    def _kind(self, kind: str) -> dict[str, Any]:
30
        if kind not in self._tracks:
31
            raise ValueError(f"unknown subject kind for command semantics: {kind!r}")
32
        return self._tracks[kind]
33
34
    def init_args(self, kind: str) -> list[str]:
35
        return list(self._kind(kind)["init"])
36
37
    def stage_args(self, kind: str) -> list[str] | None:
38
        value = self._kind(kind)["stage"]
39
        return list(value) if value is not None else None
40
41
    def commit_args(self, kind: str, message: str) -> list[str]:
42
        return [part.replace("{message}", message) for part in self._kind(kind)["commit"]]
43
44
    def branch_create_args(self, kind: str) -> list[str]:
45
        return list(self._kind(kind)["branch_create"])
46
47
    def status_args(self, kind: str, track: str) -> list[str]:
48
        return list(self._kind(kind)["status"][track])
49
50
    def diff_args(self, kind: str, track: str) -> list[str]:
51
        return list(self._kind(kind)["diff"][track])
52
53
    def output_format(self, kind: str, operation: str, track: str) -> str:
54
        family = "status" if operation.startswith("status.") else "diff"
55
        if operation == "diff.full":
56
            track = "core-equivalent"
57
        value = self._output_formats[kind][family][track]
58
        if value not in {"status", "stat", "diff", "porcelain-z", "oak-json"}:
59
            raise ValueError(f"unsupported configured output format: {value!r}")
60
        return value
61
62
    def agent_native_json_probes(self, kind: str, branch_name: str) -> dict[str, dict[str, Any]]:
63
        self._kind(kind)
64
        probes = self._agent_native_json_probes.get(kind) or {}
65
        result: dict[str, dict[str, Any]] = {}
66
        for operation, spec in probes.items():
67
            result[str(operation)] = {
68
                "output_format": str(spec.get("output_format", "json")),
69
                "args": [
70
                    str(part).replace("{branch}", branch_name)
71
                    for part in spec.get("args", [])
72
                ],
73
                "help_args": [str(part) for part in spec.get("help_args", [])],
74
                "required_fields": [str(part) for part in spec.get("required_fields", [])],
75
            }
76
            if spec.get("minimum_oak_version") is not None:
77
                result[str(operation)]["minimum_oak_version"] = str(spec["minimum_oak_version"])
78
        return result
79
80
    def snapshot_commands(self, bin_path: str, kind: str, message: str) -> list[list[str]]:
81
        """Full snapshot sequence: git pays stage+commit as two calls, oak one."""
82
        commands: list[list[str]] = []
83
        stage = self.stage_args(kind)
84
        if stage is not None:
85
            commands.append([bin_path, *stage])
86
        commands.append([bin_path, *self.commit_args(kind, message)])
87
        return commands
88
89
    def status_display(self, kind: str, track: str) -> str:
90
        return " ".join([kind, *self.status_args(kind, track)])
91
92
    def diff_display(self, kind: str, track: str) -> str:
93
        return " ".join([kind, *self.diff_args(kind, track)])
94
95
96
_semantics: CommandSemantics | None = None
97
98
99
def semantics() -> CommandSemantics:
100
    global _semantics
101
    if _semantics is None:
102
        _semantics = CommandSemantics()
103
    return _semantics