From 9cda9733c6e93fb2223d45e6c141e046deba4549 Mon Sep 17 00:00:00 2001 From: gitea-spec-agent Date: Tue, 6 Oct 2026 02:30:14 +0000 Subject: [PATCH] fix(spec): enforce scope and evidence boundaries in reviews --- .../skills/gitea-discovery-review/SKILL.md | 19 +++++- .../gitea-implementation-spec-review/SKILL.md | 18 +++++- harness/docs/discovery/README.md | 14 +++++ .../spec/test_priority_review_contracts.py | 61 +++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 harness/tests/spec/test_priority_review_contracts.py diff --git a/harness/agents/spec/skills/gitea-discovery-review/SKILL.md b/harness/agents/spec/skills/gitea-discovery-review/SKILL.md index 8d4ea09..f7af5e1 100644 --- a/harness/agents/spec/skills/gitea-discovery-review/SKILL.md +++ b/harness/agents/spec/skills/gitea-discovery-review/SKILL.md @@ -1,7 +1,7 @@ --- name: gitea-discovery-review description: 수정된 Gitea Discovery를 검토하고 정규화한다. -version: 0.3.0 +version: 0.3.1 author: genichin, Hermes Agent license: MIT platforms: [linux] @@ -47,6 +47,20 @@ Gitea 인증과 readback은 [Gitea authenticated access 운영 지침](../../../ - 미해결 OQ가 없으면 `## 미해결 질문`의 본문을 exact `없음`으로 쓴다. - 추천안을 사용자가 선택한 결정처럼 기록하거나 사용자 대신 checkbox를 선택하지 않는다. +## 범위와 기술적 결론의 경계 + +- 새 OQ·인수 조건은 [Discovery 작성 방법](../../../../docs/discovery/README.md)의 핵심 요청 / 기존 호환성 / 선택적 확장 분류와 blocking 규칙을 먼저 적용한다. 각 항목의 분류·원본 근거·사용자 채택 여부를 trusted workflow 검토 기록에 남긴다. +- 미채택 선택적 확장을 필수 범위나 blocking OQ로 승격하지 않는다. 기존 보안·호환성 제약은 선택적으로 낮추지 않으며 사용자가 채택한 요구나 미선택 OQ를 임의 삭제·해결하지 않는다. 기존 결정과 충돌하면 선택을 보존하고 사용자 판단을 받는다. +- 구현 후 acceptance 검증은 사용자 선택이 필요한 결정 공백과 구분한다. 단지 최종 구현 검증이 아직 없다는 이유로 새 OQ를 만들지 않는다. + +기술적 전제 결론은 다음 세 수준을 구분한다. + +1. 시험한 방식의 실패: 특정 후보·버전·환경에서 관찰한 실패만 보고하며 시험하지 않은 대안까지 부정하지 않는다. +2. 소스로 확인된 제약: 적용 버전의 공식 문서·소스로 제한되는 API·조건과 근거 위치를 특정한다. +3. 요구 충족 불가능: 승인된 제약 안의 가능한 대안까지 배제할 근거와 검토 범위를 제시할 수 있을 때만 사용한다. 근거가 부족하면 미검증 또는 판단 유보로 보고하고 전체 불가능으로 단정하지 않는다. + +일부 prototype 실패만으로 사용자 요구·URL·dependency 정책 변경을 필수 선택으로 제안하지 않는다. 실험 기록과 기술 delegate 결과에는 대상 요구사항, 시험한 후보·버전·환경, 성공·실패 관찰, 시험하지 않은 대안·확인 한계와 결론 적용 범위를 포함한다. Main은 delegate의 불가능 결론을 원본 실험·공식 소스에 직접 대조한 뒤 채택하며, delegate summary만으로 결정하지 않는다. + ## 절차 1. 요청에서 exact `owner`, `repository`, Issue 번호를 추출하고 단일 안전 경로 요소인지 검증한다. `operations/repository-cache.md`를 새로 읽고 canonical cache 경로를 계산·검증한다. @@ -56,7 +70,7 @@ Gitea 인증과 readback은 [Gitea authenticated access 운영 지침](../../../ 5. Issue lock을 유지한 채 canonical 지침에 따라 기존 cache를 검증한다. 검증에 실패하면 자동 초기화하거나 복구하지 않고 중단한다. 6. repository lock을 canonical 순서로 획득해 origin을 fetch하고 Gitea default-branch SHA와 local ref가 정확히 일치하는 revision을 pin한다. 7. pinned SHA에서 current Discovery의 모든 저장소 근거, 다섯 후속 문서 경로, 요구사항 ID·파일명과 사용자 prose 수정의 사실성을 조사한다. 움직이는 branch나 다른 workspace를 근거로 사용하지 않는다. -8. 각 OQ의 선택 수를 판정한다. 선택된 결정이 특정 API·dependency·runtime·배포 기능에 의존하면 pinned 저장소와 적용 버전의 공식 문서·소스를 조사해 지원 여부와 구현 경로를 확인한다. 조사만으로 판단할 수 없고 실행 실험이 필요하면 기술적 전제 검증을 미완료로 남긴다. 선택된 결정을 관련 모든 섹션에 전파하고, 새 모순·누락으로 새 사용자 결정이 필요할 때만 OQ 검토 규칙에 따라 새 OQ를 추가한다. +8. 범위 분류·blocking 판단과 기술적 결론의 경계를 먼저 적용하고 각 OQ의 선택 수를 판정한다. 선택된 결정이 특정 API·dependency·runtime·배포 기능에 의존하면 pinned 저장소와 적용 버전의 공식 문서·소스를 조사해 지원 여부와 구현 경로를 확인한다. 조사만으로 판단할 수 없고 실행 실험이 필요하면 기술적 전제 검증을 미완료로 남긴다. 선택된 결정을 관련 모든 섹션에 전파하고, 새 모순·누락으로 새 사용자 결정이 필요할 때만 OQ 검토 규칙에 따라 새 OQ를 추가한다. 9. `문서 영향 및 산출물`을 README 정책에 따라 전부 다시 판정한다. 요구사항명세서의 exact ID·경로·제목·포함할 내용·제외할 내용과 다른 문서의 구체적 변경 계약을 현재 결정과 pinned SHA에 맞춘다. 10. 조사 종료 시 Gitea default-branch SHA와 local ref가 pinned SHA와 여전히 일치하는지 확인하고 repository lock을 해제한다. Issue lock은 유지한다. 11. trusted workflow directory에 revised body artifact를 만들고 marker, H2 순서, placeholder 부재, OQ ID·checkbox, 결정 전파, 문서 영향, repository evidence와 credential 부재를 검증한다. @@ -85,6 +99,7 @@ Gitea 인증과 readback은 [Gitea authenticated access 운영 지침](../../../ - Issue/comment pre-write snapshot과 입력 승인 gate 결과 - cache 경로, bare·remote 검증, fetch와 pinned Gitea/local SHA - 사용자 선택 OQ, 유지한 OQ, 제거한 OQ와 새로 연 OQ 및 기술적 전제 검증 결과·근거·미검증 항목 +- 새 OQ·인수 조건의 범위 분류·승인 근거·blocking 사유, 기술 결론 수준·시험 범위·미검증 대안과 main의 delegate 근거 대조 결과 - 사용자 prose 수정의 수용·조정·거부 결과와 근거 - 확정된 결정의 섹션별 전파 결과 - 다섯 후속 문서의 조치·exact 경로·변경 내용 검증 결과 diff --git a/harness/agents/spec/skills/gitea-implementation-spec-review/SKILL.md b/harness/agents/spec/skills/gitea-implementation-spec-review/SKILL.md index b73e56b..57d7228 100644 --- a/harness/agents/spec/skills/gitea-implementation-spec-review/SKILL.md +++ b/harness/agents/spec/skills/gitea-implementation-spec-review/SKILL.md @@ -1,7 +1,7 @@ --- name: gitea-implementation-spec-review description: 승인된 계약에 대해 Gitea 구현 PR 적합성을 검토한다. -version: 0.2.1 +version: 0.2.2 author: genichin, Hermes Agent license: MIT platforms: [linux] @@ -221,7 +221,7 @@ terminal(command="python3 harness/agents/spec/skills/gitea-implementation-spec-r 8. Same-repository PR이면 head object도 cache에서 검증하고 exact base-to-head tree를 Gitea changed-file set·diff와 대조한다. Fork PR이면 target cache에 head object가 있다고 가정하지 않고 authoritative head repository·branch SHA와 Gitea changed-file set·diff를 대조한다. Diff 직렬화의 byte 차이만으로 실패시키지 않고 변경 경로와 exact base/head blob 및 hunk가 동일 변경을 나타내는지 검증한다. 임의 ref, checkout, clone 또는 worktree를 만들지 않는다. 9. Repository lock을 해제하되 Issue lock은 유지한다. 10. Acceptance matrix 작성 전에 구현자 실행 보고와 상세 실행 기록의 출처 및 확인 범위를 구분한다. Revision이 식별된 기존 evidence는 source revision과 reviewed head 사이 전체 diff·실행 입력 변경을 확인하고, 필수 검증별 재사용 가능 범위와 현재 head에 새로 결합해야 할 근거를 먼저 요약한다. 상세 기록이나 revision 정보의 부재만으로 새 검증 gate를 만들지 않는다. 재사용 여부는 위 Acceptance matrix의 기존 evidence binding 기준으로 판단하며 새 gate나 일괄 재실행 요구를 추가하지 않는다. 재검토에서는 이전 finding별 승인 계약 근거와 최소 보완 요청을 새 구현·evidence의 확인 위치에 대응하고, 해소 또는 잔여 사유를 기존 criterion의 `evidence`와 `reason`에 기록한다. 이미 충족된 검증을 source revision 차이만으로 다시 요구하지 않되, 변경 영향으로 새로 판정할 수 없어진 필수 조건은 그 근거와 최소 보완 범위를 특정한다. 이 초기 요약만으로 검토를 끝내지 않고 Approved Discovery에서 acceptance matrix를 만들고 모든 changed file, 문서, source, test와 exact-head-bound status evidence를 대응시킨다. -11. 독립 code-review worker 결과가 있으면 exact head에 바인딩됐고 authoritative하게 읽을 수 있을 때만 보조 evidence로 사용한다. 그 결과를 spec verdict로 그대로 복사하지 않는다. +11. 문서 검토 입력과 구조·의미 분리 절차로 trusted 계약·산출물별 의무표를 준비하고 모든 문서 의무를 대조한다. 위임 시 해당 입력 전체를 제공한다. 독립 code-review worker 결과가 있으면 exact head에 바인딩됐고 authoritative하게 읽을 수 있을 때만 보조 evidence로 사용한다. 그 결과를 spec verdict로 그대로 복사하지 않는다. 12. Verdict 규칙으로 하나의 strict result artifact와 canonical review body를 trusted workflow directory에 생성한다. 13. Exact key/type, duplicate key, marker field, digest, deterministic correlation, criterion cardinality, path binding, verdict/state 조합, credential 부재와 render → parse → render byte equality를 검증한다. 14. 여기까지는 read-only preflight다. Exact review 요청이 native review write 권한을 부여하지 않았거나 preflight가 완전하지 않으면 종료하며 review create 또는 다른 mutation을 수행하지 않는다. @@ -240,12 +240,24 @@ terminal(command="python3 harness/agents/spec/skills/gitea-implementation-spec-r 모든 종료 경로(정상·no-op·검증 실패·API 오류·예외)는 획득한 lock을 `finally`에서 역순 해제하고 non-blocking 재획득·해제를 확인한다. 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에 남기고 완료로 보고하지 않는다. +## 문서 검토 입력과 구조·의미 분리 + +문서 검토를 main이 수행하거나 delegate에 위임하기 전에 다음 입력을 trusted workflow에 준비한다. 이 준비는 제품 저장소의 새 산출물이나 result schema 변경을 요구하지 않는다. + +- Exact reviewed head와 immutable 문서 snapshot 및 승인된 Discovery의 산출물 계약. +- [Trusted requirements README](../../../../docs/requirements/README.md)와 [TEMPLATE](../../../../docs/requirements/TEMPLATE.md)의 exact 내용 또는 byte parity가 검증된 trusted bundle. 읽은 정본 경로와 revision 또는 digest를 기록하고 실제 내용을 delegate context로 제공한다. 대상 저장소 README/TEMPLATE를 작성 정책으로 대신 사용하지 않는다. +- 산출물별 의무표: exact 경로, 신규·갱신·영향 없음, 승인 근거, 필수 구조 항목, 필수 의미·동작 계약, 확인할 문서·test·evidence와 이전 finding 해소 여부. +- 기존 요구사항 갱신이면 pinned baseline 문서 및 전체 AC의 보존·승인된 변경 대응. 재검토이면 이전 finding의 승인 근거와 최소 보완 요청. +- 실행 evidence의 출처·재사용 가능 범위·확인 한계. 별도 CI·새 evidence 경로·추가 browser 실패 조합을 승인 근거 없이 필수 조건으로 만들지 않는다. + +문서 delegate에는 위 입력을 빠짐없이 전달한다. 입력이 부족하면 main이 보완하거나 직접 검토하며 delegate의 PASS를 최종 판정으로 채택하지 않는다. 구조 적합성(metadata·필수 섹션·요구사항/AC 블록·추적성)과 의미 적합성(승인 계약의 보존·충족·누락)을 각각 판정하고 exact section·line 및 참조 test 근거를 반환하게 한다. 구조 통과만으로 의미를 승인하거나 대체로 맞는 내용으로 필수 구조 누락을 무시하지 않는다. 문구·배치 차이 또는 다른 섹션·기존 test로 보존된 계약의 반복 기재 부재만으로 의미 누락을 만들지 않는다. Main은 모든 의무표 항목과 delegate finding을 pinned 문서에 직접 대조하고 기존 acceptance matrix의 contract·evidence·reason에 반영한다. + ## 위임 규칙 - Main spec-agent는 contract extraction, acceptance matrix, verdict와 외부 write에 책임을 진다. - Bounded diff 분할, criterion별 evidence 수집과 일반 코드 품질 검토는 독립 delegate에 병렬 위임할 수 있다. - Delegate에는 exact PR head, approved contract subset, changed-file allowlist와 read-only 범위를 제공한다. 적용되는 계약의 초기 작성·PR 생성 후 갱신 시점과 특정 구현 수단의 강제 여부를 함께 전달하며, 계약을 충족하는 구현 세부를 새로운 요구사항·범위 초과로 오인하지 않도록 한다. -- 문서 delegate에는 산출물별 필수 계약 항목과 section·line 대응을 요구하고 evidence-only source binding의 검증 결과 및 허용된 실행 evidence 범위를 전달한다. 계약이 요구하지 않은 CI 성공·기존 승인·모든 실패 조합의 browser test를 새 필수 조건으로 만들지 않으며, main은 위 Acceptance matrix 기준으로 누락과 각 변경 요청의 승인 근거를 직접 재검증한다. +- 문서 delegate에는 위 문서 검토 입력 전체를 제공하고 구조·의미의 별도 판정 및 산출물별 필수 계약 항목과 section·line 대응을 요구하고 evidence-only source binding의 검증 결과 및 허용된 실행 evidence 범위를 전달한다. 계약이 요구하지 않은 CI 성공·기존 승인·모든 실패 조합의 browser test를 새 필수 조건으로 만들지 않으며, main은 위 Acceptance matrix 기준으로 누락과 각 변경 요청의 승인 근거를 직접 재검증한다. - Delegate가 Gitea write, source 수정, Git commit·push·merge, lock·cache·worktree 변경을 수행하지 못하게 한다. - Delegate summary는 evidence가 아니다. Main agent가 cited path, diff, status와 test evidence를 직접 확인한다. diff --git a/harness/docs/discovery/README.md b/harness/docs/discovery/README.md index d157b12..53682c2 100644 --- a/harness/docs/discovery/README.md +++ b/harness/docs/discovery/README.md @@ -58,6 +58,20 @@ Discovery는 Gitea Issue 본문으로 작성한다. - 내용 검토: 저장소 근거가 작성 내용을 뒷받침하는지, 요구사항 문서의 갱신·신규 선택이 적절한지, 확정된 결정과 미해결 질문이 서로 모순되지 않는지 확인한다. - 형식 검증 통과만으로 내용 검토나 구현 준비가 완료됐다고 판단하지 않는다. 미해결 질문이 있는 초안은 질문을 남긴 상태로 게시할 수 있으며, 완료 보고에 이를 명시한다. +## 범위 분류와 blocking 판단 + +새 OQ 또는 인수 조건을 제안하기 전에 원본 요청, 현재 사용자 결정과 pinned source의 기존 계약을 근거로 다음 중 하나로 분류한다. + +- 핵심 요청: 원래 문제 해결에 직접 필요한 조건. +- 기존 호환성: 기존 동작·인증·보안·데이터 계약을 보존하는 조건. 핵심 요청 밖이라는 이유로 선택적 확장으로 낮추지 않는다. +- 선택적 확장: 원래 요청·기존 계약에 없는 추가 기능. 명시적으로 채택되지 않은 선택적 확장은 필수 범위·acceptance criterion·blocking OQ로 승격하지 않는다. + +분류와 승인 근거는 trusted workflow의 검토 기록에 남긴다. Discovery marker·H2 순서·hidden metadata나 제품 문서 schema는 변경하지 않는다. 선택적 제안을 남긴다면 현재 승인 범위가 아님을 명시하고 핵심 해결을 차단하지 않는다. + +- Blocking OQ는 핵심 요청 또는 기존 호환성을 안전하게 확정하는 데 실제 사용자 결정이 필요한 경우에만 만든다. 구현 후 실행할 acceptance 검증과 사용자 선택이 필요한 결정 공백을 구분한다. +- 이미 사용자가 명시적으로 채택한 추가 요구나 기존 미선택 OQ를 분류만으로 임의 삭제·해결 처리하지 않는다. 재분류로 기존 결정과 충돌하면 원문 선택을 보존하고 사용자 판단을 받는다. +- 작성 후 각 새 OQ·인수 조건의 분류와 승인 근거를 확인하고, 미채택 선택적 확장이 핵심 해결의 blocker가 되지 않았는지 검증한다. + ## OQ(${open_questions}) 작성 방법 각 OQ에는 질문에 대한 설명과 선택지를 작성하고 추천안을 표시한다. 모든 선택지는 체크박스(`[ ]`)를 사용한다. diff --git a/harness/tests/spec/test_priority_review_contracts.py b/harness/tests/spec/test_priority_review_contracts.py new file mode 100644 index 0000000..39ea6fc --- /dev/null +++ b/harness/tests/spec/test_priority_review_contracts.py @@ -0,0 +1,61 @@ +"""Static contract regressions; these do not prove LLM execution behavior.""" +from pathlib import Path +import re +import unittest + +HARNESS = Path(__file__).resolve().parents[2] +DISCOVERY = HARNESS / 'agents/spec/skills/gitea-discovery-review/SKILL.md' +IMPLEMENTATION = HARNESS / 'agents/spec/skills/gitea-implementation-spec-review/SKILL.md' +README = HARNESS / 'docs/discovery/README.md' + + +class PriorityReviewContracts(unittest.TestCase): + def test_scope_classification_and_optional_nonblocking(self): + text = README.read_text() + for value in ('핵심 요청:', '기존 호환성:', '선택적 확장:', + '명시적으로 채택되지 않은 선택적 확장은', 'blocking OQ로 승격하지 않는다'): + self.assertIn(value, text) + + def test_compatibility_user_decisions_and_existing_oq_preserved(self): + text = DISCOVERY.read_text() + for value in ('기존 보안·호환성 제약은 선택적으로 낮추지', + '미선택 OQ를 임의 삭제·해결하지', '선택을 보존하고 사용자 판단', + '사용자 선택이 필요한 결정 공백과 구분'): + self.assertIn(value, text) + + def test_experiment_verdict_boundary_and_parent_check(self): + text = DISCOVERY.read_text() + for value in ('시험한 방식의 실패:', '소스로 확인된 제약:', '요구 충족 불가능:', + '미검증 또는 판단 유보', '시험하지 않은 대안·확인 한계', + '원본 실험·공식 소스에 직접 대조', '일부 prototype 실패만으로'): + self.assertIn(value, text) + + def test_document_delegate_inputs_and_fail_closed_result_adoption(self): + text = IMPLEMENTATION.read_text() + for value in ('byte parity', '실제 내용을 delegate context로 제공', + '대상 저장소 README/TEMPLATE를 작성 정책으로 대신 사용하지', + '산출물별 의무표:', 'pinned baseline 문서 및 전체 AC', + '이전 finding의 승인 근거와 최소 보완 요청', + '입력이 부족하면 main이 보완하거나 직접 검토', + 'delegate의 PASS를 최종 판정으로 채택하지'): + self.assertIn(value, text) + + def test_structure_semantics_and_preserved_contract_not_conflated(self): + text = IMPLEMENTATION.read_text() + for value in ('구조 적합성(', '의미 적합성(', 'exact section·line', + '구조 통과만으로 의미를 승인', '반복 기재 부재만으로 의미 누락', + '제품 저장소의 새 산출물이나 result schema 변경을 요구하지'): + self.assertIn(value, text) + + def test_local_links_resolve_and_procedures_reference_new_rules(self): + for path in (DISCOVERY, IMPLEMENTATION, README): + text = path.read_text() + for link in re.findall(r'\]\(([^)]+)\)', text): + if not link.startswith(('http:', 'https:', '#')): + self.assertTrue((path.parent / link.split('#')[0]).exists(), link) + self.assertIn('8. 범위 분류·blocking 판단과 기술적 결론의 경계를 먼저 적용', DISCOVERY.read_text()) + self.assertIn('11. 문서 검토 입력과 구조·의미 분리 절차', IMPLEMENTATION.read_text()) + + +if __name__ == '__main__': + unittest.main()