목록으로

통과한 자체 시험 8건과 놓친 우회 경로 6건

1 조회
AI 에이전트 개발
#설계 검토#정적 검사#회고#릴리스#플러그인#SSOT

TL;DR

  • 새 검사를 만들고 8가지 방법으로 부숴 봤다. 전부 붉어져서 다 막았다고 적었다
  • 독립 검토 쪽에서 같은 검사를 6가지 경로로 뚫었다. 시험이 겨눈 파일이 우연히 유리한 배치였다
  • 지금은 시험 15건이 전부 붉어진다. 낱말이 파일에 있는지가 아니라 한 줄 안에 함께 있는지를 본다

두 판 동안 아무도 안 한 대체 수단

웹소설 툴킷에 스킬이 13개 있다. 그중 작가에게 선택지를 내는 스킬들은 그 형식을 한 곳에서 가져다 쓴다. webnovel-concept 의 두 절이 그 원본이고 나머지는 그것을 가리키기만 한다.

v1.4.0 때 "선택지 형식을 공통 파일로 빼자"는 제안이 왔고 기각했다. 기각 문구는 이랬다. "새 파일을 만들면 정본이 셋이 된다. 포인터를 넓히는 쪽이 싸고 사본이 안 는다."

그 포인터를 실제로 든 스킬을 세어 봤다.

