[verified] Document bounded Discovery checker preparation

This commit is contained in:
2026-10-12 16:11:48 +00:00
parent a4ead04664
commit b687af03ec
2 changed files with 80 additions and 1 deletions
@@ -66,6 +66,15 @@ Bootstrap 이후 Discovery review는 canonical [Repository cache 운영 지침](
일부 prototype 실패만으로 사용자 요구·URL·dependency 정책 변경을 필수 선택으로 제안하지 않는다. 실험 기록과 기술 delegate 결과에는 대상 요구사항, 시험한 후보·버전·환경, 성공·실패 관찰, 시험하지 않은 대안·확인 한계와 결론 적용 범위를 포함한다. Main은 delegate의 불가능 결론을 원본 실험·공식 소스에 직접 대조한 뒤 채택하며, delegate summary만으로 결정하지 않는다.
### 임시·변경된 Discovery checker 준비
검증용 임시·변경된 checker를 준비하는 경우에만 다음 대조를 외부 write 없이 수행한다. 이미 검증된 정본 checker에 중복 검사나 새 runtime gate를 추가하는 요구가 아니다. 기존 사용자 선택·OQ cardinality·승인된 결정·입력 gate와 exact readback 계약은 그대로 유지한다.
- Heading·section 검사는 LF/CRLF의 논리적 행을 구분 없이 대조한다. `splitlines()` 등으로 비교용 view를 만들되 원본 bytes, revised payload와 hash 입력은 따로 보존한다. Text reader의 자동 newline 변환이나 비교용 view의 재직렬화로 payload를 덮어쓰지 않는다. 빈 행·heading 문자열·순서·중복·누락을 임의로 보정하지 않는다. Regex가 필요하면 `flags=`를 명시하며 `re.split`의 positional maxsplit과 flags를 혼동하지 않는다. LF/CRLF 동등성은 줄끝 표현에 한정하고 본문 의미·marker·공백을 일괄 정규화하지 않는다.
- Stale prose 검사는 승인된 변경의 exact section·paragraph, OQ ID와 정확 문구 및 기대 replacement를 함께 대조한다. 새 replacement가 기존 도입 prefix를 의도적으로 포함하거나 승인된 재선택 문구를 남겼다는 이유만으로 substring 존재를 stale로 단정하지 않는다. 해당 위치의 exact replacement와 승인 근거를 확인한 경우에만 그 의도적 보존을 인정하고 다른 위치의 실제 잔여 문구·다른 OQ를 일괄 면제하지 않는다. Global substring 제거·전역 prefix 무시로 검사를 약화하지 않는다. 근거·위치·선택 의미가 모호하면 정상이나 stale로 추정하지 않고 해당 검증을 미확인/partial로 남기며 게시하지 않는다.
- 임시 checker의 LF/CRLF 정상 대조와 malformed 대조, 의도적 prefix 보존과 다른 위치의 실제 잔여 문구, self-containing replacement의 로컬 replay를 준비한다. 승인된 동일 입력·pinned source에서 재적용 결과가 exact payload와 같은지 확인하고 반복 전파·중복 replacement이면 준비 실패로 차단한다. 이 로컬 대조는 기존 authoritative readback 후 replay를 대체하지 않는다. User 결정 전파의 의미 적합성은 main이 별도로 판단하며 checker가 OQ를 자동 선택·삭제하거나 새로운 결정 정책을 만들지 않는다.
- 문서 계약과 작은 synthetic primitive 대조는 checker 작성 시의 실패 조건을 설명하는 근거일 뿐 실제 checker 전체·agent 실행·live Issue 수정의 검증이 아니다. 공통 semantic validator·정규화 엔진·publisher·schema를 추가하지 않는다. 원래 공유 deterministic validator 제안 전체는 이 보완만으로 종료하지 않고 부분 완료로 유지한다.
## 절차
1. 요청에서 exact `owner`, `repository`, Issue 번호를 추출하고 단일 안전 경로 요소인지 검증한다. `operations/repository-cache.md`를 새로 읽고 canonical cache 경로를 계산·검증한다.
@@ -78,7 +87,7 @@ Bootstrap 이후 Discovery review는 canonical [Repository cache 운영 지침](
8. 범위 분류·blocking 판단과 기술적 결론의 경계를 먼저 적용하고 각 OQ의 선택 수를 판정한다. 선택된 결정이 특정 API·dependency·runtime·배포 기능에 의존하면 pinned 저장소와 적용 버전의 공식 문서·소스를 조사해 지원 여부와 구현 경로를 확인한다. 조사만으로 판단할 수 없고 실행 실험이 필요하면 기술적 전제 검증을 미완료로 남긴다. 선택된 결정을 관련 모든 섹션에 전파하고, 새 모순·누락으로 새 사용자 결정이 필요할 때만 OQ 검토 규칙에 따라 새 OQ를 추가한다.
9. `문서 영향 및 산출물`을 README 정책에 따라 전부 다시 판정한다. 요구사항명세서의 exact ID·경로·제목·포함할 내용·제외할 내용과 다른 문서의 구체적 변경 계약을 현재 결정과 pinned SHA에 맞춘다.
10. 조사 종료 시 Gitea default-branch SHA와 local ref가 pinned SHA와 여전히 일치하는지 확인하고 `release_repository` request를 보낸다. Issue lease는 유지한다.
11. trusted workflow directory에 revised body artifact를 만들고 marker, H2 순서, placeholder 부재, OQ ID·checkbox, 결정 전파, 문서 영향, repository evidence와 credential 부재를 검증한다.
11. trusted workflow directory에 revised body artifact를 만들고 임시·변경된 checker를 준비하면 위 임시·변경된 Discovery checker 준비 절차를 적용한 뒤 marker, H2 순서, placeholder 부재, OQ ID·checkbox, 결정 전파, 문서 영향, repository evidence와 credential 부재를 검증한다.
12. revised body가 current body와 exact-match하면 comment와 body write 없이 no-op으로 종료한다.
13. write 직전에 Issue와 comments를 다시 읽는다. snapshot의 body·`updated_at` 또는 comment baseline이 drift했으면 외부 write 없이 중단한다.
14. snapshot의 body·`updated_at`·comment baseline과 비본문 필드가 불변일 때 revised body artifact의 exact bytes로 Issue body 하나만 교체한다. title, state, labels, assignees, milestone은 write payload에 포함하지 않는다.
@@ -110,6 +110,21 @@ class PriorityReviewContracts(unittest.TestCase):
self.assertLess(step.index('비정본·변경된 readback helper 호출 계약'),
step.index('Native review create를'))
def test_discovery_checker_documentation_contract_preserves_bytes_and_bounded_transformations(self):
text = DISCOVERY.read_text()
heading = '### 임시·변경된 Discovery checker 준비'
self.assertIn(heading, text, 'bounded Discovery checker preparation guidance is missing')
section = text.split(heading, 1)[1].split('\n## ', 1)[0]
for rule in ('임시·변경된', 'LF/CRLF', 'splitlines', '원본 bytes',
'payload', 'hash', 'flags=', 'section·paragraph',
'OQ ID', '정확 문구', 'prefix', '일괄 면제하지',
'replay', '외부 write 없이', '사용자 선택',
'공통 semantic validator', '부분 완료', 'synthetic'):
with self.subTest(rule=rule):
self.assertIn(rule, section)
step = text.split('11. trusted workflow directory', 1)[1].split('\n12.', 1)[0]
self.assertIn('임시·변경된 Discovery checker 준비', step)
def test_local_links_resolve_and_procedures_reference_new_rules(self):
for path in (DISCOVERY, IMPLEMENTATION, README):
text = path.read_text()
@@ -120,6 +135,61 @@ class PriorityReviewContracts(unittest.TestCase):
self.assertIn('11. 문서 검토 입력과 구조·의미 분리 절차', IMPLEMENTATION.read_text())
class SyntheticDiscoveryCheckerControls(unittest.TestCase):
"""Small local primitive examples, not a shared Discovery validator."""
def test_synthetic_logical_headings_accept_crlf_without_rewriting_bytes(self):
import hashlib
lf = b'## Scope\nbody\n## Questions\nnone\n'
crlf = lf.replace(b'\n', b'\r\n')
original_digest = hashlib.sha256(crlf).digest()
self.assertIsNone(re.search(r'^## Scope$', crlf.decode(), flags=re.MULTILINE))
expected = ['## Scope', '## Questions']
for raw in (lf, crlf):
headings = [line for line in raw.decode().splitlines() if line.startswith('## ')]
self.assertEqual(headings, expected)
for malformed in (lf.replace(b'## Scope', b'## Scopes'),
lf + b'## Scope\n', lf.replace(b'## Questions\n', b'')):
headings = [line for line in malformed.decode().splitlines() if line.startswith('## ')]
self.assertNotEqual(headings, expected)
self.assertEqual(hashlib.sha256(crlf).digest(), original_digest)
self.assertEqual(crlf, b'## Scope\r\nbody\r\n## Questions\r\nnone\r\n')
def test_synthetic_exact_paragraph_exception_does_not_hide_other_oq_residue(self):
old = 'OQ-1: legacy introduction'
replacement = old + '; explicit approved reselection'
paragraphs = {'decision': replacement, 'question': 'OQ-2: unrelated question'}
self.assertIn(old, paragraphs['decision']) # Naive global substring false positive.
self.assertEqual(paragraphs['decision'], replacement)
residue = {key: value for key, value in paragraphs.items()
if old in value and not (key == 'decision' and value == replacement)}
self.assertEqual(residue, {})
paragraphs['question'] = old + '; unresolved reference in OQ-2'
residue = {key: value for key, value in paragraphs.items()
if old in value and not (key == 'decision' and value == replacement)}
self.assertEqual(set(residue), {'question'})
self.assertEqual(paragraphs['decision'], replacement)
def test_synthetic_self_containing_exact_replacement_replays_without_duplication(self):
old = b'Legacy introduction'
replacement = old + b'; approved detail'
naive_replay = replacement.replace(old, replacement)
self.assertNotEqual(naive_replay, replacement)
# Approved exact paragraph fixture only; no general normalization policy.
def fixture_transform(paragraph):
if paragraph == replacement:
return paragraph
if paragraph == old:
return replacement
raise ValueError('ambiguous paragraph fixture')
first = fixture_transform(old)
self.assertEqual(first, replacement)
self.assertEqual(fixture_transform(first), first)
with self.assertRaisesRegex(ValueError, 'ambiguous paragraph fixture'):
fixture_transform(old + b'; unrelated edit')
class SyntheticReadbackInvocationTests(unittest.TestCase):
"""Local Python binding/collector controls, not a production pre-create gate."""