Skip to content
Draft
4 changes: 3 additions & 1 deletion .github/workflows/repository-intelligence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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'
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
27 changes: 27 additions & 0 deletions docs/GOVERNED_DEVELOPER_OS_HPL_BINDING_V2.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions repo_intelligence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
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",
"analyze_change_impact",
"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",
]
21 changes: 20 additions & 1 deletion repo_intelligence/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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":
Expand All @@ -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)

Expand Down
202 changes: 202 additions & 0 deletions repo_intelligence/hpl_binding.py
Original file line number Diff line number Diff line change
@@ -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,
)
Loading
Loading