[verified] Handle delivery acknowledgements and read-only recovery

This commit is contained in:
2026-10-13 06:26:49 +00:00
parent 3759fcbde3
commit dbd381a41b
5 changed files with 439 additions and 0 deletions
@@ -0,0 +1,152 @@
"""Read-only recovery must never repeat an uncertain delivery mutation."""
import importlib.util
from pathlib import Path
import unittest
PATH = Path(__file__).resolve().parents[3] / 'harness/agents/impl/skills/gitea-issue-implementation/scripts/implementation_delivery.py'
class ReadOnlyAdapter:
def __init__(self, head):
self.head = head
self.calls = []
self.proof = True
def push(self, head):
raise AssertionError('reconciliation must not push')
def create_or_reuse_pr(self, head):
raise AssertionError('reconciliation must not create or patch')
def read_remote_head(self):
self.calls.append('branch')
return self.head
def read_pr(self, number):
self.calls.append('pr')
return {'number': number, 'head': self.head, 'state': 'open'}
def verify_reconciliation(self, head, number):
self.calls.append('complete_intent')
return self.proof
class ReconciliationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
spec = importlib.util.spec_from_file_location('delivery_reconciliation_under_test', PATH)
cls.module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cls.module)
def test_read_only_recovery_returns_exact_state_without_repeated_mutation(self):
adapter = ReadOnlyAdapter('a' * 40)
result = self.module.reconcile_implementation(adapter, head=adapter.head, number=19, declaration={'mode': 'none'})
self.assertEqual(result, {'remote_head': adapter.head, 'pr': {'number': 19, 'head': adapter.head, 'state': 'open'}})
self.assertEqual(adapter.calls, ['branch', 'pr', 'complete_intent'])
def test_missing_declaration_blocks_all_reads(self):
adapter = ReadOnlyAdapter('a' * 40)
with self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(adapter, head=adapter.head, number=19, declaration=None)
self.assertEqual(adapter.calls, [])
def test_invalid_identifiers_block_all_reads(self):
for head, number in [('bad', 19), ('a' * 40, True), ('a' * 40, 0)]:
adapter = ReadOnlyAdapter('a' * 40)
with self.subTest(head=head, number=number), self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(adapter, head=head, number=number, declaration={'mode': 'none'})
self.assertEqual(adapter.calls, [])
def test_branch_mismatch_blocks_pr_and_proof_reads(self):
adapter = ReadOnlyAdapter('b' * 40)
with self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(adapter, head='a' * 40, number=19, declaration={'mode': 'none'})
self.assertEqual(adapter.calls, ['branch'])
def test_body_snapshot_scope_or_collection_failure_cannot_complete(self):
for proof in [False, None, 1, {}, 'yes']:
adapter = ReadOnlyAdapter('a' * 40)
adapter.proof = proof
with self.subTest(proof=proof), self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(adapter, head=adapter.head, number=19, declaration={'mode': 'none'})
self.assertEqual(adapter.calls, ['branch', 'pr', 'complete_intent'])
def test_loopback_committed_write_with_bad_ack_recovers_read_only(self):
import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen
spec = importlib.util.spec_from_file_location('response_reconciliation_integration', PATH.with_name('delivery_response.py'))
assert spec is not None and spec.loader is not None
response = importlib.util.module_from_spec(spec)
spec.loader.exec_module(response)
head = 'a' * 40
state = {'writes': 0, 'body': '', 'head': head, 'number': 19, 'state': 'open',
'base': 'main', 'repository': 'owner/repo', 'author': 6,
'issue': {'body': 'approved', 'updated_at': 'pinned', 'comments': []},
'files': ['one.py'], 'commits': [head], 'collections_complete': True}
expected_issue = dict(state['issue'])
class Handler(BaseHTTPRequestHandler):
def do_PATCH(self):
state['writes'] += 1
state['body'] = self.rfile.read(int(self.headers['Content-Length'])).decode()
self.send_response(201)
self.end_headers()
self.wfile.write(b'{') # Committed, but malformed acknowledgement.
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(json.dumps(state).encode())
def log_message(self, format, *args):
pass
server = HTTPServer(('127.0.0.1', 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
url = 'http://127.0.0.1:' + str(server.server_port)
def send(method='GET'):
request = Request(url, data=b'intended' if method == 'PATCH' else None, method=method)
with urlopen(request, timeout=2) as reply:
return reply.status, reply.read(response.MAX_RESPONSE_BYTES + 1)
def read():
return response.deliver_once(send, expected_status=200)
class Adapter(ReadOnlyAdapter):
def read_remote_head(self):
return read()['head']
def read_pr(self, number):
return read()
def verify_reconciliation(self, head, number):
actual = read()
return (actual['body'] == 'intended' and actual['head'] == head
and actual['number'] == number and actual['state'] == 'open'
and actual['base'] == 'main' and actual['repository'] == 'owner/repo'
and actual['author'] == 6 and actual['issue'] == expected_issue
and actual['files'] == ['one.py'] and actual['commits'] == [head]
and actual['collections_complete'] is True)
try:
with self.assertRaises(response.AcknowledgementUncertainError):
response.deliver_once(lambda: send('PATCH'), expected_status=201)
result = self.module.reconcile_implementation(Adapter(head), head=head, number=19, declaration={'mode': 'none'})
self.assertEqual(result['remote_head'], head)
for key, bad in [('body', 'competing change'), ('issue', {}), ('collections_complete', False)]:
saved = state[key]
state[key] = bad
with self.subTest(key=key), self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(Adapter(head), head=head, number=19, declaration={'mode': 'none'})
state[key] = saved
self.assertEqual(state['writes'], 1)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=2)
self.assertFalse(thread.is_alive())
def test_closed_pr_cannot_complete(self):
adapter = ReadOnlyAdapter('a' * 40)
adapter.read_pr = lambda number: {'number': number, 'head': adapter.head, 'state': 'closed'}
with self.assertRaises(self.module.AdoptionError):
self.module.reconcile_implementation(adapter, head=adapter.head, number=19, declaration={'mode': 'none'})
self.assertNotIn('complete_intent', adapter.calls)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,162 @@
import json
import threading
import unittest
import sys
from pathlib import Path
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.error import HTTPError
from urllib.request import Request, urlopen
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / 'agents/impl/skills/gitea-issue-implementation/scripts'))
import delivery_response as dr
def make_handler():
class FakeHandler(BaseHTTPRequestHandler):
status = 200
body = b'{"ok":true}'
calls = 0
def do_GET(self): self.respond()
def do_POST(self): self.respond()
def do_PATCH(self): self.respond()
def respond(self):
type(self).calls += 1
self.send_response(type(self).status)
self.end_headers()
self.wfile.write(type(self).body)
def log_message(self, format, *args): pass
return FakeHandler
FakeHandler = make_handler()
class DeliveryResponseTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.server = HTTPServer(('127.0.0.1', 0), FakeHandler)
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
cls.thread.start()
cls.url = 'http://127.0.0.1:%d/' % cls.server.server_port
@classmethod
def tearDownClass(cls):
cls.server.shutdown()
cls.server.server_close()
def setUp(self):
FakeHandler.calls = 0
FakeHandler.body = b'{"ok":true}'
FakeHandler.status = 200
def request(self, method, expected):
def send():
req = Request(self.url, method=method)
try:
with urlopen(req, timeout=2) as response:
return response.status, response.read(1024 * 1024 + 1)
except HTTPError as e:
return e.code, e.read(1024 * 1024 + 1)
return dr.deliver_once(send, expected_status=expected)
def test_endpoint_specific_success_statuses(self):
for method, status in [('GET', 200), ('POST', 201), ('PATCH', 201)]:
FakeHandler.status = status
self.assertEqual(self.request(method, status), {'ok': True})
def test_rejects_invalid_expected_status_before_send(self):
calls = []
for invalid in (None, True, 0, 700, '201'):
with self.subTest(invalid=invalid):
with self.assertRaises(dr.InvalidExpectationError):
dr.deliver_once(lambda: calls.append(1), expected_status=invalid)
self.assertEqual(calls, [])
def test_unexpected_success_status_uncertain_not_heuristic(self):
FakeHandler.status = 202
with self.assertRaises(dr.AcknowledgementUncertainError):
self.request('POST', 201)
self.assertEqual(FakeHandler.calls, 1)
def test_reconstructed_method_only_heuristic_rejects_valid_patch_ack(self):
# Reconstructed historical predicate, NOT a prior runtime RED run.
old_expected = 201 if 'PATCH' == 'POST' else 200
self.assertNotEqual(201, old_expected)
FakeHandler.status = 201
self.assertEqual(self.request('PATCH', 201), {'ok': True})
self.assertEqual(FakeHandler.calls, 1)
def test_failure_status_cannot_be_configured_as_success(self):
calls = []
for status in (100, 199, 300, 400, 500, 599):
with self.subTest(status=status), self.assertRaises(dr.InvalidExpectationError):
dr.deliver_once(lambda: calls.append(1) or (status, b'{}'), expected_status=status)
self.assertEqual(calls, [])
def test_raised_auth_or_redirect_error_is_rejected_not_uncertain(self):
for status in (300, 302, 304, 399, 401, 403):
calls = []
def send():
calls.append(1)
raise HTTPError('http://example.invalid/private', status, 'sensitive detail', {}, None)
with self.subTest(status=status):
with self.assertRaises(dr.RejectedResponseError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertEqual(calls, [1])
self.assertNotIn('sensitive detail', str(caught.exception))
def test_sender_explicit_rejection_is_not_reclassified_as_uncertain(self):
def send():
raise dr.RejectedResponseError('sensitive detail')
with self.assertRaises(dr.RejectedResponseError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertNotIn('sensitive detail', str(caught.exception))
def test_tls_certificate_rejection_is_not_reclassified_as_uncertain(self):
import ssl
from urllib.error import URLError
for error in (ssl.SSLCertVerificationError('sensitive detail'),
URLError(ssl.SSLCertVerificationError('sensitive detail'))):
def send():
raise error
with self.subTest(error=type(error).__name__), self.assertRaises(dr.RejectedResponseError):
dr.deliver_once(send, expected_status=201)
def test_auth_and_redirect_rejected(self):
for status in (401, 403, 302):
with self.subTest(status=status):
FakeHandler.status = status
with self.assertRaises(dr.RejectedResponseError):
self.request('POST', 201)
def test_bad_json_shape_and_size_are_uncertain(self):
for body in (b'{', b'[]', b'null', b'{"x":NaN}', b'"x"', b' ' * (1024 * 1024 + 1)):
with self.subTest(body=body[:12]):
FakeHandler.body = body
FakeHandler.status = 201
with self.assertRaises(dr.AcknowledgementUncertainError):
self.request('POST', 201)
def test_transport_error_once_and_sanitized_uncertainty(self):
calls = []
def send():
calls.append(1)
raise OSError('secret token should not leak')
with self.assertRaises(dr.AcknowledgementUncertainError) as caught:
dr.deliver_once(send, expected_status=201)
self.assertEqual(len(calls), 1)
self.assertNotIn('secret token', str(caught.exception))
self.assertIn('reconcil', str(caught.exception).lower())
def test_control_exceptions_propagate(self):
for exc in (KeyboardInterrupt(), SystemExit()):
with self.subTest(exc=type(exc).__name__):
with self.assertRaises(type(exc)):
dr.deliver_once(lambda: (_ for _ in ()).throw(exc), expected_status=200)
if __name__ == '__main__':
unittest.main()