refactor: unify impl operation gates and correction verification
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""Canonical impl contracts stay reachable without duplicated skill bodies."""
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from harness.tests.common.impl_contract_docs import read_contract, REQUIRED
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
class ImplArchitectureDocsTests(unittest.TestCase):
|
||||
def test_skills_require_single_detailed_workflow_source(self):
|
||||
for name in ("gitea-issue-implementation", "gitea-issue-rework"):
|
||||
path = ROOT / "harness/agents/impl/skills" / name / "SKILL.md"
|
||||
text = path.read_text()
|
||||
reference = path.parent / "references/workflow.md"
|
||||
with self.subTest(skill=name):
|
||||
self.assertIn(REQUIRED, text)
|
||||
self.assertLess(len(text.splitlines()), 100)
|
||||
self.assertEqual(text.count("name: " + name), 1)
|
||||
self.assertNotIn("## Managed-lock broker gate", text)
|
||||
self.assertFalse(reference.read_text().startswith("---"))
|
||||
contract = read_contract(path)
|
||||
self.assertEqual(contract.count("## Managed-lock broker gate"), 1)
|
||||
for gate in ("## Delegation evidence acceptance gate", "## Test inventory source-binding gate", "## Canonical external verification evidence"):
|
||||
self.assertEqual(contract.count(gate), 1)
|
||||
|
||||
def test_all_canonical_impl_markdown_links_resolve(self):
|
||||
for name in ("gitea-issue-implementation", "gitea-issue-rework"):
|
||||
directory = ROOT / "harness/agents/impl/skills" / name
|
||||
for path in [directory / "SKILL.md", directory / "references/workflow.md"]:
|
||||
for link in re.findall(r"\[[^\]]+\]\(([^)]+)\)", path.read_text()):
|
||||
if link.startswith(("https://", "http://", "#")):
|
||||
continue
|
||||
target = link.split("#", 1)[0]
|
||||
with self.subTest(path=path.name, link=link):
|
||||
self.assertTrue((path.parent / target).resolve().exists())
|
||||
|
||||
def test_correction_default_and_exception_remain_distinct(self):
|
||||
text = (ROOT / "harness/agents/impl/skills/gitea-issue-rework/references/readback.md").read_text()
|
||||
for token in ("strict", "separately", "atomic_client_precondition=false", "residual", "neutered"):
|
||||
self.assertIn(token.lower(), text.lower())
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,152 @@
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
sys.path.insert(0, str(ROOT / "harness" / "lib"))
|
||||
import impl_operation as op
|
||||
|
||||
|
||||
class ImplOperationTests(unittest.TestCase):
|
||||
def test_reentrant_preflight_cannot_authorize_duplicate_mutation(self):
|
||||
coordinator = op.Coordinator()
|
||||
intent = make_intent()
|
||||
approval = make_approval(intent)
|
||||
writes = []
|
||||
entered = []
|
||||
def preflight(value):
|
||||
if not entered:
|
||||
entered.append(True)
|
||||
coordinator.execute(intent, approval, provider)
|
||||
return {"fresh": True}
|
||||
provider = adapter(preflight=preflight, mutate=lambda value: writes.append("write"))
|
||||
coordinator.execute(intent, approval, provider)
|
||||
self.assertEqual(writes, ["write"])
|
||||
|
||||
def test_preflight_cannot_change_approved_payload_before_mutation(self):
|
||||
intent = make_intent()
|
||||
calls = []
|
||||
def preflight(i):
|
||||
i.payload["body"] = "unapproved"
|
||||
return {"fresh": True}
|
||||
c = op.Coordinator()
|
||||
result = c.execute(intent, make_approval(intent), adapter(preflight=preflight,
|
||||
mutate=lambda i: calls.append(i.payload["body"])), calls.append)
|
||||
self.assertEqual(calls[-1], "ok")
|
||||
self.assertEqual(result.status, "verified")
|
||||
|
||||
def test_attempt_callback_must_be_callable_before_preflight(self):
|
||||
calls = []
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(),
|
||||
adapter(preflight=lambda i: calls.append("preflight") or {"fresh": True}), on_attempt=3)
|
||||
self.assertEqual(calls, [])
|
||||
self.assertEqual(result.status, "partial")
|
||||
|
||||
def test_attempt_callback_failure_consumes_authority_without_write(self):
|
||||
calls = []
|
||||
c = op.Coordinator()
|
||||
a = adapter(mutate=lambda i: calls.append("write"))
|
||||
result = c.execute(make_intent(), make_approval(), a,
|
||||
lambda record: (_ for _ in ()).throw(RuntimeError("secret credential")))
|
||||
self.assertEqual(calls, [])
|
||||
self.assertNotIn("secret credential", result.reason)
|
||||
self.assertEqual(c.execute(make_intent(), make_approval(), a).status, "partial")
|
||||
|
||||
def test_malformed_canonical_target_rejected(self):
|
||||
for target in ("owner/..", "owner/.", "owner/repo\\x", "owner/repo\n"):
|
||||
with self.subTest(target=target):
|
||||
i = op.Intent("v1", "push", target, 1, "a"*40, {})
|
||||
self.assertRaises(ValueError, op.validate, i, op.Approval("v1", "0"*64, "approved"))
|
||||
|
||||
def test_unserializable_ack_does_not_lose_verified_result(self):
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(mutate=lambda i: object()))
|
||||
self.assertEqual(result.status, "verified")
|
||||
|
||||
def test_verify_exception_returns_partial_and_no_retry(self):
|
||||
calls = []
|
||||
c = op.Coordinator()
|
||||
a = adapter(mutate=lambda i: calls.append("write"), verify=lambda *args: (_ for _ in ()).throw(RuntimeError("private")))
|
||||
result = c.execute(make_intent(), make_approval(), a)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertNotIn("private", result.reason)
|
||||
c.execute(make_intent(), make_approval(), a)
|
||||
self.assertEqual(calls, ["write"])
|
||||
|
||||
def test_versioned_intent_and_approval_validate(self):
|
||||
intent = op.Intent("v1", "comment.patch", "owner/repo", 12, "a" * 40, {"body": "ok"})
|
||||
approval = op.Approval("v1", intent.digest, "approved")
|
||||
self.assertEqual(op.validate(intent, approval).digest, intent.digest)
|
||||
|
||||
def test_invalid_full_sha_rejected_before_callbacks(self):
|
||||
calls = []
|
||||
intent = op.Intent("v1", "push", "owner/repo", 1, "a" * 39, {})
|
||||
result = op.Coordinator().execute(intent, op.Approval("v1", "0" * 64, "approved"), adapter(), calls.append)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_stale_preflight_fails_without_mutation(self):
|
||||
calls = []
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(preflight=lambda i: {"fresh": False}), calls.append)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_unsupported_capability_fails_before_write(self):
|
||||
calls = []
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(capability=False), calls.append)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_uncertain_ack_reconciles_without_second_write(self):
|
||||
calls = []
|
||||
a = adapter(mutate=lambda i: (_ for _ in ()).throw(op.UncertainAcknowledgement()), readback=lambda i: {"state": "applied"}, verify=lambda i, p, r: True)
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(), a, calls.append)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(result.status, "verified")
|
||||
self.assertEqual(result.assurance["durable_at_most_once"], False)
|
||||
|
||||
def test_reuse_cannot_mutate_twice(self):
|
||||
calls = []
|
||||
c = op.Coordinator()
|
||||
a = adapter()
|
||||
c.execute(make_intent(), make_approval(), a, calls.append)
|
||||
again = c.execute(make_intent(), make_approval(), a, calls.append)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(again.status, "partial")
|
||||
|
||||
def test_verification_requires_trusted_adapter_callback(self):
|
||||
calls = []
|
||||
result = op.Coordinator().execute(make_intent(), make_approval(), adapter(verify=lambda *args: False), calls.append)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertNotEqual(result.status, "verified")
|
||||
|
||||
def test_correction_needs_separate_approval_unless_explicit_patch_exception(self):
|
||||
calls = []
|
||||
correction = make_intent(operation="correction")
|
||||
result = op.Coordinator().execute(correction, make_approval(correction), adapter(capability="ordinary_patch"), calls.append)
|
||||
self.assertEqual(result.status, "partial")
|
||||
self.assertEqual(calls, [])
|
||||
approval = make_approval(correction, correction=True, ordinary_patch=True)
|
||||
result = op.Coordinator().execute(correction, approval, adapter(capability="ordinary_patch"), calls.append)
|
||||
self.assertEqual(result.assurance["atomic_client_precondition"], False)
|
||||
self.assertIn("residual_race", result.assurance)
|
||||
|
||||
|
||||
def make_intent(operation="comment.patch"):
|
||||
return op.Intent("v1", operation, "owner/repo", 12, "a" * 40, {"body": "ok"})
|
||||
|
||||
|
||||
def make_approval(intent=None, correction=False, ordinary_patch=False):
|
||||
intent = intent or make_intent()
|
||||
return op.Approval("v1", intent.digest, "approved", correction=correction, ordinary_patch=ordinary_patch)
|
||||
|
||||
|
||||
def adapter(**kwargs):
|
||||
defaults = dict(preflight=lambda i: {"fresh": True}, capability=True,
|
||||
mutate=lambda i: {"written": True}, readback=lambda i: {"state": "applied"},
|
||||
verify=lambda i, p, r: True)
|
||||
defaults.update(kwargs)
|
||||
return op.Adapter(**defaults)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Integration regression tests for coordinator-backed initial delivery."""
|
||||
import importlib.util
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "harness/agents/impl/skills/gitea-issue-implementation/scripts/implementation_delivery.py"
|
||||
sys.path.insert(0, str(ROOT))
|
||||
spec = importlib.util.spec_from_file_location("implementation_delivery", SCRIPT)
|
||||
delivery = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(delivery)
|
||||
from harness.lib.impl_operation import Approval, Coordinator, Intent
|
||||
|
||||
HEAD = "a" * 40
|
||||
|
||||
class Adapter:
|
||||
def __init__(self):
|
||||
self.events=[]; self.number=7; self.fail_pr=False; self.uncertain=False; self.received=[]
|
||||
def push_operation(self, intent):
|
||||
self.received.append(intent)
|
||||
return self.push(intent.payload['head'])
|
||||
def create_or_reuse_pr_operation(self, intent):
|
||||
self.received.append(intent)
|
||||
return self.create_or_reuse_pr(intent.payload['head'])
|
||||
def push(self, head): self.events.append("push"); return True
|
||||
def read_remote_head(self): self.events.append("read-head"); return HEAD
|
||||
def create_or_reuse_pr(self, head):
|
||||
self.events.append("create-pr")
|
||||
if self.fail_pr: raise RuntimeError("failed")
|
||||
if self.uncertain: raise RuntimeError("uncertain ack")
|
||||
return self.number
|
||||
def read_pr(self, number): self.events.append("read-pr"); return {"number":number,"head":HEAD,"state":"open"}
|
||||
def preflight_operation(self, intent): self.events.append("preflight:"+intent.operation); return {"fresh":True,"scope":intent.target}
|
||||
def verify_operation(self, intent, before, observed): self.events.append("verify:"+intent.operation); return True
|
||||
def find_intended_pr(self, head): self.events.append("find-pr"); return None if self.fail_pr else {"number":self.number}
|
||||
|
||||
def make_intents():
|
||||
push=Intent("v1","push","org/repo",42,HEAD,{"head":HEAD})
|
||||
pr=Intent("v1","pr.create","org/repo",42,HEAD,{"head":HEAD,"body":"approved body","base":"main"})
|
||||
return push, Approval("v1",push.digest,"approved"), pr, Approval("v1",pr.digest,"approved")
|
||||
|
||||
class IntegrationTests(unittest.TestCase):
|
||||
def invoke(self, adapter, intents=None, approvals=None, recorder=None):
|
||||
p,pa,r,ra=make_intents(); intents=intents or (p,r); approvals=approvals or (pa,ra)
|
||||
return delivery.deliver_approved_implementation(adapter,head=HEAD,declaration={"mode":"none"},push_intent=intents[0],push_approval=approvals[0],pr_intent=intents[1],pr_approval=approvals[1],coordinator=Coordinator(),on_attempt=recorder or (lambda entry: None))
|
||||
def test_full_composed_delivery(self):
|
||||
a=Adapter(); result=self.invoke(a)
|
||||
self.assertEqual(result["status"],"verified"); self.assertEqual(result["artifact"]["pr"]["number"],7)
|
||||
self.assertEqual(a.events.count("push"),1); self.assertEqual(a.events.count("create-pr"),1)
|
||||
def test_uncertain_pr_acknowledgement_reconciles_without_second_write(self):
|
||||
a = Adapter()
|
||||
a.uncertain = True
|
||||
result = self.invoke(a)
|
||||
self.assertEqual(result["status"], "verified")
|
||||
self.assertEqual(a.events.count("create-pr"), 1)
|
||||
self.assertEqual(a.events.count("find-pr"), 1)
|
||||
|
||||
def test_adoption_rejection_never_promotes_existing_remote_state(self):
|
||||
a = Adapter()
|
||||
old = delivery._delivery_adoption.require_adoption
|
||||
delivery._delivery_adoption.require_adoption = lambda *args, **kwargs: (_ for _ in ()).throw(ValueError("rejected"))
|
||||
try:
|
||||
result = self.invoke(a)
|
||||
finally:
|
||||
delivery._delivery_adoption.require_adoption = old
|
||||
self.assertEqual(result["status"], "partial")
|
||||
self.assertNotIn("push", a.events)
|
||||
self.assertNotIn("create-pr", a.events)
|
||||
|
||||
def test_approved_payload_head_mismatch_rejected_before_any_write(self):
|
||||
a = Adapter()
|
||||
push, push_approval, pr, pr_approval = make_intents()
|
||||
for operation in ('push', 'pr.create'):
|
||||
candidate = Intent('v1', operation, 'org/repo', 42, HEAD, {'head':'f'*40,'body':'approved'})
|
||||
candidate_approval = Approval('v1',candidate.digest,'approved')
|
||||
intents = (candidate,pr) if operation == 'push' else (push,candidate)
|
||||
approvals = (candidate_approval,pr_approval) if operation == 'push' else (push_approval,candidate_approval)
|
||||
with self.subTest(operation=operation),self.assertRaises(ValueError):
|
||||
self.invoke(a,intents=intents,approvals=approvals)
|
||||
self.assertNotIn('push',a.events)
|
||||
self.assertNotIn('create-pr',a.events)
|
||||
|
||||
def test_mutation_callbacks_receive_entire_approved_intent(self):
|
||||
a = Adapter()
|
||||
push, push_approval, pr, pr_approval = make_intents()
|
||||
self.assertEqual(self.invoke(a)['status'],'verified')
|
||||
self.assertEqual([item.as_dict() for item in a.received],[push.as_dict(),pr.as_dict()])
|
||||
|
||||
def test_new_adapter_contract_does_not_require_legacy_mutators(self):
|
||||
a = Adapter()
|
||||
a.push = None
|
||||
a.create_or_reuse_pr = None
|
||||
a.push_operation = lambda intent: a.events.append('push')
|
||||
a.create_or_reuse_pr_operation = lambda intent: a.events.append('create-pr') or 7
|
||||
self.assertEqual(self.invoke(a)['status'],'verified')
|
||||
self.assertEqual(a.events.count('push'),1)
|
||||
self.assertEqual(a.events.count('create-pr'),1)
|
||||
|
||||
def test_missing_approval_no_writes(self):
|
||||
a=Adapter(); p,pa,r,ra=make_intents(); ra=Approval("v1","0"*64,"approved")
|
||||
with self.assertRaises(ValueError): self.invoke(a,approvals=(pa,ra))
|
||||
self.assertNotIn("push",a.events)
|
||||
def test_preflight_failure_no_mutation(self):
|
||||
a=Adapter(); a.preflight_operation=lambda intent:{"fresh":False}
|
||||
result=self.invoke(a); self.assertEqual(result["status"],"partial"); self.assertNotIn("push",a.events)
|
||||
def test_pr_failure_preserves_verified_push(self):
|
||||
a=Adapter(); a.fail_pr=True; result=self.invoke(a)
|
||||
self.assertEqual(result["status"],"partial"); self.assertEqual(result["push"].status,"verified"); self.assertIsNone(result["artifact"])
|
||||
def test_mutations_each_require_fresh_adoption(self):
|
||||
a=Adapter(); calls=[]; old=delivery._delivery_adoption.require_adoption
|
||||
delivery._delivery_adoption.require_adoption=lambda *args,**kwargs:calls.append(kwargs["expected_stage"])
|
||||
try: self.invoke(a)
|
||||
finally: delivery._delivery_adoption.require_adoption=old
|
||||
self.assertEqual(len(calls),4)
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Functional correction verification with provider-shaped synthetic readbacks."""
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from harness.tests.impl import test_rework_readback as fixtures
|
||||
|
||||
DIRECTORY = Path(__file__).parents[2] / 'agents/impl/skills/gitea-issue-rework/scripts'
|
||||
spec = importlib.util.spec_from_file_location('correction_verifier', DIRECTORY/'rework_correction.py')
|
||||
assert spec is not None and spec.loader is not None
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
|
||||
class CorrectionVerifierTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
fixture = fixtures.ReworkReadbackTests()
|
||||
fixture.setUp()
|
||||
fixture.contract["version"] = "1.27.3"
|
||||
fixture.overrides["/version"] = ({"version":"1.27.3"}, {}, 200)
|
||||
self.addCleanup(fixture.doCleanups)
|
||||
self.new = copy.deepcopy(fixture.expected)
|
||||
self.old = copy.deepcopy(self.new)
|
||||
free = self.new['marker_free_body']
|
||||
for label in ('Source Issue', 'Pull Request', 'Source Review'):
|
||||
free = free.replace('- '+label+': ', '- '+label+': #')
|
||||
digest = hashlib.sha256(free.encode()).hexdigest()
|
||||
marker = self.new['marker'].replace(self.new['result_sha256'], digest)
|
||||
self.old.update(body=free+marker, marker=marker, marker_free_body=free, result_sha256=digest)
|
||||
self.plan = fixture.legacy.prepare_correction(self.old,self.new,comment_id=501,actor_id=42)
|
||||
fixture.add_result()
|
||||
fixture.comments[0]['body'] = self.old['body']
|
||||
self.before = fixture.adapter().snapshot(self.new)
|
||||
fixture.comments[0]['body'] = self.new['body']
|
||||
fixture.comments[0]['updated_at'] = '2026-01-03T00:00:00Z'
|
||||
self.after = fixture.adapter().snapshot(self.new)
|
||||
event = {'id':601,'type':'comment_ref','user':{'id':42,'login':'gitea-impl-agent'},
|
||||
'created_at':fixtures.T1,'updated_at':fixtures.T1,'ref_action':'none',
|
||||
'issue_url':fixtures.ORIGIN+'/acme/demo/issues/7',
|
||||
'ref_issue':{'number':9,'html_url':fixtures.ORIGIN+'/acme/demo/pulls/9'},
|
||||
'ref_comment':copy.deepcopy(self.before['raw']['comments']['rows'][0])}
|
||||
self.before['source_issue_timeline'] = self.timeline(event)
|
||||
changed = copy.deepcopy(event)
|
||||
changed.update(ref_action='neutered',updated_at='2026-01-03T00:00:00Z',
|
||||
ref_comment=copy.deepcopy(self.after['raw']['comments']['rows'][0]))
|
||||
self.after['source_issue_timeline'] = self.timeline(changed)
|
||||
self.approval = {'approved':True,'operation':'same_comment_correction',
|
||||
'historical_failure_baseline_sha256':'1'*64,'old_expected_document':self.old,
|
||||
'new_expected_document':self.new,'timeline_profile':'observed-comment-ref-neutralization',
|
||||
'ordinary_patch':True,'provider_version':self.before['provider']['version']}
|
||||
|
||||
def timeline(self,event):
|
||||
return {'complete':True,'rows':[event], 'pages':[{'page':1,'count':1,'headers':{'X-Total-Count':'1'}}]}
|
||||
|
||||
def verify(self,profile='observed-comment-ref-neutralization'):
|
||||
return mod.verify_correction(self.plan,self.before,self.after,self.old['body'],self.new['body'],
|
||||
self.approval,approval_sha256=mod.sha(mod.canonical(self.approval)),
|
||||
historical_failure_sha256='1'*64,profile=profile)
|
||||
|
||||
def test_exact_approved_reference_neutralization_passes(self):
|
||||
result=self.verify()
|
||||
self.assertEqual(result['status'],'verified')
|
||||
self.assertEqual(result['changed_timeline_ids'],[601])
|
||||
self.assertEqual(result['historical_handoff_status'],'partial_unchanged')
|
||||
|
||||
def test_strict_unchanged_timeline_passes(self):
|
||||
self.after['source_issue_timeline']=copy.deepcopy(self.before['source_issue_timeline'])
|
||||
self.approval['timeline_profile']='strict'
|
||||
self.assertEqual(self.verify('strict')['status'],'verified')
|
||||
|
||||
def test_reference_profile_requires_separate_approval(self):
|
||||
self.approval['timeline_profile']='strict'
|
||||
with self.assertRaises(mod.Bad): self.verify()
|
||||
|
||||
def test_source_and_cache_deltas_fail(self):
|
||||
for name,field in [('source_issue','title'),('pr','comments'),('pr_issue','comments')]:
|
||||
original=copy.deepcopy(self.after)
|
||||
self.after['raw'][name][field] = 99 if field=='comments' else 'changed'
|
||||
with self.subTest(name=name),self.assertRaises(mod.Bad): self.verify()
|
||||
self.after=original
|
||||
|
||||
def test_unrelated_timeline_field_change_fails(self):
|
||||
self.after['source_issue_timeline']['rows'][0]['body']='unexpected'
|
||||
with self.assertRaises(mod.Bad): self.verify()
|
||||
|
||||
def test_unrelated_null_timeline_field_is_still_a_mutation(self):
|
||||
self.after['source_issue_timeline']['rows'][0]['unexpected_field'] = None
|
||||
with self.assertRaises(mod.Bad): self.verify()
|
||||
|
||||
def test_duplicate_and_missing_timeline_rows_fail(self):
|
||||
original=copy.deepcopy(self.after)
|
||||
for rows in [[],self.after['source_issue_timeline']['rows']*2]:
|
||||
self.after=copy.deepcopy(original)
|
||||
self.after['source_issue_timeline']['rows']=rows
|
||||
with self.subTest(rows=len(rows)),self.assertRaises(mod.Bad): self.verify()
|
||||
|
||||
def test_wrong_actor_head_and_extra_body_change_fail(self):
|
||||
for field,value in [('actor_id',99),('head','d'*40),('new_body','arbitrary')]:
|
||||
original=copy.deepcopy(self.plan)
|
||||
self.plan[field]=value
|
||||
with self.subTest(field=field),self.assertRaises(mod.Bad): self.verify()
|
||||
self.plan=original
|
||||
|
||||
def test_reference_timestamp_cannot_move_backwards(self):
|
||||
self.after['source_issue_timeline']['rows'][0]['updated_at']='2025-01-01T00:00:00Z'
|
||||
with self.assertRaises(mod.Bad): self.verify()
|
||||
|
||||
def test_real_cli_valid_inputs_and_no_overwrite(self):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root=Path(td)
|
||||
args=[sys.executable,'-B',str(DIRECTORY/'rework_correction.py'),'verify']
|
||||
values={'plan':self.plan,'before':self.before,'after':self.after,
|
||||
'old-comment':{'body':self.old['body']},'new-comment':{'body':self.new['body']},'approval':self.approval}
|
||||
for name,value in values.items():
|
||||
raw=json.dumps(value).encode(); path=root/(name+'.json'); path.write_bytes(raw); path.chmod(0o600)
|
||||
args.extend(['--'+name,str(path),'--'+name+'-sha256',hashlib.sha256(raw).hexdigest()])
|
||||
output=root/'receipt.json'
|
||||
args.extend(['--historical-failure-sha256','1'*64,'--profile','observed-comment-ref-neutralization','--output',str(output)])
|
||||
result=subprocess.run(args,capture_output=True,text=True,timeout=10)
|
||||
self.assertEqual(result.returncode,0,result.stderr)
|
||||
saved=output.read_bytes()
|
||||
again=subprocess.run(args,capture_output=True,text=True,timeout=10)
|
||||
self.assertNotEqual(again.returncode,0)
|
||||
self.assertEqual(output.read_bytes(),saved)
|
||||
|
||||
def test_digest_tampering_is_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
path=Path(td)/'data.json';path.write_text('{}')
|
||||
with self.assertRaises(mod.Bad):mod.load(path,'0'*64)
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
@@ -0,0 +1,163 @@
|
||||
"""Regression tests for main-owned rework publication integration."""
|
||||
import copy
|
||||
import hashlib
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT = ROOT / "agents/impl/skills/gitea-issue-rework/scripts"
|
||||
sys.path.insert(0, str(SCRIPT))
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import rework_publication as pub
|
||||
from harness.lib import impl_operation as ops
|
||||
|
||||
HEAD = "a" * 40
|
||||
|
||||
def expected():
|
||||
# Generate through the canonical renderer to avoid hand-authored bodies.
|
||||
import rework_comment as rc
|
||||
d = dict(owner="o", repository="r", issue=4, pr=5, review=6,
|
||||
reviewed_head="b"*40, final_head=HEAD, contract_sha256="c"*64,
|
||||
result="partial", findings=[dict(id="F1", status="resolved", classification="valid_fix",
|
||||
changes="changed safely", verification="checks pass", reason="verified")],
|
||||
local_checks="pass", runtime_browser="not run", commit_status="없음", actions_runs="없음")
|
||||
dd, vals, fs, extras, cs, ar = rc.canon(d)
|
||||
body = rc.render_body(dd, vals, fs, extras, cs, ar)
|
||||
rh = hashlib.sha256(body.encode()).hexdigest()
|
||||
corr = hashlib.sha256("\0".join(["o", "r", "4", "5", "6", "b"*40, HEAD, "c"*64]).encode()).hexdigest()
|
||||
marker = f"<!-- gitea-agent:implementation-rework/v1 owner=o repository=r issue=4 pr=5 review=6 reviewed-head={'b'*40} head={HEAD} contract-sha256={'c'*64} result-sha256={rh} correlation={corr} -->\n"
|
||||
return dict(body=body+marker, marker=marker, marker_free_body=body, result_sha256=rh, correlation=corr,
|
||||
target=dict(owner="o", repository="r", issue=4, pr=5), review=6,
|
||||
reviewed_head="b"*40, head=HEAD, contract_sha256="c"*64)
|
||||
|
||||
def setup_case(op="comment.create", payload=None, approval_flags=None):
|
||||
if payload is None:
|
||||
e=expected(); payload={"expected":e,"target":e["target"]}
|
||||
intent=ops.Intent("v1", op, "o/r", 5, HEAD, payload)
|
||||
approval=ops.Approval("v1", intent.digest, "approved", **(approval_flags or {}))
|
||||
declaration={"mode":"none"}
|
||||
calls=[]
|
||||
adapter=ops.Adapter(preflight=lambda i: {"fresh":True,"scope":"exact"}, capability=True,
|
||||
mutate=lambda i: calls.append(copy.deepcopy(i)) or {"acknowledgement":"ok"},
|
||||
readback=lambda i: {"scope":"exact","head":HEAD}, verify=lambda i,p,o: True)
|
||||
return intent,approval,declaration,calls,adapter
|
||||
|
||||
class PublicationTests(unittest.TestCase):
|
||||
def test_entrypoint_is_present_not_import_error(self):
|
||||
self.assertTrue(callable(getattr(pub,"execute_rework_operation",None)))
|
||||
|
||||
def test_invalid_body_target_head_approval_do_not_write(self):
|
||||
cases=[]
|
||||
cases.append(setup_case("pr.patch", {"body":"body", "closing":"#4"}))
|
||||
cases.append(setup_case("comment.create", {"expected":expected(),"target":{"owner":"evil","repository":"r","issue":4,"pr":5}}))
|
||||
p={"head":"f"*40}; cases.append(setup_case("push",p))
|
||||
for intent,approval,decl,calls,adapter in cases:
|
||||
with self.subTest(intent=intent.operation), self.assertRaises(Exception):
|
||||
pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda x:None)
|
||||
self.assertEqual(calls,[])
|
||||
|
||||
def test_single_mutation_and_fresh_adoption_at_boundary(self):
|
||||
intent,approval,decl,calls,adapter=setup_case()
|
||||
old=pub.require_adoption
|
||||
try:
|
||||
adopted=Mock(return_value=None); pub.require_adoption=adopted
|
||||
attempts=[]
|
||||
result=pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,
|
||||
coordinator=ops.Coordinator(),on_attempt=attempts.append)
|
||||
self.assertEqual(result.status,"verified")
|
||||
self.assertEqual(len(calls),1); self.assertEqual(len(attempts),1)
|
||||
adopted.assert_called_once()
|
||||
finally: pub.require_adoption=old
|
||||
|
||||
def test_adoption_and_preflight_failure_have_zero_mutations(self):
|
||||
intent,approval,decl,calls,adapter=setup_case()
|
||||
old=pub.require_adoption
|
||||
try:
|
||||
pub.require_adoption=Mock(side_effect=ValueError("stale"))
|
||||
r=pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda x:None)
|
||||
self.assertEqual(r.status,"partial"); self.assertEqual(calls,[])
|
||||
adapter.preflight=lambda i: {"fresh":False}
|
||||
r=pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda x:None)
|
||||
self.assertEqual(r.status,"partial"); self.assertEqual(calls,[])
|
||||
finally: pub.require_adoption=old
|
||||
|
||||
def test_uncertain_ack_readback_once_literal_true_required(self):
|
||||
intent,approval,decl,calls,adapter=setup_case()
|
||||
old=pub.require_adoption
|
||||
try:
|
||||
pub.require_adoption=Mock(return_value=None)
|
||||
mutations=[]; reads=[]
|
||||
adapter.mutate=lambda i: mutations.append(i) or (_ for _ in ()).throw(ops.UncertainAcknowledgement())
|
||||
adapter.readback=lambda i: reads.append(i) or {"head":HEAD}
|
||||
adapter.verify=lambda i,p,o: 1
|
||||
result=pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda x:None)
|
||||
self.assertEqual(result.status,"partial")
|
||||
self.assertEqual((len(mutations),len(reads)),(1,1))
|
||||
finally: pub.require_adoption=old
|
||||
|
||||
def test_reuse_collects_fresh_state_without_any_mutation(self):
|
||||
intent, approval, declaration, calls, adapter = setup_case()
|
||||
reads = []
|
||||
adapter.preflight = lambda value: reads.append("preflight") or {"fresh":True}
|
||||
adapter.readback = lambda value: reads.append("readback") or {"exact":True}
|
||||
adapter.verify_reuse = lambda value, before, after: after.get("exact") is True
|
||||
result = pub.verify_rework_reuse(adapter,intent=intent,approval=approval,declaration=declaration)
|
||||
self.assertEqual(result['action'],'reuse')
|
||||
self.assertEqual(reads,['preflight','readback'])
|
||||
self.assertEqual(calls,[])
|
||||
|
||||
def test_reuse_stale_preflight_blocks_even_when_callback_would_accept(self):
|
||||
intent, approval, declaration, calls, adapter = setup_case()
|
||||
reads=[]
|
||||
adapter.preflight=lambda value:{"fresh":False}
|
||||
adapter.readback=lambda value:reads.append(True)
|
||||
adapter.verify_reuse=lambda *args:True
|
||||
with self.assertRaises(ValueError):
|
||||
pub.verify_rework_reuse(adapter,intent=intent,approval=approval,declaration=declaration)
|
||||
self.assertEqual(reads,[])
|
||||
self.assertEqual(calls,[])
|
||||
|
||||
def test_canonical_comment_cannot_publish_to_different_intent_scope(self):
|
||||
intent, approval, declaration, calls, adapter = setup_case()
|
||||
for target, target_id in [('evil/r', 5), ('o/r', 999)]:
|
||||
changed = ops.Intent('v1', intent.operation, target, target_id, HEAD, intent.payload)
|
||||
approved = ops.Approval('v1', changed.digest, 'approved')
|
||||
with self.subTest(target=target, target_id=target_id), self.assertRaises(ValueError):
|
||||
pub.execute_rework_operation(adapter,intent=changed,approval=approved,declaration=declaration,
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda record:None)
|
||||
self.assertEqual(calls, [])
|
||||
|
||||
def test_unimplemented_client_cas_correction_blocks_before_write(self):
|
||||
import rework_comment as rc
|
||||
new = expected()
|
||||
old = copy.deepcopy(new)
|
||||
body = new['marker_free_body']
|
||||
for label in ('Source Issue','Pull Request','Source Review'):
|
||||
body = body.replace('- '+label+': ','- '+label+': #')
|
||||
digest = hashlib.sha256(body.encode()).hexdigest()
|
||||
marker = new['marker'].replace(new['result_sha256'],digest)
|
||||
old.update(body=body+marker,marker=marker,marker_free_body=body,result_sha256=digest)
|
||||
payload = {'old_expected':old,'new_expected':new,'comment_id':7,'actor_id':42}
|
||||
intent = ops.Intent('v1','correction','o/r',7,HEAD,payload)
|
||||
approval = ops.Approval('v1',intent.digest,'approved',correction=True)
|
||||
calls = []
|
||||
adapter = ops.Adapter(lambda value:{'fresh':True},True,
|
||||
lambda value:calls.append('write'),lambda value:{'exact':True},lambda *args:True)
|
||||
with self.assertRaises(ValueError):
|
||||
pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration={'mode':'none'},
|
||||
coordinator=ops.Coordinator(),on_attempt=lambda record:None)
|
||||
self.assertEqual(calls,[])
|
||||
|
||||
def test_generic_comment_patch_rejected_and_correction_requires_explicit_approval(self):
|
||||
intent,approval,decl,calls,adapter=setup_case("comment.patch",{"body":"arbitrary"})
|
||||
with self.assertRaises(ValueError): pub.execute_rework_operation(adapter,intent=intent,approval=approval,declaration=decl,coordinator=ops.Coordinator(),on_attempt=lambda x:None)
|
||||
self.assertEqual(calls,[])
|
||||
|
||||
if __name__ == "__main__": unittest.main()
|
||||
@@ -7,6 +7,7 @@ import unittest
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
from harness.tests.common.impl_contract_docs import read_contract
|
||||
|
||||
DIRECTORY = Path(__file__).parents[2] / 'agents/impl/skills/gitea-issue-rework/scripts'
|
||||
ORIGIN = 'https://gitea.jsfamily.quest'
|
||||
@@ -696,7 +697,7 @@ class ReworkReadbackTests(unittest.TestCase):
|
||||
|
||||
def test_shared_commit_message_safety_rule_is_linked_and_amend_is_narrow(self):
|
||||
common = (DIRECTORY.parents[4]/'operations/repository-cache.md').read_text()
|
||||
skill = (DIRECTORY.parent/'SKILL.md').read_text()
|
||||
skill = read_contract(DIRECTORY.parent / 'SKILL.md')
|
||||
self.assertIn('## Commit-message reference safety', common)
|
||||
for clause in ('complete subject and body', 'cross-repository',
|
||||
'non-default branch', 'explicit user authorization',
|
||||
|
||||
Reference in New Issue
Block a user