$ grep -rln "「2. 선택지를 준다」" webnovel-toolkit/skills/*/SKILL.md
webnovel-toolkit/skills/webnovel-world/SKILL.md

13개 중 1개였다. 기각 사유가 "하지 마라"가 아니라 "다른 방법으로 하라"였는데, 그 다른 방법을 아무도 하지 않았다. 그 사이 webnovel-plot 은 "채우는 방식을 2~3안으로 제시한다"고 스스로 적어 놓고 원본을 안 들었다. webnovel-market 은 자기 형식을 따로 들고 있었다.

파일명만 보고 있던 사본 검사

문단 셋을 넣으면 끝날 일로 봤다. 이 저장소에는 "같은 파일이 두 곳에 있으면 한쪽만 고쳐진다"를 막는 검사가 이미 있었기 때문이다.

그 검사가 무엇을 보는지 열어 봤다.

if f in ('trigger_audit.py', 'probes.json', 'wncontract.py'):

파일명 셋이다. 원본의 문장을 다른 스킬에 통째로 붙여 넣어도 이 검사는 아무 말도 안 한다. 실제로 임시 사본에 붙여 넣고 돌려 봤더니 통과 20건, 실패 0건이 그대로 나왔다.

문단만 넣으면 이 판은 지켜 주는 것이 하나도 없는 상태가 된다. 그래서 검사를 함께 만들었다. 원본 문장이 다른 스킬에 복제됐는지, 포인터를 들어야 할 스킬에서 그것이 사라졌는지를 본다.

8가지로 부숴 본 결과

만든 검사가 실제로 붉어지는지 확인해야 했다. 저장소 파일은 안 건드리고 스킬 폴더를 임시 폴더로 복사해서 사본을 망가뜨린 뒤 검사 함수를 직접 불렀다.

무엇을 망가뜨렸을 때 어떤 문장이 나와야 하는지를 먼저 적고 대조했다. 아무것도 안 망가뜨린 경우, 포인터에서 절 하나만 지운 경우, 문단을 통째로 지운 경우, 원본 문장을 복사해 넣은 경우, 예외 선언만 지운 경우, 원본 쪽 지문을 지운 경우, 대상이 아닌 스킬을 건드린 경우까지 일곱이다. 앞 절의 사본 붙여 넣기를 더해 8건이다.

8건 전부 예상한 문장이 나왔다. 다 막았다고 적고 커밋했다.

유리한 배치를 겨눈 시험

독립 검토를 띄웠다. 새 검사가 무엇을 못 보는지 보는 쪽에서 이렇게 돌아왔다.

포인터를 실제로 죽이는 경로 여섯이 전부 초록으로 통과했다.

가장 무거운 것부터 봤다. 검사는 두 낱말이 파일 어디에든 있으면 통과시키고 있었다.

if not all(m in text for m in OPTION_SPEC_MARK):

webnovel-market 은 그 낱말들이 세 줄에 흩어져 있다. 83행이 실제 포인터 문장이고, 85행과 87행이 각각 낱말 하나씩을 또 들고 있다. 83행만 지우면 나머지 두 줄이 검사를 통과시킨다. 원본을 읽으라는 지시는 사라졌는데 검사는 초록이다.

내 시험 8건이 이걸 못 잡은 이유는 포인터를 지우는 시험이 webnovel-characterwebnovel-plot 만 겨눴기 때문이다. 두 파일 다 두 낱말이 한 줄에만 있어서 그 줄을 건드리면 무조건 붉어진다. 판별력이 있어서 갈린 게 아니라 배치가 그랬다.

나머지 다섯도 같은 성격이었다. 원본의 절 번호가 2에서 5로 밀려도 초록, 포인터 스킬 폴더 이름을 바꿔도 초록, "읽어라"를 "따르지 않는다"로 뒤집어도 초록, references/ 폴더에 원본을 복사해도 초록, 등록 안 된 스킬이 예외 선언을 스스로 붙여도 초록이었다.

여섯 밖에 하나가 더 있었고 그게 제일 아팠다. 검사 호출을 main() 에서 한 줄 지워도 "검사 목록 동기화"가 초록이었다. 그 검사는 문서 표에 없는 검사만 봤지, 표에 있는데 안 도는 검사는 안 봤다. 내 시험이 함수를 직접 불렀기 때문에 그게 실제로 꽂혀 있는지는 한 번도 확인되지 않았다.

고친 내용

낱말이 한 줄 안에 함께 있는지를 본다. 지시 동사도 지문에 넣었다. 사본 금지 약속만 넣었을 때는 "읽어라"를 뒤집는 시험이 여전히 안 갈렸다. 문장이 같은 줄에 남아 있어서다.

그 밖에 원본의 절 제목이 실제로 있는지, 상수에 적힌 스킬 폴더가 실재하는지, 목록 개수가 기대와 맞는지, 예외 선언이 등록된 곳에만 있는지를 함께 본다. 사본은 SKILL.md 뿐 아니라 references/ 와 커맨드 문서까지 훑는다. 긴 산문을 두는 자리가 거기다.

"검사 목록 동기화"는 반대 방향도 보게 했다. 표에 있는데 안 돈 검사가 있으면 붉어진다.

개선

시험 8건이 15건이 됐다. 첫 라운드 8건 중 하나는 옛 검사가 눈감는지 보던 것이라 새 하네스에서 빠졌고, 남은 7건에 검토가 찾은 6건, 배선 1건, 개수 잠금 1건을 더했다. 15건 전부 붉어진다.

고치기 전에는 포인터를 죽이는 경로 6가지와 배선 1가지가 통과했다. 지금은 0가지다. 원본을 가리키는 스킬은 1개에서 4개가 됐고 전체 검사는 20개에서 21개가 됐다.

시험을 다시 짜는 동안 한 번 더 걸렸다. 기대 문자열을 20자로 자르는 자리에서 내가 셈을 틀렸다. 붉어진 것은 검사가 아니라 내 기대였다.

남은 것

바꿔 적은 사본은 여전히 못 잡는다. 지문 대조는 글자가 같은 것만 본다. 원본의 판단 기준을 자기 자리에 맞게 풀어 쓴 문장은 안 걸린다. 이번 판의 첫 반영이 그런 문장을 하나 만들었는데, 검사는 지나갔고 사람이 잡았다.

넣을 수 있는 검사를 못 찾았다. 비슷한 문장을 잡는 방식은 오탐이 검출보다 빨리 는다. 지금은 못 잡는다는 사실을 코드 주석과 규약 문서에 적어 두는 데까지만 했다. 다음 사람이 "이건 검사가 본다"고 믿고 산문을 늘리지 않게 하는 것이 목적이다.


댓글

댓글 작성