"""Task grant, ledger, and broker fast-forward contracts.""" from __future__ import annotations import sys import hashlib import io import json import time from email.message import Message from pathlib import Path import pytest from testing.tests.test_hermes_scm_broker_support import _load, _object_entry, _pack_of, _receive_command ROOT = Path(__file__).parents[2] sys.path.insert(0, str(ROOT / "services/hermes/scm-common/scripts")) import receive_pack_scan # noqa: E402 import scm_task_grants as grants # noqa: E402 KEY = b"a" * 64 def claims(**overrides): value = { "repo": "atlas-iac", "ref": "wt/t_root", "base": "main", "board": "titan-iac", "root_task_id": "t_root", "assignment_task_id": "t_child", "run": "run_1", "ordinal": 1, "expires": 101, "expected_old": "0" * 40, "new_head": "1" * 40, "continuation_kind": "", } value.update(overrides) return value def test_grant_is_exact_signed_schema_and_expiry_bound(): grants = _load("scm_task_grants") token = grants.sign_grant(claims(), KEY) assert grants.verify_grant(token, key=KEY, now=100)["root_task_id"] == "t_root" with pytest.raises(grants.PolicyError, match="signature"): grants.verify_grant(token[:-1] + "A", key=KEY, now=100) with pytest.raises(grants.PolicyError, match="expired"): grants.verify_grant(token, key=KEY, now=101) def test_ledger_requires_absent_initial_ref_and_exact_owner_cas(tmp_path): grants = _load("scm_task_grants") ledger = grants.TaskLedger(tmp_path / "ledger.db") first = claims() ledger.register(first, remote_head=None) ledger.authorize_update(first) ledger.commit(first) assert ledger.get("atlas-iac", "wt/t_root") == ("titan-iac", "t_root", "1" * 40) with pytest.raises(grants.PolicyError, match="head changed"): ledger.authorize_update(first) with pytest.raises(grants.PolicyError, match="another task"): ledger.register(claims(root_task_id="t_other"), remote_head="1" * 40) with pytest.raises(grants.PolicyError, match="requires operator adoption"): ledger.register(claims(root_task_id="t_untracked", ref="wt/untracked"), remote_head="2" * 40) with pytest.raises(grants.PolicyError, match="different branch"): ledger.register(claims(ref="wt/fork"), remote_head=None) def test_operator_adoption_seeds_only_the_exact_live_reviewed_branch(tmp_path): grants = _load("scm_task_grants") ledger = grants.TaskLedger(tmp_path / "ledger.db") record = { "repo": "atlas-iac", "ref": "wt/t_root", "board": "titan-iac", "root_task_id": "t_root", "latest_head": "a" * 40, "pr_number": 55, } ledger.seed_adoption(record, "a" * 40) assert ledger.get("atlas-iac", "wt/t_root") == ("titan-iac", "t_root", "a" * 40) with pytest.raises(grants.PolicyError, match="does not match"): ledger.seed_adoption({**record, "ref": "wt/other"}, "b" * 40) # A later broker-confirmed revision survives a restart with the original # reviewed import record still mounted. ledger.commit(claims(expected_old="a" * 40, new_head="b" * 40)) ledger.seed_adoption(record, "b" * 40) assert ledger.get("atlas-iac", "wt/t_root")[2] == "b" * 40 def test_fast_forward_requires_quarantined_parent_path(): old, middle, new = "a" * 40, "b" * 40, "c" * 40 assert receive_pack_scan._proves_descends(new, old, {new: (middle,), middle: (old,)}) assert not receive_pack_scan._proves_descends(new, old, {new: (middle,)}) assert not receive_pack_scan._proves_descends(new, old, {new: ("d" * 40,)}) def test_signed_default_wt_branch_accepts_a_real_commit_pack_but_unsigned_does_not(): raw = b"tree " + b"0" * 40 + b"\nauthor test 1 +0000\ncommitter test 1 +0000\n\nmessage\n" head = hashlib.sha1(b"commit " + str(len(raw)).encode() + b"\0" + raw).hexdigest() body = _receive_command(b"0" * 40, head.encode(), b"refs/heads/wt/t_default", _pack_of([_object_entry(1, raw)])) assert receive_pack_scan.validate_receive_pack(body, "token", (b"token",), expected=("0" * 40, head, "wt/t_default")) == ("0" * 40, head, "wt/t_default") with pytest.raises(receive_pack_scan.PolicyError, match="namespaced"): receive_pack_scan.validate_receive_pack(body, "token", (b"token",)) @pytest.mark.parametrize("ref", ["main", "master", "refs/tags/v1"]) def test_granted_push_never_allows_base_or_tag_refs(ref): if ref.startswith("refs/"): raw_ref = ref.encode() else: raw_ref = f"refs/heads/{ref}".encode() body = _receive_command(b"0" * 40, b"1" * 40, raw_ref) with pytest.raises(receive_pack_scan.PolicyError): receive_pack_scan.validate_receive_pack(body, "token", (b"token",), expected=("0" * 40, "1" * 40, ref)) def test_alias_requests_canonicalize_before_the_broker_forwards_them(): api = _load("gitea_api") assert api.api_url(api.CANONICAL_BASE_URL, "/api/v1/repos/titan/titan-iac/pulls") == "https://scm.bstein.dev/api/v1/repos/titan/atlas-iac/pulls" assert api.authorize_request("GET", "/api/v1/repos/titan/atlas-iac/branches/wt/t_root", None) == "branch" def test_registration_uses_the_fixed_broker_http_envelope(): client = _load("scm_broker_client") seen = [] class Response: status = 200 class headers: @staticmethod def get_content_type(): return "application/json" def __enter__(self): return self def __exit__(self, *_args): return False @staticmethod def read(_limit): return b'{"registered":true}' def opener(request, timeout): seen.append((request, timeout)) return Response() assert client.register_task("header-safe-grant", opener=opener) == b'{"registered":true}' request, timeout = seen[0] assert request.full_url == client.BROKER_ORIGIN + "/v1/tasks/register" assert request.data == b'{"grant":"header-safe-grant"}' and timeout == 30 def test_draft_refresh_requires_exact_owned_pr_and_has_no_mutating_fields(): drafts = _load("scm_task_drafts") grant, number, title, body = drafts.request_fields( {"grant": "signed", "pr_number": 55, "title": "refresh", "body": "evidence"}, "token" ) assert (grant, number, title, body) == ("signed", 55, "WIP: refresh", "evidence") claims = {"repo": "atlas-iac", "ref": "wt/t_root", "base": "main", "new_head": "a" * 40} pull = {"number": 55, "state": "open", "head": {"ref": "wt/t_root", "sha": "a" * 40, "repo": {"full_name": "titan/atlas-iac"}}, "base": {"ref": "main", "repo": {"full_name": "titan/atlas-iac"}}} drafts.matches_pull(pull, claims, 55) with pytest.raises(drafts.PolicyError, match="does not match"): drafts.matches_pull({**pull, "base": {"ref": "master"}}, claims, 55) with pytest.raises(drafts.PolicyError, match="fields"): drafts.request_fields({"grant": "x", "pr_number": 55, "title": "x", "body": "x", "state": "closed"}, "token") def _control_handler(broker, body: dict): """Build the broker's real control handler without opening a TCP listener.""" encoded = json.dumps(body).encode() handler = object.__new__(broker.BrokerHandler) headers = Message() headers["Content-Type"] = "application/json" headers["Content-Length"] = str(len(encoded)) handler.path = "/v1/tasks/draft-update" handler.headers = headers handler.rfile = io.BytesIO(encoded) handler.wfile = io.BytesIO() handler.connection = type("Connection", (), {"settimeout": staticmethod(lambda _value: None)})() handler._json = lambda _status, value: handler.wfile.write(value) return handler def test_post_push_repair_refreshes_the_same_owned_pr_after_real_ledger_commit(tmp_path, monkeypatch): """Draft prose refresh follows an already committed old->new branch update.""" broker = _load("scm_broker") ledger = grants.TaskLedger(tmp_path / "ledger.db") old, new = "a" * 40, "b" * 40 initial = { **claims(expected_old="0" * 40, new_head=old, expires=int(time.time()) + 120), "continuation_kind": "implementation", } ledger.register(initial, remote_head=None) ledger.commit(initial) repair = { **claims( assignment_task_id="t_repair", expected_old=old, new_head=new, expires=int(time.time()) + 120, ), "continuation_kind": "repair", } # The receive-pack path has already performed the authenticated CAS. The # following refresh must authorize the owner at `new`, not re-check old. ledger.commit(repair) token = grants.sign_grant(repair, KEY) pull = { "number": 55, "state": "open", "head": {"ref": "wt/t_root", "sha": new, "repo": {"full_name": "titan/atlas-iac"}}, "base": {"ref": "main", "repo": {"full_name": "titan/atlas-iac"}}, } updates = [] monkeypatch.setattr(broker, "read_token", lambda: "token") monkeypatch.setattr(broker, "verify_grant", lambda value: grants.verify_grant(value, key=KEY)) monkeypatch.setattr(broker, "_task_ledger", lambda: ledger) monkeypatch.setattr(broker, "_branch_head", lambda *_args: new) monkeypatch.setattr(broker, "read", lambda *_args, **_kwargs: json.dumps(pull).encode()) monkeypatch.setattr( broker, "update_draft", lambda _token, repo, number, title, body: updates.append((repo, number, title, body)) or json.dumps({"number": number, "html_url": "https://scm.bstein.dev/titan/atlas-iac/pulls/55"}).encode(), ) _control_handler(broker, { "grant": token, "pr_number": 55, "title": "Repair CI failure", "body": "Tests: pytest", })._control() assert ledger.get("atlas-iac", "wt/t_root") == ("titan-iac", "t_root", new) assert updates == [("atlas-iac", 55, "WIP: Repair CI failure", "Tests: pytest")]