[verified] Clarify changed readback helper invocation preflight

This commit is contained in:
2026-10-12 15:35:46 +00:00
parent e5339694ab
commit 20cc7858f6
2 changed files with 68 additions and 2 deletions
@@ -259,7 +259,7 @@ terminal(command="python3 harness/agents/spec/skills/gitea-implementation-spec-r
15. Write 직전에 Issue 전체 필드·comments, PR details·base·head·changed files와 전체 review baseline을 다시 읽는다. Contract snapshot, base target, head SHA, changed-file set 또는 baseline이 drift하면 native review를 쓰지 않는다.
`commit.files`처럼 순서에 의미가 없는 collection은 filename 중복을 먼저 거부하고 filename·status 기준으로 정규화해 비교한다. 순서 차이만 제외하고 모든 항목·필드 및 exact blob/hunk는 보존·대조하며, 모든 배열을 일괄 set 처리하지 않는다. Criteria 순서와 의미 있는 event 순서는 유지한다.
16. Stable reviewer account ID, head, contract digest와 result digest가 모두 같은 canonical review가 정확히 하나 이미 있으면 exact review와 review comments를 authoritative하게 readback한다. Verdict-compatible 최종 state와 stale/dismissed 상태까지 검증된 경우에만 write 없는 no-op으로 종료한다. `PENDING`이면 완료나 새 create로 진행하지 않고 절차 19의 복구 조건을 적용한다. 같은 binding의 다른 결과 또는 중복 review가 있으면 fail-closed한다.
17. 호출 전에 선택한 adapter의 실제 요청 schema와 endpoint별 성공 status를 검증하며 확인할 수 없으면 게시하지 않는다. Native review create를 `body=<canonical body>`, `comments=[]`, `commit_id=<exact head SHA>`와 명시적 verdict-compatible 요청 필드로 정확히 한 번 호출한다. MCP는 tool schema의 `state` 등 실제 인자를 사용하고, 직접 REST는 서버 schema의 `event=<APPROVED|REQUEST_CHANGES|COMMENT>`를 사용한다. MCP 인자를 REST payload에 복사하거나 모든 POST의 성공 status를 `201`로 가정하지 않는다. REST review endpoint의 `200` 등 확인된 성공 status만 인정하고 authoritative 응답의 `state`는 기존 verdict mapping과 exact-match해야 한다.
17. 호출 전에 선택한 adapter의 실제 요청 schema와 endpoint별 성공 status를 검증하며 확인할 수 없으면 게시하지 않는다. 비정본·변경된 readback helper를 사용하면 아래 비정본·변경된 readback helper 호출 계약을 먼저 적용한다. Native review create를 `body=<canonical body>`, `comments=[]`, `commit_id=<exact head SHA>`와 명시적 verdict-compatible 요청 필드로 정확히 한 번 호출한다. MCP는 tool schema의 `state` 등 실제 인자를 사용하고, 직접 REST는 서버 schema의 `event=<APPROVED|REQUEST_CHANGES|COMMENT>`를 사용한다. MCP 인자를 REST payload에 복사하거나 모든 POST의 성공 status를 `201`로 가정하지 않는다. REST review endpoint의 `200` 등 확인된 성공 status만 인정하고 authoritative 응답의 `state`는 기존 verdict mapping과 exact-match해야 한다.
18. Create 응답 성공만 신뢰하지 않는다. `get_review`, complete `get_reviews` pagination과 exact review의 `get_review_comments`를 다시 읽어 baseline 보존과 exact 대상 review record 하나(새 create면 신규 ID 하나, 기존 `PENDING` 복구면 같은 ID 유지), stable author account ID·body·state·commit·timestamp·stale/dismissed 상태 및 inline comment가 0개임을 검증한다. `PENDING`은 제출 완료가 아니며 절차 19의 복구 조건을 적용한다.
새 최종 review는 `stale=false`, `dismissed=false`여야 한다. `official`은 고정된 `false`를 요구하지 않고 authoritative provider 계약·조회된 권한·review 정책과 대조해 판정하며, 그 값만으로 작성자·본문·head·최종 state 검증을 대체하지 않는다. Baseline 변경은 절차 20의 검증된 파생 projection만 제외하고 보존한다.
19. Review create가 timeout·오류 또는 malformed response이면 blind retry하지 않는다. Complete review collection과 candidate review comments를 한 번 authoritative reconciliation해 baseline 대비 exact result가 하나이고 verdict-compatible 최종 state와 inline comment 부재가 확인될 때만 제출 완료로 판정한다. Exact owned `PENDING` review 하나가 확인되면 그 ID를 보존하고 partial publication으로 기록한다. 같은 review 제출이 승인된 경우에만, submit schema·성공 status 및 identity·Issue contract·PR base/head·changed files·review baseline·canonical body를 새로 검증하고(이미 인과가 증명된 해당 review의 파생 projection만 제외), 동일 ID를 명시적 verdict로 한 번 submit한 뒤 절차 18과 20의 readback을 적용한다. 새 create는 하지 않으며 submit 응답이 불확실하면 동일 ID를 authoritative readback해 최종 제출을 확인하거나 uncertain/partial로 중단한다. Candidate가 없거나 여러 개·경쟁 review·drift가 있으면 복구 write 없이 중단한다.
@@ -272,6 +272,15 @@ terminal(command="python3 harness/agents/spec/skills/gitea-implementation-spec-r
모든 종료 경로(정상·no-op·검증 실패·API 오류·예외·cancel)는 canonical broker cleanup을 `finally`에서 수행한다: 보유한 `release_repository`, `release`, tracked broker wait/reap, `probe`의 `free=true` readback 순서를 확인한다. Write 후 응답과 authoritative readback snapshot은 판정 assertion 전에 credential 없이 먼저 보존하고, 검증 실패 사유는 별도로 기록한다. 불확실한 POST는 절차 19만 적용하고 후속 write를 중단한다. 종료 시 final report를 authoritative 결과로 확정하며 native review 제출 상태와 절차 전체 상태를 분리한다. Progress artifact를 사용했다면 최종 상태·review ID·잔여 오류를 갱신하거나 stage·timestamp·final report 참조를 갖춘 중간 snapshot임을 명시한다. Cleanup 실패도 final report에 남기고 완료로 보고하지 않는다.
### 비정본·변경된 readback helper 호출 계약
비정본 helper, 교체·변경된 adapter 또는 workflow-local pagination callback을 사용할 때만 native create 이전에 게시 후 사용할 readback 호출 형태를 확인한다. 정본 adapter를 변경 없이 사용하는 경로에는 중복 signature 검사를 강제하지 않는다. 기존 요청 schema·identity·source·lease 검증과 complete readback은 그대로 유지한다.
- Main은 실제 사용할 helper의 source revision 또는 digest, 문서화된 interface와 caller의 positional·keyword 인수를 대조한다. Python callable의 확인된 signature는 `inspect.signature(readback_callable).bind(*planned_args, **planned_kwargs)`로 점검할 수 있다. 이 점검에서 callback 본문이나 native create를 실행하지 않는다. `collect_pages`의 callback은 `fetch_page(page)`이며 page size는 collector의 별도 인수다. 다른 helper에도 이 형태를 임의로 복사하지 않는다.
- Signature 조회 불가, wrapper의 variadic 인수 또는 `__signature__` 등으로 실제 interface를 확인할 수 없는 경우 binding 통과만 신뢰하지 않는다. 승인된 interface·변경 source와 bounded synthetic fixture의 호출 근거를 대조한다. 불일치·근거 부족이면 native create하지 않고 blocked/partial과 최소 보완 이유를 기록한다. 실제 네트워크를 호출하거나 trial create로 signature를 시험하지 않는다.
- 이는 호출 형태의 확인일 뿐 인증·provider wire schema·pagination completeness·callback 내부 동작이나 향후 네트워크 성공의 증명이 아니다. Write 뒤 readback이 실패하면 응답과 정확한 대상 근거를 보존하고 기존 read-only reconciliation으로 확인하거나 uncertain/partial로 중단한다. Signature 문제를 이유로 native create를 반복하지 않는다.
- 새 runtime gate·범용 interface 검사기·adapter·schema·journal을 추가하지 않는다. 문서 계약 회귀는 지침의 존재만 확인하고, synthetic binding/collector 회귀는 로컬 호출 동작만 확인한다. 이를 실제 agent 실행이나 live publication 검증으로 보고하지 않는다.
## 문서 검토 입력과 구조·의미 분리
문서 검토를 main이 수행하거나 delegate에 위임하기 전에 다음 입력을 trusted workflow에 준비한다. 이 준비는 제품 저장소의 새 산출물이나 result schema 변경을 요구하지 않는다.
@@ -1,4 +1,4 @@
"""Static contract regressions; these do not prove LLM execution behavior."""
"""Documentation contracts and local synthetic controls; not agent/live execution."""
from pathlib import Path
import re
import unittest
@@ -93,6 +93,23 @@ class PriorityReviewContracts(unittest.TestCase):
with self.subTest(rule=rule):
self.assertIn(rule, section)
def test_changed_readback_helper_documentation_contract_is_conditional_and_precreate(self):
text = IMPLEMENTATION.read_text()
heading = '### 비정본·변경된 readback helper 호출 계약'
self.assertIn(heading, text, 'conditional readback invocation guidance is missing')
section = text.split(heading, 1)[1].split('\n## ', 1)[0]
for rule in ('비정본', '변경된', 'native create 이전',
'정본 adapter를 변경 없이', '중복 signature 검사를 강제하지',
'inspect.signature', '.bind(', '실행하지', 'partial',
'인증', 'pagination completeness', 'read-only',
'새 runtime gate', 'source revision'):
with self.subTest(rule=rule):
self.assertIn(rule, section)
step = text.split('17. 호출 전에', 1)[1].split('\n18.', 1)[0]
self.assertIn('비정본·변경된 readback helper 호출 계약', step)
self.assertLess(step.index('비정본·변경된 readback helper 호출 계약'),
step.index('Native review create를'))
def test_local_links_resolve_and_procedures_reference_new_rules(self):
for path in (DISCOVERY, IMPLEMENTATION, README):
text = path.read_text()
@@ -103,5 +120,45 @@ class PriorityReviewContracts(unittest.TestCase):
self.assertIn('11. 문서 검토 입력과 구조·의미 분리 절차', IMPLEMENTATION.read_text())
class SyntheticReadbackInvocationTests(unittest.TestCase):
"""Local Python binding/collector controls, not a production pre-create gate."""
def test_synthetic_signature_binding_rejects_extra_argument_without_callback_execution(self):
import inspect
calls = []
def pages(page, *, endpoint='reviews'):
calls.append((page, endpoint))
signature = inspect.signature(pages)
bound = signature.bind(1, endpoint='reviews')
self.assertEqual(bound.arguments, {'page': 1, 'endpoint': 'reviews'})
self.assertEqual(calls, [])
with self.assertRaises(TypeError):
signature.bind(1, 50)
with self.assertRaises(TypeError):
signature.bind(1, page_size=50)
self.assertEqual(calls, [])
def test_actual_collector_calls_synthetic_callback_with_one_page_argument(self):
import importlib.util
import inspect
path = HARNESS / 'agents/spec/skills/gitea-implementation-spec-review/scripts/review_support.py'
spec = importlib.util.spec_from_file_location('readback_invocation_support', path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
calls = []
def pages(page):
calls.append(page)
return {'items': [{'id': page}], 'total_count': 2, 'has_more': page < 2}
inspect.signature(pages).bind(1)
self.assertEqual(calls, [])
self.assertEqual(module.collect_pages(pages, 'id', page_size=1), [{'id': 1}, {'id': 2}])
self.assertEqual(calls, [1, 2])
if __name__ == '__main__':
unittest.main()