Files
gitea-agent/harness/agents/review/skills/gitea-code-review/scripts/native_review.py
T

1164 lines
59 KiB
Python

#!/usr/bin/env python3
"""Bounded code-review publication using the caller's authenticated adapter."""
from __future__ import annotations
import hashlib
import importlib.util
import json
import re
from pathlib import Path
# Reuse public JSON/page/cache and canonical spec codecs, not the spec-only publisher.
_SPEC_PATH = (Path(__file__).resolve().parents[4] /
'spec/skills/gitea-implementation-spec-review/scripts/review_support.py')
_spec = importlib.util.spec_from_file_location('gitea_native_spec_support', _SPEC_PATH)
_support = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_support)
_LOCK_PATH = Path(__file__).resolve().parents[5] / 'lib/managed_lock.py'
_lock_spec = importlib.util.spec_from_file_location('gitea_managed_lock', _LOCK_PATH)
_locks = importlib.util.module_from_spec(_lock_spec)
_lock_spec.loader.exec_module(_locks)
_BROKER_PATH = Path(__file__).resolve().parents[5] / 'lib/managed_lock_broker.py'
_broker_spec = importlib.util.spec_from_file_location('gitea_managed_lock_broker', _BROKER_PATH)
_broker = importlib.util.module_from_spec(_broker_spec)
_broker_spec.loader.exec_module(_broker)
_JOURNAL_PATH = Path(__file__).resolve().with_name('publication_journal.py')
_journal_spec = importlib.util.spec_from_file_location('gitea_publication_journal', _JOURNAL_PATH)
_journal = importlib.util.module_from_spec(_journal_spec)
_journal_spec.loader.exec_module(_journal)
ValidationError = _support.ValidationError
loads_json = _support.loads_json
collect_pages = _support.collect_pages
ORIGIN = 'https://gitea.jsfamily.quest'
SCHEMA = 'gitea-agent.code-review-publication/v1'
def canonical_journal_path(plan):
"""Return the fixed, versioned path for this publication binding."""
return _journal.canonical_path(plan, anchor_file=__file__, origin=ORIGIN,
validate=validate_plan)
def _expected_intent(plan, before):
payload = build_payload(plan)
return {'schema': 'gitea-agent.native-review-intent/v1',
'fingerprint': hashlib.sha256(canonical(plan)).hexdigest(),
'before_sha256': hashlib.sha256(canonical(before)).hexdigest(),
'head': plan['target']['head'], 'event': payload['event'],
'body_sha256': hashlib.sha256(payload['body'].encode()).hexdigest()}
def _legacy_path(directory):
import os, stat
value = os.fspath(directory)
if not isinstance(value, str) or not value.startswith('/') or any(
part in ('', '.', '..') for part in value.split('/')[1:]):
fail('unsafe legacy journal path')
source = Path(value)
try:
_journal._check_ancestor_chain(source)
st = source.lstat()
except (OSError, ValueError):
fail('unsafe legacy journal')
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
fail('unsafe legacy journal')
return source, (st.st_dev, st.st_ino)
from contextlib import contextmanager
@contextmanager
def _locked_legacy(directory):
import fcntl, os, stat
source, identity = _legacy_path(directory)
fd = None
try:
# Never create a source mutex: registration preserves the entire source.
fd = os.open(source / 'publication.lock', os.O_RDWR | os.O_NOFOLLOW | os.O_NONBLOCK)
st = os.fstat(fd)
if not stat.S_ISREG(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o600 or st.st_nlink != 1:
fail('unsafe legacy journal lock')
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
def check():
_, current = _legacy_path(source)
live = (source / 'publication.lock').lstat()
if current != identity or (live.st_dev, live.st_ino) != (st.st_dev, st.st_ino):
fail('legacy journal identity changed')
check()
yield source, check
check()
except ValidationError:
raise
except (OSError, ValueError):
fail('legacy journal unavailable or unsafe')
finally:
if fd is not None:
os.close(fd)
def _capture_legacy(source):
import stat
records = {}; raw_records = {}; inventory = []
allowed = {'before.json', 'intent.json', 'verified.json', 'publication.lock'}
try:
entries = list(source.iterdir())
except OSError:
fail('legacy journal artifact unavailable or unsafe')
for entry in entries:
if entry.name not in allowed:
fail('unknown legacy journal artifact')
try:
st = entry.lstat()
except OSError:
fail('legacy journal artifact unavailable or unsafe')
if entry.name == 'publication.lock':
continue
if not stat.S_ISREG(st.st_mode) or st.st_nlink != 1:
fail('unsafe legacy journal artifact')
for name in ('before.json', 'intent.json', 'verified.json'):
try:
raw = _journal._secure_file(source / name, cap=16 * 1024 * 1024 if name == 'before.json' else 1024 * 1024)
except FileNotFoundError:
continue
except (OSError, ValueError):
fail('legacy journal artifact unavailable or unsafe')
try:
records[name] = loads_json(raw.decode('utf-8'))
raw_records[name] = raw
except (UnicodeError, ValueError):
fail('malformed legacy journal artifact')
inventory.append({'name': name, 'size': len(raw), 'sha256': hashlib.sha256(raw).hexdigest()})
framed = {'schema': 'gitea-agent.legacy-native-journal-digest/v1', 'files': inventory}
return records, raw_records, framed, hashlib.sha256(canonical(framed)).hexdigest()
def legacy_journal_digest(legacy_directory):
"""Read-only digest of exact, named source bytes under its existing mutex."""
with _locked_legacy(legacy_directory) as (source, check):
_, _, _, digest_value = _capture_legacy(source)
check()
return digest_value
def _assert_registry(entry, plan):
try:
_journal.assert_bound(entry, plan, anchor_file=__file__, origin=ORIGIN, validate=validate_plan)
except (OSError, ValueError):
fail('canonical journal binding unavailable or changed')
def _check_registration(journal, plan):
if journal.read('import.pending.json') is not None:
fail('interrupted legacy registration')
receipt = journal.read('registration.json')
if receipt is None:
return
keys = {'schema', 'state', 'legacy_sha256', 'source_files', 'imported_files', 'before_sha256', 'intent_sha256'}
if type(receipt) is not dict or set(receipt) != keys or receipt['schema'] != 'gitea-agent.native-review-import/v1' or receipt['state'] != 'complete':
fail('interrupted or malformed legacy registration')
files = receipt['source_files']
if type(files) is not list or len(files) not in (2, 3):
fail('malformed legacy registration file inventory')
if [f.get('name') if type(f) is dict else None for f in files] not in (
['before.json', 'intent.json'], ['before.json', 'intent.json', 'verified.json']):
fail('malformed legacy registration file inventory')
for f in files:
if set(f) != {'name', 'size', 'sha256'} or type(f['size']) is not int or not 0 <= f['size'] <= (16 * 1024 * 1024 if f['name'] == 'before.json' else 1024 * 1024):
fail('malformed legacy registration file inventory')
digest(f['sha256'], 64)
imported = receipt['imported_files']
if type(imported) is not list or [f.get('source') if type(f) is dict else None for f in imported] not in (
['before.json', 'intent.json'], ['before.json', 'intent.json', 'verified.json']):
fail('malformed imported legacy file inventory')
if [f['source'] for f in imported] != [f['name'] for f in files]:
fail('incomplete imported legacy file inventory')
for f in imported:
if (set(f) != {'source', 'target', 'size', 'sha256'}
or f['target'] != ('legacy-verified.json' if f['source'] == 'verified.json' else f['source'])
or type(f['size']) is not int or f['size'] < 0):
fail('malformed imported legacy file inventory')
digest(f['sha256'], 64)
source = next((row for row in files if row['name'] == f['source']), None)
if source is None or source['size'] != f['size'] or source['sha256'] != f['sha256']:
fail('legacy source/import receipt mismatch')
for field in ('legacy_sha256', 'before_sha256', 'intent_sha256'):
digest(receipt[field], 64)
framed = {'schema': 'gitea-agent.legacy-native-journal-digest/v1', 'files': files}
if hashlib.sha256(canonical(framed)).hexdigest() != receipt['legacy_sha256']:
fail('legacy registration receipt mismatch')
before = journal.read('before.json'); intent = journal.read('intent.json')
if before is None or intent is None or hashlib.sha256(canonical(before)).hexdigest() != receipt['before_sha256'] or hashlib.sha256(canonical(intent)).hexdigest() != receipt['intent_sha256']:
fail('legacy registration artifact mismatch')
if canonical(intent) != canonical(_expected_intent(plan, before)):
fail('legacy registration intent mismatch')
for f in imported:
try:
raw = _journal._secure_file(Path(journal.path) / f['target'],
cap=16 * 1024 * 1024 if f['source'] == 'before.json' else 1024 * 1024)
except (OSError, ValueError):
fail('legacy imported artifact unavailable or unsafe')
if len(raw) != f['size'] or hashlib.sha256(raw).hexdigest() != f['sha256']:
fail('legacy imported artifact integrity mismatch')
try:
loads_json(raw.decode('utf-8'))
except (UnicodeError, ValueError):
fail('malformed legacy imported artifact')
def register_legacy_journal(plan, legacy_directory, *, expected_legacy_sha256,
api, lease, declaration=None):
"""Explicit, local-only import; preserve source and never retry native POST."""
validate_plan(plan)
if type(expected_legacy_sha256) is not str or not re.fullmatch(r'[0-9a-f]{64}', expected_legacy_sha256):
fail('invalid legacy journal digest')
require_adoption(declaration, plan['target']['head'])
check_lease(lease, plan)
role_identity(read_api(api, 'identity'), plan['reviewer'])
validate_snapshot(plan, read_api(api, 'snapshot', plan['target']))
with _locked_legacy(legacy_directory) as (source, check):
records, raw_records, framed, digest_value = _capture_legacy(source)
if digest_value != expected_legacy_sha256:
fail('legacy journal digest mismatch')
before = records.get('before.json'); intent = records.get('intent.json')
if type(before) is not dict or type(intent) is not dict:
fail('legacy journal lacks durable intent')
validate_snapshot(plan, before)
if canonical(intent) != canonical(_expected_intent(plan, before)):
fail('legacy journal intent mismatch')
if 'verified.json' in records and type(records['verified.json']) is not dict:
fail('invalid legacy verification artifact')
check(); check_lease(lease, plan)
target = canonical_journal_path(plan)
try:
entry = _journal.open_binding(target, plan, anchor_file=__file__, origin=ORIGIN,
validate=validate_plan, require_new=True)
except (OSError, ValueError):
fail('canonical registration target unavailable or already exists')
with Journal(entry) as journal:
_assert_registry(entry, plan)
if journal.read('import.pending.json') != {'schema': 'gitea-agent.native-review-import/v1', 'state': 'pending'}:
fail('legacy registration bootstrap incomplete')
if any(journal.read(name) is not None for name in ('before.json', 'intent.json', 'verified.json', 'registration.json')):
fail('canonical journal already populated')
receipt = {'schema': 'gitea-agent.native-review-import/v1', 'state': 'pending',
'legacy_sha256': digest_value, 'source_files': framed['files'],
'before_sha256': hashlib.sha256(canonical(before)).hexdigest(),
'intent_sha256': hashlib.sha256(canonical(intent)).hexdigest()}
# Durable pending receipt precedes copying any former attempt.
journal.write('registration.json', receipt)
imported_files = []
for name in ('before.json', 'intent.json', 'verified.json'):
if name in raw_records:
target_name = 'legacy-verified.json' if name == 'verified.json' else name
try:
_journal._atomic_bytes(entry, target_name, raw_records[name])
persisted = _journal._secure_file(entry / target_name,
cap=16 * 1024 * 1024 if name == 'before.json' else 1024 * 1024)
except (OSError, ValueError):
fail('legacy artifact import failed')
if persisted != raw_records[name]:
fail('legacy artifact integrity mismatch')
imported_files.append({'source': name, 'target': target_name,
'size': len(raw_records[name]),
'sha256': hashlib.sha256(raw_records[name]).hexdigest()})
receipt['imported_files'] = imported_files
journal.write('registration.json', receipt)
check()
_assert_registry(entry, plan)
receipt['state'] = 'complete'
journal.write('registration.json', receipt)
# The final receipt is durable before removing bootstrap's sentinel.
import os
journal.check_identity()
os.unlink(entry / 'import.pending.json')
_journal._fsync_dir(entry)
return {'registered': True, 'journal_directory': str(entry), 'read_only': True}
def fail(message):
raise ValidationError(message)
def require_adoption(declaration, revision):
if declaration is None:
fail('delegation adoption declaration required')
try:
repo_root = Path(__file__).resolve().parents[6]
import sys
if str(repo_root) not in sys.path:
sys.path.insert(0, str(repo_root))
from harness.lib.delegation_adoption import AdoptionError, require_adoption as adopt
except Exception:
fail('delegation adoption unavailable')
try:
return adopt(declaration, expected_source={'kind': 'git_tree', 'revision': revision}, expected_stage='code-review')
except AdoptionError as exc:
fail(str(exc))
except Exception:
fail('delegation adoption unavailable')
def positive_id(value):
if type(value) is not int or value < 1:
fail('invalid positive identity')
return value
def digest(value, length):
if type(value) is not str or not re.fullmatch('[0-9a-f]{' + str(length) + '}', value):
fail('invalid digest')
return value
def marker(plan):
t = plan['target']
return (f'<!-- gitea-agent:code-review/v1 owner={t["owner"]} repository={t["repository"]} '
f'issue={t["issue"]} pr={t["pull"]} head={t["head"]} '
f'contract-sha256={plan["contract_sha256"]} -->')
def validate_plan(plan):
keys = {'schema', 'target', 'contract_sha256', 'reviewer', 'spec_reviewer',
'spec_review_id', 'spec_paths', 'verdict', 'body', 'provider_contract'}
if type(plan) is not dict or set(plan) != keys or plan['schema'] != SCHEMA:
fail('invalid publication plan')
t = plan['target']
if type(t) is not dict or set(t) != {'owner', 'repository', 'issue', 'pull', 'head', 'base'}:
fail('invalid target schema')
for k in ['owner', 'repository']:
value = t[k]
if type(value) is not str or not re.fullmatch(r'[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}', value):
fail('invalid target identifier')
for k in ['issue', 'pull']:
positive_id(t[k])
for k in ['head', 'base']:
digest(t[k], 40)
digest(plan['contract_sha256'], 64)
for k, login in [('reviewer', 'gitea-review-agent'), ('spec_reviewer', 'gitea-spec-agent')]:
who = plan[k]
if type(who) is not dict or set(who) != {'id', 'login'} or who['login'] != login:
fail('invalid role identity')
positive_id(who['id'])
if plan['reviewer']['id'] == plan['spec_reviewer']['id']:
fail('reviewer independence violation')
positive_id(plan['spec_review_id'])
paths = plan['spec_paths']
if type(paths) is not list or any(type(x) is not str or not x or x.startswith('/')
or any(p in ['', '.', '..'] for p in x.split('/'))
or any(ord(c) < 32 for c in x) for x in paths) or len(set(paths)) != len(paths):
fail('invalid declared spec path scope')
if type(plan['verdict']) is not str or plan['verdict'] not in ['approve', 'request_changes']:
fail('unsupported publication verdict')
body = plan['body']
if type(body) is not str or not 1 <= len(body) <= 250000:
fail('invalid body')
try:
body.encode('utf-8')
except UnicodeError:
fail('invalid body encoding')
if (not body.startswith(marker(plan) + '\n') or body.count('<!-- gitea-agent:code-review/v1 ') != 1
or [x for x in body.splitlines() if x.startswith('- Verdict:')] != [f'- Verdict: `{plan["verdict"]}`']):
fail('body binding mismatch')
provider = plan['provider_contract']
fields = {'supersede_same_author', 'retouch_dismissed', 'unordered_commit_files', 'evidence'}
if type(provider) is not dict or set(provider) != fields:
fail('invalid provider contract')
flags = [provider[k] for k in fields - {'evidence'}]
if any(type(x) is not bool for x in flags) or type(provider['evidence']) is not list:
fail('invalid provider projection flags')
if any(type(x) is not str or not x.strip() for x in provider['evidence']):
fail('invalid provider evidence')
# Replacement booleans remain typed v1 compatibility fields, not permissions.
# Normal same-author metadata transitions are verified from before/after/exact.
# File-order normalization is still a separate, evidence-backed exception.
if provider['unordered_commit_files'] and not provider['evidence']:
fail('provider projection requires evidence')
return plan
def build_payload(plan):
validate_plan(plan)
return {'commit_id': plan['target']['head'], 'body': plan['body'],
'event': {'approve': 'APPROVED', 'request_changes': 'REQUEST_CHANGES'}[plan['verdict']],
'comments': []}
def canonical(value):
# Reuse duplicate/nonfinite validation before fingerprinting or comparison.
try:
text = json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False, separators=(',', ':'))
loads_json(text)
return text.encode('utf-8')
except (TypeError, ValueError, UnicodeError):
fail('invalid JSON artifact')
def indexed(items, key='id'):
if type(items) is not list:
fail('invalid collection')
out = {}
for item in items:
if type(item) is not dict or key not in item:
fail('invalid collection item')
identity = item[key]
if key == 'id':
positive_id(identity)
elif key == 'sha':
digest(identity, 40)
elif (type(identity) is not str or not identity or identity.startswith('/')
or '\\' in identity or any(p in ['', '.', '..'] for p in identity.split('/'))):
fail('invalid filename')
if identity in out:
fail('duplicate collection identity')
out[identity] = item
return out
def check_pages(items, pages):
if type(pages) is not list or not pages:
fail('missing complete pagination evidence')
total = None; counted = 0
for number, page in enumerate(pages, 1):
if type(page) is not dict or set(page) != {'page', 'count', 'declared_total', 'link'}:
fail('invalid pagination record')
if type(page['page']) is not int or page['page'] != number:
fail('invalid page sequence')
count = page['count']; declared = page['declared_total']; link = page['link']
if type(count) is not int or not 0 <= count <= 50:
fail('invalid page size')
if declared is not None:
if type(declared) is not int or declared < 0 or (total is not None and declared != total):
fail('conflicting declared total')
total = declared
if link is not None and type(link) is not str:
fail('invalid Link header')
if number < len(pages) and count != 50:
fail('invalid pagination continuation')
counted += count
last = pages[-1]
if counted != len(items) or (total is not None and total != counted):
fail('collection cardinality mismatch')
if (last['link'] and re.search(r'rel=["\']?next', last['link'])) or (total is None and last['count'] == 50):
fail('truncated pagination')
def role_identity(value, expected):
if type(value) is not dict or type(value.get('id')) is not int or value.get('id') != expected['id'] or value.get('login') != expected['login']:
fail('role identity mismatch')
if value.get('origin', ORIGIN + '/api/v1') != ORIGIN + '/api/v1':
fail('origin mismatch')
def validate_snapshot(plan, snapshot):
validate_plan(plan)
required = {'identity', 'spec_user', 'repository', 'issue', 'pr', 'files', 'commits',
'reviews', 'review_comments', 'issue_comments', 'pr_comments', 'statuses', 'pagination'}
if type(snapshot) is not dict or not required <= set(snapshot):
fail('incomplete snapshot')
canonical(snapshot)
t = plan['target']; full = t['owner'] + '/' + t['repository']
role_identity(snapshot['identity'], plan['reviewer'])
role_identity(snapshot['spec_user'], plan['spec_reviewer'])
repo = snapshot['repository']; issue = snapshot['issue']; pr = snapshot['pr']
if any(type(x) is not dict for x in [repo, issue, pr, snapshot['pagination']]):
fail('invalid target snapshot shape')
positive_id(issue.get('number')); positive_id(pr.get('number'))
if 'repository' in issue and (type(issue['repository']) is not dict or issue['repository'].get('full_name') != full):
fail('source Issue repository mismatch')
for side in ['base', 'head']:
if (type(pr.get(side)) is not dict or type(pr[side].get('repo')) is not dict
or type(pr[side]['repo'].get('full_name')) is not str):
fail('invalid PR repository binding')
if (repo.get('full_name') != full or issue.get('number') != t['issue']
or pr.get('number') != t['pull'] or pr.get('html_url') != f'{ORIGIN}/{full}/pulls/{t["pull"]}'
or issue.get('state') != 'open' or pr.get('state') != 'open' or pr.get('merged') is not False):
fail('target or lifecycle mismatch')
if type(issue.get('body')) is not str or hashlib.sha256(issue['body'].encode('utf-8')).hexdigest() != plan['contract_sha256']:
fail('contract drift')
if snapshot.get('contract_sha256', plan['contract_sha256']) != plan['contract_sha256']:
fail('contract digest mismatch')
if (pr['base']['sha'] != t['base'] or pr['head']['sha'] != t['head']
or pr['base']['repo']['full_name'] != full or pr['base']['ref'] != repo['default_branch']):
fail('head/base/default branch drift')
author = positive_id(pr['user']['id'])
if author in [plan['reviewer']['id'], plan['spec_reviewer']['id']]:
fail('reviewer independence violation')
links = re.findall(r'(?im)^(?:closes|fixes|resolves)[ \t]+[^\r\n]+$', pr.get('body', ''))
if links != ['Closes #' + str(t['issue'])]:
fail('source issue linkage mismatch')
for name, key in [('files', 'filename'), ('commits', 'sha'), ('reviews', 'id'),
('issue_comments', 'id'), ('pr_comments', 'id'), ('statuses', 'id')]:
indexed(snapshot[name], key)
check_pages(snapshot[name], snapshot['pagination'].get(name))
review_map = indexed(snapshot['reviews'])
comments = snapshot['review_comments']
if type(comments) is not dict or set(comments) != {str(rid) for rid in review_map}:
fail('incomplete review comment collection')
for rid, review in review_map.items():
if type(review.get('user')) is not dict:
fail('invalid historical reviewer identity')
positive_id(review['user'].get('id'))
digest(review.get('commit_id'), 40)
if (type(review.get('body')) is not str or review.get('state') not in ['COMMENT', 'PENDING', 'APPROVED', 'REQUEST_CHANGES']
or any(type(review.get(k)) is not bool for k in ['official', 'dismissed', 'stale'])):
fail('invalid native review shape')
indexed(comments[str(rid)])
check_pages(comments[str(rid)], snapshot['pagination'].get('review_comments_' + str(rid)))
if type(review.get('comments_count')) is not int or review['comments_count'] != len(comments[str(rid)]):
fail('review comment cardinality mismatch')
approved = [r for r in snapshot['reviews'] if r.get('user', {}).get('id') == plan['spec_reviewer']['id']
and r.get('commit_id') == t['head'] and r.get('state') == 'APPROVED'
and r.get('stale') is False and r.get('dismissed') is False]
if len(approved) != 1 or approved[0]['id'] != plan['spec_review_id']:
fail('missing or ambiguous current-head spec approval')
role_identity(approved[0]['user'], plan['spec_reviewer'])
parsed = _support.parse_review(approved[0]['body'], set(plan['spec_paths']))
if (parsed['verdict'] != 'approve' or parsed['contract_sha256'] != plan['contract_sha256']
or parsed['target'] != {k: t[k] for k in ['owner', 'repository', 'issue', 'pull', 'head']}):
fail('spec approval binding mismatch')
return snapshot
def normalized_commits(commits, enabled):
import copy
out = copy.deepcopy(commits)
for row in out:
files = row.get('files', [])
indexed(files, 'filename')
if enabled and 'files' in row:
row['files'] = sorted(files, key=lambda f: f['filename'])
return out
def same_source(plan, before, after, new_review=None):
import copy
validate_snapshot(plan, before); validate_snapshot(plan, after)
a = copy.deepcopy(before); b = copy.deepcopy(after)
mutable = {'reviews', 'review_comments', 'pagination'}
# Provider-derived changes are tied to this exact review, never globally ignored.
if new_review is not None:
times = {new_review['submitted_at'], new_review['updated_at']}
for section in ['issue', 'pr']:
if a[section].get('updated_at') != b[section].get('updated_at'):
if b[section].get('updated_at') not in times:
fail('unrelated timestamp mutation')
b[section]['updated_at'] = a[section].get('updated_at')
if 'review_comments' in a['pr']:
old = a['pr']['review_comments']; new = b['pr'].get('review_comments')
if type(old) is not int or type(new) is not int or new != old + 1:
fail('PR review count projection mismatch')
b['pr']['review_comments'] = old
requested_before = indexed(a['pr'].get('requested_reviewers', []))
requested_after = indexed(b['pr'].get('requested_reviewers', []))
extra = set(requested_after) - set(requested_before)
if set(requested_before) - set(requested_after) or extra - {plan['reviewer']['id']}:
fail('unrelated requested reviewer mutation')
if any(canonical(row) != canonical(requested_after[rid]) for rid, row in requested_before.items()):
fail('requested reviewer content changed')
if extra:
role_identity(requested_after[plan['reviewer']['id']], plan['reviewer'])
b['pr']['requested_reviewers'] = a['pr'].get('requested_reviewers', [])
for data in [a, b]:
data['files'] = sorted(data['files'], key=lambda f: f['filename'])
data['commits'] = normalized_commits(data['commits'], plan['provider_contract']['unordered_commit_files'])
if canonical({k: v for k, v in a.items() if k not in mutable}) != canonical({k: v for k, v in b.items() if k not in mutable}):
fail('source snapshot drift')
def exact_binding(plan, snapshot, exact):
payload = build_payload(plan)
if type(exact) is not dict:
fail('invalid exact review')
rid = positive_id(exact.get('id'))
role_identity(exact.get('user'), plan['reviewer'])
if (exact.get('body') != payload['body'] or exact.get('commit_id') != payload['commit_id']
or exact.get('state') != payload['event'] or exact.get('stale') is not False
or exact.get('dismissed') is not False or type(exact.get('official')) is not bool
or type(exact.get('comments_count')) is not int or exact['comments_count'] != 0
or exact.get('pull_request_url') != snapshot['pr']['html_url']):
fail('exact review binding mismatch')
if snapshot['review_comments'].get(str(rid)) != []:
fail('inline comments are unsupported')
if canonical(indexed(snapshot['reviews']).get(rid)) != canonical(exact):
fail('exact review differs from complete collection')
for key in ['submitted_at', 'updated_at']:
from datetime import datetime
try:
stamp = datetime.fromisoformat(exact[key].replace('Z', '+00:00'))
if stamp.utcoffset() is None:
fail('unbound review timestamp')
except (KeyError, ValueError, TypeError, AttributeError):
fail('invalid review timestamp')
return rid
def verify_publication(plan, before, after, exact):
rid = exact_binding(plan, validate_snapshot(plan, after), exact)
same_source(plan, before, after, exact)
old = indexed(before['reviews']); new = indexed(after['reviews'])
if set(new) - set(old) != {rid} or set(old) - set(new):
fail('review cardinality mismatch')
current = [r for r in after['reviews'] if r['user']['id'] == plan['reviewer']['id']
and r['commit_id'] == plan['target']['head'] and r['state'] in ['PENDING', 'APPROVED', 'REQUEST_CHANGES']
and not r['dismissed']]
if len(current) != 1 or current[0]['id'] != rid:
fail('competing current-head review')
projected = []
for old_id, review in old.items():
fresh = new[old_id]
if before['review_comments'][str(old_id)] != after['review_comments'][str(old_id)]:
fail('historical review comments changed')
if canonical(review) == canonical(fresh):
continue
permitted = {'official', 'dismissed', 'updated_at'}
if (review['user']['id'] != plan['reviewer']['id'] or review['state'] not in ['APPROVED', 'REQUEST_CHANGES']
or set(review) != set(fresh)
or canonical({k: v for k, v in review.items() if k not in permitted}) != canonical({k: v for k, v in fresh.items() if k not in permitted})):
fail('historical review immutable content changed')
# Normal same-author Gitea replacement is part of the readback contract,
# not an opt-in exception. Immutable fields/comments were checked above;
# only these exact flag transitions and the new review timestamp qualify.
superseded = review.get('official') is True and review.get('dismissed') is False
retouched = review.get('official') is False and review.get('dismissed') is True
if (not (superseded or retouched) or fresh.get('official') is not False or fresh.get('dismissed') is not True
or fresh.get('updated_at') not in {exact['submitted_at'], exact['updated_at']}):
fail('unauthorized historical review projection')
projected.append(old_id)
return {'verified': True, 'review_id': rid, 'state': exact['state'], 'head': plan['target']['head'],
'projected_review_ids': projected, 'review_records_added': 1,
'commit_file_order_normalized': before['commits'] != after['commits'], 'read_only': True}
class UncertainPublication(ValidationError):
"""A durable attempt exists; a second create is prohibited."""
class Journal:
def __init__(self, directory):
import os, stat
self.path = Path(directory)
if not self.path.is_absolute():
fail('journal must be an absolute trusted path')
for p in [self.path, *self.path.parents]:
if p.is_symlink():
fail('symlinked journal path')
self.path.mkdir(mode=0o700, exist_ok=True)
st = self.path.lstat()
if not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
fail('unsafe journal directory')
self.fd = None
self.identity = (st.st_dev, st.st_ino)
def check_identity(self):
import os, stat
try:
_journal._check_ancestor_chain(self.path)
st = self.path.lstat()
if (st.st_dev, st.st_ino) != self.identity or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o700:
fail('journal directory identity changed')
if self.fd is not None:
held = os.fstat(self.fd); named = (self.path / 'publication.lock').lstat()
if (held.st_dev, held.st_ino) != (named.st_dev, named.st_ino) or not stat.S_ISREG(named.st_mode) or named.st_nlink != 1 or named.st_uid != os.getuid() or stat.S_IMODE(named.st_mode) != 0o600:
fail('journal mutex identity changed')
except (OSError, ValueError):
fail('journal identity unavailable or changed')
def __enter__(self):
import fcntl, os, stat
self.fd = os.open(self.path / 'publication.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
try:
st = os.fstat(self.fd)
if not stat.S_ISREG(st.st_mode) or st.st_uid != os.getuid() or stat.S_IMODE(st.st_mode) != 0o600 or st.st_nlink != 1:
fail('unsafe journal lock')
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
self.check_identity()
except OSError:
os.close(self.fd); self.fd = None
fail('journal mutex unavailable or busy')
except BaseException:
os.close(self.fd); self.fd = None
raise
return self
def __exit__(self, *args):
import os
os.close(self.fd)
self.fd = None
def read(self, name):
self.check_identity()
if name not in ('before.json', 'intent.json', 'verified.json', 'import.pending.json', 'registration.json'):
fail('unsupported journal artifact')
try:
cap = 16 * 1024 * 1024 if name == 'before.json' else 1024 * 1024
data = _journal._secure_file(self.path / name, cap=cap)
except FileNotFoundError:
return None
except (OSError, ValueError):
fail('journal artifact unavailable or unsafe')
try:
value = loads_json(data.decode('utf-8'))
except (UnicodeError, ValueError):
fail('malformed journal artifact')
self.check_identity()
return value
def write(self, name, value):
import os, tempfile
self.read(name) # Reject existing symlink/hardlink/foreign artifacts.
data = canonical(value)
cap = 16 * 1024 * 1024 if name == 'before.json' else 1024 * 1024
if len(data) > cap:
fail('journal artifact exceeds bounds')
fd, tmp = tempfile.mkstemp(dir=self.path, prefix='.native-review-')
try:
with os.fdopen(fd, 'wb') as out:
out.write(data); out.flush(); os.fsync(out.fileno())
self.check_identity()
os.replace(tmp, self.path / name)
directory = os.open(self.path, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(directory)
finally:
os.close(directory)
finally:
if os.path.exists(tmp):
os.unlink(tmp)
def candidates(plan, snapshot):
own = [r for r in snapshot['reviews'] if r['user']['id'] == plan['reviewer']['id']
and r['commit_id'] == plan['target']['head'] and r['state'] in ['PENDING', 'APPROVED', 'REQUEST_CHANGES']]
matching = [r for r in own if marker(plan) in r.get('body', '')]
if len(matching) > 1 or any(r not in matching for r in own):
fail('duplicate or conflicting current-head review')
if matching and (matching[0]['body'] != plan['body'] or matching[0]['state'] != build_payload(plan)['event']):
fail('conflicting canonical review')
return matching
def read_api(api, method, *args):
try:
return getattr(api, method)(*args)
except Exception:
raise ValidationError('authoritative API read unavailable; do not retry POST') from None
def check_lease(lease, plan):
try:
result = lease.check(plan)
except Exception:
raise ValidationError('held Issue lease invalid or unavailable') from None
if result is not None:
fail('unsupported lease check result')
def publish(plan, baseline, api, lease, journal_directory=None, *, declaration=None):
"""Publish through the fixed canonical journal binding; never retry POST."""
import copy
validate_snapshot(plan, baseline)
require_adoption(declaration, plan['target']['head'])
payload = build_payload(plan)
if any(not callable(getattr(api, k, None)) for k in ['identity', 'snapshot', 'create_review', 'read_review']):
fail('unsupported adapter capabilities')
if not callable(getattr(lease, 'check', None)):
fail('missing held Issue lease')
fingerprint = hashlib.sha256(canonical(plan)).hexdigest()
check_lease(lease, plan)
expected_directory = canonical_journal_path(plan)
if journal_directory is not None:
import os
supplied = os.fspath(journal_directory)
if type(supplied) is not str or supplied != str(expected_directory):
fail('non-canonical journal path')
# Authenticate the reviewer before claiming a registry binding.
role_identity(read_api(api, 'identity'), plan['reviewer'])
try:
bound_directory = _journal.open_binding(expected_directory, plan, anchor_file=__file__, origin=ORIGIN, validate=validate_plan)
except (OSError, ValueError):
fail('canonical journal unavailable or conflicting binding')
with Journal(bound_directory) as journal:
_assert_registry(bound_directory, plan)
_check_registration(journal, plan)
intent = journal.read('intent.json')
if intent is None and any(journal.read(name) is not None for name in ('before.json', 'verified.json')):
raise UncertainPublication('incomplete canonical journal; do not retry POST')
before = baseline
if intent is not None:
if type(intent) is not dict or intent.get('fingerprint') != fingerprint:
fail('journal belongs to a different publication')
before = journal.read('before.json')
if before is None or hashlib.sha256(canonical(before)).hexdigest() != intent.get('before_sha256'):
fail('journal baseline integrity mismatch')
validate_snapshot(plan, before)
expected_intent = {'schema': 'gitea-agent.native-review-intent/v1', 'fingerprint': fingerprint,
'before_sha256': hashlib.sha256(canonical(before)).hexdigest(),
'head': plan['target']['head'], 'event': payload['event'],
'body_sha256': hashlib.sha256(payload['body'].encode()).hexdigest()}
if canonical(intent) != canonical(expected_intent):
fail('journal intent schema or binding mismatch')
check_lease(lease, plan)
role_identity(read_api(api, 'identity'), plan['reviewer'])
fresh = validate_snapshot(plan, read_api(api, 'snapshot', plan['target']))
matching = candidates(plan, fresh)
if matching:
exact = read_api(api, 'read_review', plan['target'], matching[0]['id'])
if exact['id'] in indexed(before['reviews']):
same_source(plan, before, fresh)
if canonical(before['reviews']) != canonical(fresh['reviews']) or before['review_comments'] != fresh['review_comments']:
fail('no-op baseline drift')
rid = exact_binding(plan, fresh, exact)
result = {'verified': True, 'review_id': rid, 'state': exact['state'], 'head': plan['target']['head'], 'review_records_added': 0}
else:
result = verify_publication(plan, before, fresh, exact)
check_lease(lease, plan)
result.update(mode='no_op', read_only=False, wrote=False)
journal.write('verified.json', result)
return result
if intent is not None:
raise UncertainPublication('durable attempt has no authoritative match; do not retry POST')
same_source(plan, baseline, fresh)
if canonical(baseline['reviews']) != canonical(fresh['reviews']) or baseline['review_comments'] != fresh['review_comments']:
fail('review baseline drift')
check_lease(lease, plan)
# A second source read immediately precedes intent/create; do not mutate a stale baseline.
role_identity(read_api(api, 'identity'), plan['reviewer'])
latest = validate_snapshot(plan, read_api(api, 'snapshot', plan['target']))
same_source(plan, fresh, latest)
if canonical(fresh['reviews']) != canonical(latest['reviews']) or fresh['review_comments'] != latest['review_comments']:
fail('review baseline drift')
before = copy.deepcopy(latest)
journal.write('before.json', before)
journal.write('intent.json', {'schema': 'gitea-agent.native-review-intent/v1', 'fingerprint': fingerprint,
'before_sha256': hashlib.sha256(canonical(before)).hexdigest(),
'head': plan['target']['head'], 'event': payload['event'],
'body_sha256': hashlib.sha256(payload['body'].encode()).hexdigest()})
check_lease(lease, plan)
require_adoption(declaration, plan['target']['head'])
journal.check_identity()
_assert_registry(bound_directory, plan)
_check_registration(journal, plan)
uncertain = False
response_id = None
try:
created = api.create_review(plan['target'], payload)
if type(created) is dict:
response_id = positive_id(created.get('id'))
else:
uncertain = True
except Exception:
# Never expose raw transport exception text (which may contain credentials).
uncertain = True
check_lease(lease, plan)
try:
role_identity(read_api(api, 'identity'), plan['reviewer'])
after = validate_snapshot(plan, read_api(api, 'snapshot', plan['target']))
matching = candidates(plan, after)
if len(matching) != 1:
raise UncertainPublication('create outcome not uniquely found; do not retry POST')
if response_id is not None and response_id != matching[0]['id']:
fail('create response and collection mismatch')
exact = read_api(api, 'read_review', plan['target'], matching[0]['id'])
result = verify_publication(plan, before, after, exact)
except ValidationError:
raise
except Exception:
raise UncertainPublication('authoritative readback unavailable; retained attempt; do not retry POST') from None
check_lease(lease, plan)
result.update(mode='reconciled' if uncertain else 'created', read_only=False, wrote=True)
journal.write('verified.json', result)
return result
class ApiAdapter:
"""REST bridge: request(path, payload=None) -> (JSON, headers, status).
The caller supplies one already-authenticated, no-redirect, TLS-verifying,
role-local transport. This class never loads/falls back to credentials.
"""
def __init__(self, request, *, origin, credential_source):
if not callable(request) or origin != ORIGIN + '/api/v1':
fail('invalid authenticated transport origin')
if credential_source not in ['$HERMES_HOME/.env:GITEA_TOKEN', 'role-scoped-external-secret:GITEA_TOKEN']:
fail('unverified role credential source')
self.request = request
self.origin = origin
self.credential_source = credential_source
def call(self, path, payload=None):
try:
data, headers, status = self.request(path, payload)
except Exception:
raise ValidationError('authenticated API request failed') from None
expected = [200, 201] if payload is not None else [200]
if type(status) is not int or status not in expected or not hasattr(headers, 'items'):
fail('authenticated API status/shape mismatch')
return data, {str(k).lower(): v for k, v in headers.items()}
def identity(self):
data, _ = self.call('/user')
if type(data) is not dict or data.get('login') != 'gitea-review-agent':
fail('unexpected authenticated role')
positive_id(data.get('id'))
return {'id': data['id'], 'login': data['login'], 'origin': self.origin,
'credential_source': self.credential_source}
def prefix(self, target):
for key in ['owner', 'repository']:
if type(target.get(key)) is not str or not re.fullmatch(r'[A-Za-z0-9_][A-Za-z0-9_.-]{0,254}', target[key]):
fail('invalid request target')
positive_id(target['issue']); positive_id(target['pull'])
return '/repos/' + target['owner'] + '/' + target['repository']
def collection(self, endpoint, key):
records = []
def fetch(page):
data, headers = self.call(endpoint + '?limit=50&page=' + str(page))
raw_total = headers.get('x-total-count')
if raw_total is not None and (type(raw_total) is not str or not re.fullmatch(r'\d+', raw_total)):
fail('invalid declared collection total')
total = int(raw_total) if raw_total is not None else None
if data is None and total == 0:
data = []
if type(data) is not list:
fail('invalid API collection')
link = headers.get('link')
if link is not None and type(link) is not str:
fail('invalid collection Link header')
more = True if link and re.search(r'rel=["\']?next', link) else None
if more is None and total is not None:
more = page * 50 < total
records.append({'page': page, 'count': len(data), 'declared_total': total, 'link': link})
envelope = {'items': data, 'has_more': more}
if total is not None:
envelope['total_count'] = total
return envelope
items = collect_pages(fetch, key, page_size=50, max_pages=1000)
check_pages(items, records)
return items, records
def snapshot(self, target):
identity = self.identity() # Always first authoritative read.
prefix = self.prefix(target)
repo, _ = self.call(prefix)
issue, _ = self.call(prefix + '/issues/' + str(target['issue']))
pr, _ = self.call(prefix + '/pulls/' + str(target['pull']))
spec_user, _ = self.call('/users/gitea-spec-agent')
if type(spec_user) is not dict or spec_user.get('login') != 'gitea-spec-agent':
fail('invalid spec identity')
out = {'identity': identity, 'spec_user': {'id': positive_id(spec_user.get('id')), 'login': spec_user['login']},
'repository': repo, 'issue': issue, 'pr': pr, 'pagination': {}, 'review_comments': {}}
pull = prefix + '/pulls/' + str(target['pull'])
endpoints = [('files', pull + '/files', 'filename'), ('commits', pull + '/commits', 'sha'),
('reviews', pull + '/reviews', 'id'),
('issue_comments', prefix + '/issues/' + str(target['issue']) + '/comments', 'id'),
('pr_comments', prefix + '/issues/' + str(target['pull']) + '/comments', 'id'),
('statuses', prefix + '/statuses/' + digest(target['head'], 40), 'id')]
for name, endpoint, key in endpoints:
out[name], out['pagination'][name] = self.collection(endpoint, key)
for review in out['reviews']:
rid = str(positive_id(review['id']))
out['review_comments'][rid], out['pagination']['review_comments_' + rid] = self.collection(pull + '/reviews/' + rid + '/comments', 'id')
return out
def create_review(self, target, payload):
if type(payload) is not dict or set(payload) != {'commit_id', 'body', 'event', 'comments'} or payload['comments'] != []:
fail('invalid create payload')
data, _ = self.call(self.prefix(target) + '/pulls/' + str(target['pull']) + '/reviews', payload)
return data
def read_review(self, target, review_id):
positive_id(review_id)
data, _ = self.call(self.prefix(target) + '/pulls/' + str(target['pull']) + '/reviews/' + str(review_id))
return data
def _validate_registered_source(plan, policy_path, deadline_monotonic):
import math, os, stat, subprocess, time
if type(deadline_monotonic) not in [int, float] or not math.isfinite(deadline_monotonic):
fail('invalid or expired Issue lease')
t = plan['target']
info = _support.validate_cache_paths(policy_path, t['owner'], t['repository'], t['issue'])
wt = Path(info['repository']) / 'worktrees' / f'issue-{t["issue"]}' / 'checkout'
opened = []
def close_opened():
while opened:
try: os.close(opened.pop())
except OSError: pass
def reject(message):
close_opened()
fail(message)
def open_dir(path):
parts = path.parts
fd = os.open(parts[0], os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC)
opened.append(fd)
try:
for part in parts[1:]:
nxt = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC,
dir_fd=fd)
opened.append(nxt); os.close(fd); opened.remove(fd); fd = nxt
return fd
except BaseException:
close_opened(); raise
try:
worktrees_fd = open_dir(wt.parent.parent)
issue_fd = os.open(wt.parent.name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC,
dir_fd=worktrees_fd)
opened.append(issue_fd)
checkout_fd = os.open(wt.name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC,
dir_fd=issue_fd)
opened.append(checkout_fd)
for directory_fd in [worktrees_fd, issue_fd, checkout_fd]:
st = os.fstat(directory_fd)
if (not stat.S_ISDIR(st.st_mode) or st.st_uid != os.getuid()
or st.st_mode & (stat.S_IWGRP | stat.S_IWOTH)):
reject('unsafe existing worktree path')
state_fd = os.open('state.json', os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC, dir_fd=issue_fd)
opened.append(state_fd)
git_pointer_fd = os.open('.git', os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC, dir_fd=checkout_fd)
opened.append(git_pointer_fd)
state_st = os.fstat(state_fd); git_st = os.fstat(git_pointer_fd)
if not stat.S_ISREG(state_st.st_mode) or state_st.st_uid != os.getuid() or stat.S_IMODE(state_st.st_mode) != 0o600:
reject('unsafe worktree state')
if not stat.S_ISREG(git_st.st_mode) or git_st.st_uid != os.getuid():
reject('invalid worktree Git pointer')
chunks = []
while chunk := os.read(state_fd, 65536):
chunks.append(chunk)
try:
state = loads_json(b''.join(chunks).decode('utf-8'))
except Exception:
close_opened()
raise
os.lseek(git_pointer_fd, 0, os.SEEK_SET)
pointer = os.read(git_pointer_fd, 4096).decode('utf-8').strip()
if not pointer.startswith('gitdir: '):
reject('invalid worktree Git pointer')
worktree_git_fd = open_dir(Path(pointer[8:]))
except OSError:
close_opened()
raise ValidationError('unsafe existing worktree path') from None
if (type(state) is not dict or state.get('schema') != 'gitea-agent.worktree/v1' or state.get('owner') != t['owner']
or state.get('repository') != t['repository'] or type(state.get('issue')) is not int
or state['issue'] != t['issue'] or type(state.get('created_by_role')) is not str
or state['created_by_role'] not in ('gitea-impl-agent', 'impl')
or state.get('mode') != 'detached'):
reject('worktree registration identity mismatch')
git_binary = '/usr/bin/git'; binary_stat = Path(git_binary).lstat()
if not stat.S_ISREG(binary_stat.st_mode) or binary_stat.st_uid != 0 or binary_stat.st_mode & 0o022:
reject('untrusted system Git binary')
env = {'PATH': '/usr/bin:/bin', 'GIT_CONFIG_GLOBAL': '/dev/null', 'GIT_CONFIG_NOSYSTEM': '1', 'GIT_OPTIONAL_LOCKS': '0'}
git_dir_fd = open_dir(Path(info['repository_git']))
def git(*args):
try:
process = subprocess.run([git_binary, '-c', 'core.fsmonitor=false', '-c', 'core.hooksPath=/dev/null',
'-c', 'safe.directory=/proc/self/fd/' + str(checkout_fd),
'--git-dir=/proc/self/fd/' + str(worktree_git_fd),
'--work-tree=/proc/self/fd/' + str(checkout_fd), *args], capture_output=True, text=True, env=env, timeout=15,
pass_fds=(checkout_fd, worktree_git_fd, git_dir_fd))
except Exception:
close_opened()
raise ValidationError('read-only Git check unavailable') from None
if process.returncode or len(process.stdout) > 1000000:
reject('read-only Git check failed')
return process.stdout
if git('rev-parse', 'HEAD').strip() != t['head']:
reject('worktree HEAD mismatch')
if git('status', '--porcelain=v1', '--untracked-files=all').strip():
reject('existing worktree is not clean')
if git('rev-parse', '--path-format=absolute', '--git-common-dir').strip() != info['repository_git']:
reject('worktree common Git directory mismatch')
records = git('--git-dir=/proc/self/fd/' + str(git_dir_fd), 'worktree', 'list', '--porcelain', '-z').split('\0\0')
matches = [r.split('\0') for r in records if r.startswith('worktree ' + str(wt) + '\0')]
if len(matches) != 1 or 'detached' not in matches[0] or not any(x == 'locked' or x.startswith('locked ') for x in matches[0]):
reject('worktree detached/locked registration mismatch')
close_opened()
if time.monotonic() >= deadline_monotonic:
fail('Issue lease expired during source checks')
class HeldIssueLease:
"""Validate a caller-owned Issue lock FD; never close/release the caller's FD."""
def __init__(self, issue_fd, policy_path, deadline_monotonic):
self.issue_fd = issue_fd; self.policy_path = policy_path; self.deadline_monotonic = deadline_monotonic
def check(self, plan):
import math, time
validate_plan(plan)
if (type(self.issue_fd) is not int or type(self.deadline_monotonic) not in [int, float]
or not math.isfinite(self.deadline_monotonic) or time.monotonic() >= self.deadline_monotonic):
fail('invalid or expired Issue lease')
t = plan['target']; info = _support.validate_cache_paths(self.policy_path, t['owner'], t['repository'], t['issue'])
lock = Path(info['locks']) / f'issue-{t["issue"]}.lock'
try:
if not _locks.validate_held_flock(self.issue_fd, lock):
fail('Issue lock is not already held by this process')
except (OSError, _locks.LockValidationError):
raise ValidationError('Issue lock unavailable') from None
_validate_registered_source(plan, self.policy_path, self.deadline_monotonic)
class BrokerIssueLease:
"""Validate an authenticated shared managed-lock broker Issue lease."""
def __init__(self, handle_path, policy_path, deadline_monotonic):
self.handle_path = handle_path; self.policy_path = policy_path; self.deadline_monotonic = deadline_monotonic
def check(self, plan):
import math, time
validate_plan(plan)
if (type(self.deadline_monotonic) not in [int, float] or not math.isfinite(self.deadline_monotonic)
or time.monotonic() >= self.deadline_monotonic):
fail('invalid or expired Issue lease')
t = plan['target']
try:
handle = _broker.read_handle_metadata(self.handle_path)
status = _broker.request_handle(self.handle_path, 'status')
except Exception:
raise ValidationError('broker Issue lease unavailable') from None
if (type(status) is not dict or status.get('status') not in ('held', 'repository-held')
or status.get('owner') != t['owner'] or status.get('repository') != t['repository']
or status.get('issue') != t['issue'] or status.get('role') != 'gitea-review-agent'
or type(status.get('pid')) is not int or status.get('pid') != handle.get('pid')
or type(handle.get('pid')) is not int or status.get('deadline') != handle.get('deadline')
or type(status.get('deadline')) not in [int, float]
or status.get('deadline') > self.deadline_monotonic or time.monotonic() >= status.get('deadline')):
fail('broker Issue lease identity mismatch')
_validate_registered_source(plan, self.policy_path, self.deadline_monotonic)
def main(argv=None):
import argparse, sys
parser = argparse.ArgumentParser(description='Read-only native review artifact preflight/replay. Live publication is the Python publish API.')
commands = parser.add_subparsers(dest='command', required=True)
pre = commands.add_parser('preflight')
pre.add_argument('--plan', required=True); pre.add_argument('--baseline', required=True)
verify = commands.add_parser('verify')
for name in ['plan', 'before', 'after', 'exact']:
verify.add_argument('--' + name, required=True)
args = parser.parse_args(argv)
def read(path):
return loads_json(Path(path).read_text(encoding='utf-8'))
try:
plan = read(args.plan)
if args.command == 'preflight':
snapshot = validate_snapshot(plan, read(args.baseline))
out = {'ready': True, 'read_only': True, 'artifact_validation_only': True,
'head': plan['target']['head'], 'event': build_payload(plan)['event'],
'reviewer_id': plan['reviewer']['id'], 'spec_review_id': plan['spec_review_id'],
'snapshot_sha256': hashlib.sha256(canonical(snapshot)).hexdigest()}
else:
out = verify_publication(plan, read(args.before), read(args.after), read(args.exact))
print(json.dumps(out, ensure_ascii=False))
return 0
except Exception:
# No arbitrary artifact or transport exception is echoed to the terminal.
print(json.dumps({'error': 'invalid or unavailable review artifacts', 'read_only': True}), file=sys.stderr)
return 1
if __name__ == '__main__':
raise SystemExit(main())