From 27e1f9be4b9317945f51e8e5d42ddde8545b4cd8 Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:19 +0200 Subject: [PATCH 1/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- repo_intelligence/hpl_binding.py | 202 +++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 repo_intelligence/hpl_binding.py diff --git a/repo_intelligence/hpl_binding.py b/repo_intelligence/hpl_binding.py new file mode 100644 index 0000000..e063771 --- /dev/null +++ b/repo_intelligence/hpl_binding.py @@ -0,0 +1,202 @@ +"""HPL/OpenHands binding payload generation for Governed Developer OS v2. + +This module translates deterministic Developer OS scopes into HPL-compatible +proposal/request/policy payloads for the already-certified OpenHands capability +surface. It does not call HPL, OpenHands, Git, or the network. +""" +from __future__ import annotations + +import hashlib +import json +import re +from pathlib import PurePosixPath + + +EFFECTS={ + "repo.read":"OPENHANDS_REPO_READ", + "test.execute":"OPENHANDS_TEST_EXECUTE", + "repo.patch":"OPENHANDS_REPO_PATCH", +} +MODULE_RE=re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*$") + + +def _canon(v:object)->str: + return json.dumps(v,sort_keys=True,separators=(",",":"),ensure_ascii=False) + + +def _sha_bytes(value:bytes)->str: + return "sha256:"+hashlib.sha256(value).hexdigest() + + +def _sha_text(value:str)->str: + return _sha_bytes(value.encode("utf-8")) + + +def _validate_relative_path(value:str)->str: + p=PurePosixPath(value) + if not value or p.is_absolute() or ".." in p.parts or "." in p.parts: + raise ValueError("path must be a bounded relative path") + return p.as_posix() + + +def python_test_path_to_module(path:str)->str: + normalized=_validate_relative_path(path) + if not normalized.endswith(".py"): + raise ValueError("test.execute v1 requires a Python test module") + module=normalized[:-3].replace("/",".") + if not MODULE_RE.fullmatch(module): + raise ValueError("test module path is not a valid dotted module") + return module + + +def _envelope(capability:str,scope:dict,effect_args:dict,*,reason:str,allow_consequential:bool)->dict: + effect=EFFECTS[capability] + proposal={ + "proposer":"openhands", + "intent":reason, + "requested_capabilities":[capability], + "inputs":{}, + "expected_effects":[effect], + } + request={ + "actor":"openhands", + "capabilities":[capability], + "scope":scope, + "reason":reason, + } + policy={ + "allowed_capabilities":[capability], + "capability_bounds":scope, + "allow_consequential":allow_consequential, + "allowed_backends":["CLASSICAL"], + "budget_steps":1, + "determinism_mode":"deterministic", + "effect_type":effect, + "effect_args":effect_args, + } + core={ + "schema_version":"1.0", + "authority_semantics":"hpl_admission_required", + "execution_authorized":False, + "proposal":proposal, + "request":request, + "policy":policy, + } + core["binding_sha256"]=_sha_text(_canon(core)) + return core + + +def build_repo_read_binding( + developer_plan:dict, + *, + conversation_id:str, + path:str, + timeout_seconds:int=10, + max_response_bytes:int=1_000_000, +)->dict: + scope_plan=developer_plan["scopes"]["repo.read"] + path=_validate_relative_path(path) + if path not in scope_plan.get("readable_paths",[]): + raise ValueError("path is outside developer-plan repo.read scope") + if not conversation_id or len(conversation_id)>64 or any(ch not in "0123456789abcdefABCDEF-" for ch in conversation_id): + raise ValueError("conversation_id is invalid") + scope={ + "conversation_id":conversation_id, + "path":path, + "timeout_seconds":int(timeout_seconds), + "max_response_bytes":int(max_response_bytes), + } + args={ + "conversation_id":conversation_id, + "path":path, + "content_artifact":"openhands_repo_read.bin", + "receipt_artifact":"openhands_repo_read_receipt.json", + } + return _envelope( + "repo.read",scope,args, + reason=f"Developer OS bounded read of {path}", + allow_consequential=False, + ) + + +def build_test_execute_binding( + developer_plan:dict, + *, + workspace:str, + test_path:str, + timeout_seconds:int=120, + max_response_bytes:int=1_000_000, +)->dict: + scope_plan=developer_plan["scopes"]["test.execute"] + test_path=_validate_relative_path(test_path) + if test_path not in scope_plan.get("test_paths",[]): + raise ValueError("test path is outside developer-plan test.execute scope") + module=python_test_path_to_module(test_path) + workspace=_validate_relative_path(workspace) + scope={ + "runner":"python_unittest", + "target":module, + "workspace":workspace, + "timeout_seconds":int(timeout_seconds), + "max_response_bytes":int(max_response_bytes), + } + args={ + "runner":"python_unittest", + "target":module, + "workspace":workspace, + "receipt_artifact":"openhands_test_receipt.json", + } + return _envelope( + "test.execute",scope,args, + reason=f"Developer OS bounded test execution for {module}", + allow_consequential=False, + ) + + +def build_repo_patch_binding( + developer_plan:dict, + *, + workspace:str, + path:str, + branch:str, + expected_preimage_sha256:str, + replacement_text:str, + timeout_seconds:int=20, + max_patch_bytes:int=1_000_000, +)->dict: + scope_plan=developer_plan["scopes"]["repo.patch"] + path=_validate_relative_path(path) + if path not in scope_plan.get("writable_paths",[]): + raise ValueError("path is outside developer-plan repo.patch writable scope") + workspace=_validate_relative_path(workspace) + if not branch or branch in {"main","master"}: + raise ValueError("repo.patch requires a non-protected branch") + if not isinstance(replacement_text,str): + raise ValueError("replacement_text must be text") + if not expected_preimage_sha256.startswith("sha256:") or len(expected_preimage_sha256)!=71: + raise ValueError("expected_preimage_sha256 must be a sha256: digest") + replacement_bytes=replacement_text.encode("utf-8") + if len(replacement_bytes)>int(max_patch_bytes): + raise ValueError("replacement exceeds max_patch_bytes") + replacement_sha256=_sha_bytes(replacement_bytes) + scope={ + "workspace":workspace, + "path":path, + "branch":branch, + "expected_preimage_sha256":expected_preimage_sha256, + "replacement_sha256":replacement_sha256, + "timeout_seconds":int(timeout_seconds), + "max_patch_bytes":int(max_patch_bytes), + } + args={ + "workspace":workspace, + "path":path, + "branch":branch, + "replacement_text":replacement_text, + "receipt_artifact":"openhands_repo_patch_receipt.json", + } + return _envelope( + "repo.patch",scope,args, + reason=f"Developer OS bounded complete-file replacement for {path}", + allow_consequential=True, + ) From a8b8cfb8fe75834e96b44ea9d3b17897c78fe913 Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:24 +0200 Subject: [PATCH 2/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- tests/test_hpl_binding.py | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/test_hpl_binding.py diff --git a/tests/test_hpl_binding.py b/tests/test_hpl_binding.py new file mode 100644 index 0000000..47c5679 --- /dev/null +++ b/tests/test_hpl_binding.py @@ -0,0 +1,83 @@ +from pathlib import Path +from repo_intelligence.developer_os import build_developer_plan +from repo_intelligence.graph import build_repository_graph +from repo_intelligence.hpl_binding import ( + build_repo_patch_binding, + build_repo_read_binding, + build_test_execute_binding, + python_test_path_to_module, +) +from repo_intelligence.impact import analyze_change_impact + + +def _plan(tmp_path:Path): + (tmp_path/"src").mkdir() + (tmp_path/"tests").mkdir() + (tmp_path/"src"/"calc.py").write_text("def add(a,b):\n return a+b\n",encoding="utf-8") + (tmp_path/"tests"/"test_calc.py").write_text( + "from src.calc import add\n\ndef test_add():\n assert add(1,2)==3\n", + encoding="utf-8", + ) + graph=build_repository_graph(tmp_path) + impact=analyze_change_impact(graph,["src/calc.py"]) + return build_developer_plan(graph,impact,"Fix calc") + + +def test_read_binding_matches_hpl_shape(tmp_path:Path): + plan=_plan(tmp_path) + binding=build_repo_read_binding( + plan,conversation_id="123e4567-e89b-12d3-a456-426614174000",path="src/calc.py" + ) + assert binding["request"]["actor"]=="openhands" + assert binding["request"]["capabilities"]==["repo.read"] + assert binding["policy"]["effect_type"]=="OPENHANDS_REPO_READ" + assert binding["policy"]["allow_consequential"] is False + assert binding["execution_authorized"] is False + + +def test_test_binding_uses_fixed_unittest_runner(tmp_path:Path): + plan=_plan(tmp_path) + binding=build_test_execute_binding( + plan,workspace="codebase-prompting",test_path="tests/test_calc.py" + ) + assert binding["policy"]["effect_args"]["runner"]=="python_unittest" + assert binding["policy"]["effect_args"]["target"]=="tests.test_calc" + assert binding["policy"]["effect_type"]=="OPENHANDS_TEST_EXECUTE" + + +def test_patch_binding_is_consequential_and_digest_bound(tmp_path:Path): + plan=_plan(tmp_path) + binding=build_repo_patch_binding( + plan, + workspace="codebase-prompting", + path="src/calc.py", + branch="feat/fix-calc", + expected_preimage_sha256="sha256:"+"a"*64, + replacement_text="def add(a,b):\n return a+b\n", + ) + assert binding["policy"]["effect_type"]=="OPENHANDS_REPO_PATCH" + assert binding["policy"]["allow_consequential"] is True + scope=binding["request"]["scope"] + assert scope["replacement_sha256"].startswith("sha256:") + assert binding["execution_authorized"] is False + + +def test_patch_binding_refuses_dependency_scope_expansion(tmp_path:Path): + plan=_plan(tmp_path) + try: + build_repo_patch_binding( + plan, + workspace="codebase-prompting", + path="tests/test_calc.py", + branch="feat/fix-calc", + expected_preimage_sha256="sha256:"+"a"*64, + replacement_text="x=1\n", + ) + except ValueError: + pass + else: + raise AssertionError("expected ValueError") + + +def test_python_test_path_to_module(): + assert python_test_path_to_module("tests/test_example.py")=="tests.test_example" From 3f77c8254fdabc166b583f7dc105cf532bd114a6 Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:28 +0200 Subject: [PATCH 3/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md diff --git a/docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md b/docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md new file mode 100644 index 0000000..5ca94ff --- /dev/null +++ b/docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md @@ -0,0 +1,27 @@ +# Governed Developer OS HPL Binding v2 + +Phase N v2 translates deterministic Developer OS scopes into HPL-compatible +OpenHands admission payloads. + +Supported bindings: + +- `repo.read` → `OPENHANDS_REPO_READ` +- `test.execute` → `OPENHANDS_TEST_EXECUTE` +- `repo.patch` → `OPENHANDS_REPO_PATCH` + +Each binding contains: + +- ProposalEnvelope-compatible `proposal`; +- CapabilityRequest-compatible `request`; +- AgenticAdmissionPolicy-compatible `policy`; +- deterministic `binding_sha256`. + +The bridge does not import the HPL kernel and does not call OpenHands. It emits +the exact data shape required for the separate HPL admission boundary. + +For `repo.patch`, `allow_consequential=true` is emitted, but +`execution_authorized=false` remains explicit until the HPL scheduler admits +the request and mints an ExecutionToken. + +The patch binding remains one complete-file replacement for one existing file, +matching the certified kernel v1 capability. From 3a3c80364850e73f2a86e528f0daab122e2cf8ef Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:32 +0200 Subject: [PATCH 4/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- repo_intelligence/cli.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/repo_intelligence/cli.py b/repo_intelligence/cli.py index 3c7caa2..8a672ec 100644 --- a/repo_intelligence/cli.py +++ b/repo_intelligence/cli.py @@ -7,6 +7,7 @@ from .impact import analyze_change_impact from .scope import build_openhands_scope from .developer_os import build_developer_plan +from .hpl_binding import build_repo_read_binding, build_test_execute_binding def _write(path:str,data:dict)->None: @@ -41,6 +42,18 @@ def main()->None: d.add_argument("--change-request",required=True) d.add_argument("--out",default="developer-plan.json") + b=sub.add_parser("hpl-read-binding") + b.add_argument("developer_plan") + b.add_argument("--conversation-id",required=True) + b.add_argument("--path",required=True) + b.add_argument("--out",default="hpl-read-binding.json") + + t=sub.add_parser("hpl-test-binding") + t.add_argument("developer_plan") + t.add_argument("--workspace",required=True) + t.add_argument("--test-path",required=True) + t.add_argument("--out",default="hpl-test-binding.json") + a=p.parse_args() if a.cmd=="graph": @@ -56,10 +69,16 @@ def main()->None: graph=json.loads(Path(a.graph).read_text(encoding="utf-8")) impact=json.loads(Path(a.impact).read_text(encoding="utf-8")) data=build_openhands_scope(graph,impact,a.operation) - else: + elif a.cmd=="developer-plan": graph=json.loads(Path(a.graph).read_text(encoding="utf-8")) impact=json.loads(Path(a.impact).read_text(encoding="utf-8")) data=build_developer_plan(graph,impact,a.change_request) + elif a.cmd=="hpl-read-binding": + plan=json.loads(Path(a.developer_plan).read_text(encoding="utf-8")) + data=build_repo_read_binding(plan,conversation_id=a.conversation_id,path=a.path) + else: + plan=json.loads(Path(a.developer_plan).read_text(encoding="utf-8")) + data=build_test_execute_binding(plan,workspace=a.workspace,test_path=a.test_path) _write(a.out,data) From a02f17ef16a84335efe784c7e622eb9a5e887a75 Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:38 +0200 Subject: [PATCH 5/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- repo_intelligence/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/repo_intelligence/__init__.py b/repo_intelligence/__init__.py index 7ea7c25..91dfc30 100644 --- a/repo_intelligence/__init__.py +++ b/repo_intelligence/__init__.py @@ -5,6 +5,7 @@ from .impact import analyze_change_impact from .scope import build_openhands_scope from .developer_os import build_developer_plan, reconcile_developer_run +from .hpl_binding import build_repo_patch_binding, build_repo_read_binding, build_test_execute_binding __all__=[ "build_repository_graph", @@ -12,6 +13,9 @@ "build_openhands_scope", "build_developer_plan", "reconcile_developer_run", + "build_repo_read_binding", + "build_test_execute_binding", + "build_repo_patch_binding", "load_architecture_contract", "validate_architecture_contract", ] From 343532b27fa8cf6ea1a665198269f26ce5990a2e Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:42 +0200 Subject: [PATCH 6/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- .github/workflows/repository-intelligence.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/repository-intelligence.yml b/.github/workflows/repository-intelligence.yml index 50a1eeb..9e1390c 100644 --- a/.github/workflows/repository-intelligence.yml +++ b/.github/workflows/repository-intelligence.yml @@ -2,7 +2,7 @@ name: Repository Intelligence CI on: pull_request: push: - branches: ["feat/repository-intelligence-v1", "feat/repository-intelligence-v2", "feat/repository-intelligence-v3", "feat/governed-developer-os-v1"] + branches: ["feat/repository-intelligence-v1", "feat/repository-intelligence-v2", "feat/repository-intelligence-v3", "feat/governed-developer-os-v1", "feat/governed-developer-os-v2"] permissions: contents: read jobs: @@ -22,3 +22,5 @@ jobs: - run: python -c 'import json; d=json.load(open("/tmp/openhands-scope.json")); assert d["execution_authorized"] is False' - run: repo-intel developer-plan /tmp/repository-graph.json /tmp/change-impact.json --change-request 'CI deterministic developer plan' --out /tmp/developer-plan.json - run: python -c 'import json; d=json.load(open("/tmp/developer-plan.json")); assert d["execution_authorized"] is False and d["invariants"]["patch_requires_hpl_authority"] is True' + - run: repo-intel hpl-read-binding /tmp/developer-plan.json --conversation-id 123e4567-e89b-12d3-a456-426614174000 --path README.md --out /tmp/hpl-read-binding.json + - run: python -c 'import json; d=json.load(open("/tmp/hpl-read-binding.json")); assert d["policy"]["effect_type"] == "OPENHANDS_REPO_READ" and d["execution_authorized"] is False' From debf6dd872dca2fa8bbfb6707c7518918894c0c9 Mon Sep 17 00:00:00 2001 From: Mopati Ramaologa <102808043+Mopati123@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:14:46 +0200 Subject: [PATCH 7/7] feat(developer-os): bind deterministic plan to HPL OpenHands payloads --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 41732a6..e106b1f 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,13 @@ The generated plan is proposal-only. It composes bounded `repo.read`, `test.execute`, and `repo.patch` scopes, requires retesting after mutation, requires repository-intelligence recomputation, and requires final evidence. The patch stage remains subject to separate HPL execution authority. + + +## Governed Developer OS HPL binding v2 + +Phase N v2 emits HPL-compatible admission payloads for the already-certified +OpenHands `repo.read`, `test.execute`, and `repo.patch` capabilities. + +The binding layer remains proposal-only and network-free. HPL must still admit +the consequential request and mint the ExecutionToken before any mutation can +occur.