fix(wiki): knowledge-wiki 후속 6건 — G1 오탐 · 커서 분리 · push 경계 · 훅 미탐 - #41
Merged
Conversation
정규식으로 쉘 인용을 다루려 하면 JSON→bash→grep 3중 이스케이프에서 관리가 안 된다.
매칭 전에 따옴표를 제거한다 (tr -d, octal escape 로 인용 충돌 회피).
그 위에 두 가지를 좁혔다.
- 종료 문자에 / 를 넣어 $HOME/ · ~/ 를 잡되, ~/Dev/proj 같은 타깃 삭제는 통과시킨다
- rm 과 -rf 사이에 오는 플래그를 최대 3개까지 허용 (--no-preserve-root 형태)
상한을 둔 건 백트래킹 폭발을 막기 위함이다. 이 훅은 모든 Bash 호출에서 돈다.
이전 정규식이 통과시키던 것: 따옴표로 감싼 홈/루트 타깃, $HOME/, ${HOME},
플래그를 앞에 끼운 형태. 총 10건.
test_danger_hook.sh 를 붙였다. 정규식을 눈으로 읽지 않고 hooks.json 에서 명령을
그대로 뽑아 실제 JSON→shell→grep 체인을 태운다. 42건 — 미탐 21 · 오탐 방지 14 · 기타 7.
이전 정규식으로 돌리면 10건이 실패한다. 이 정규식은 두 번 연속 실측 없이 수정됐다.
Closes #37
knowledge-wiki 후속 4건. 파일이 서로 얽혀 있어 한 커밋으로 묶었다. #35 — G1 오탐으로 유효 세션이 대량 폐기되던 문제 deny 패턴을 두 계층으로 나눴다. G1(원문)은 D1 자격증명 + 주민번호만, G4(article)는 전체. 원문은 기계 밖으로 나가지 않는다 — 반출되는 건 G2 가 새로 쓴 article 이고 그건 G4 가 같은 강도로 검사한다. "대화 중에 파일 경로를 언급했다"는 이유로 세션을 버리면 보호는 그대로인데 회수만 잃는다. D1 을 G1 에 남기는 이유는 다르다. 시크릿은 모델에 넣는 순간 산출물에서 되살아날 수 있고, 그런 세션은 드물어 폐기 비용이 낮다. 패턴도 정밀화했다. - 사설 IP 는 옥텟 4개 강제. 3개만 요구해 macOS 10.15.7, Kafka 10.2.1 을 잡았다 - 이메일에서 git@·noreply@ 등 서비스 계정 제외. SSH remote 를 언급한 세션이 전부 죽었고, SKILL.md 자체가 git@github.com 을 담고 있어 이 문서를 읽은 세션은 자기 자신 때문에 폐기됐다 실측 (최근 세션 30건): 긴 세션 폐기 8/16 → 0/16. #36 — 커서가 세션이 아니라 subdir 기준이던 문제 기본 설정에선 모든 repo 가 같은 per-user/ 를 써서 커서 하나를 전 프로젝트가 공유했다. 재읽기(중복 article)와 구간 누락이 난다. transcript 경로 해시로 커서를 나누고, 위치를 ~/.wigtn/state/ 로 옮겼다. 커서와 no-hooks 설정은 위키 콘텐츠가 아니라 머신 상태다. 위키 안에 두면 clone 한 팀 repo 워킹카피가 지저분해지는데, 팀 repo 에 .gitignore 를 써 넣는 건 요청하지 않은 공유 저장소 변경이라 답이 아니다. #38 — no_hooks 확보 실패 시 fail-open 이던 문제 설정 파일을 못 만들면 --settings 없이 claude 를 띄웠고, 자식 세션이 같은 Stop 훅을 물고 재귀했다. 모듈의 다른 실패 경로는 전부 폐기다. 여기도 폐기한다. #39 — 한글 제목이면 파일명이 note 로 수렴해 덮어쓰던 문제 slug 가 비면 제목 해시로 대체하고(결정론), 타임스탬프에 초를 넣고, 그래도 겹치면 접미사를 붙인다. 덮어쓰기는 하지 않는다 — 잃어버린 쪽은 로그에도 "게시 성공"으로 남아 사라진 걸 알 방법이 없다. #40 — remote + 홈 전체 include 조합 include 가 홈/루트 전체면 remote 가 있어도 push 를 보류한다. 축적과 로컬 커밋은 계속하고 사유를 로그에 남긴다. remote 한 줄을 추가하는 행위의 인지는 "팀 공유를 켰다"인데 실제로는 "이 기계의 모든 repo 지식이 나가기 시작했다"가 된다. 축적까지 끄지 않는 건, 그러면 remote 를 추가한 사용자가 아무 일도 안 일어나는 상태를 만나기 때문이다 — 방금 고친 실패 모드와 같은 모양이다. 마커로 명시 opt-in 한 repo 는 범위가 좁으므로 보류 대상이 아니다. INGEST-POLICY.md 와 SKILL.md 를 코드와 맞췄다. 회귀 테스트 33 → 76건. 계층 분리를 되돌리거나 구 패턴을 복원하면 각각 4건씩 실패한다 (동작을 검사한다). Closes #35, closes #36, closes #38, closes #39, closes #40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#34 리뷰에서 후속으로 넘긴 6건을 처리한다. 두 갈래다 — knowledge-wiki 의 경계 조정(#35 #36 #38 #39 #40)과
위험 명령 훅의 미탐(#37).
관통하는 문제는 하나다. 경계가 잘못된 지점에 그어져 있었다. 너무 앞(G1이 원문을 반출물 기준으로 검사),
너무 넓게(커서가 프로젝트 공용), 또는 코드가 아니라 주석으로(push 범위).
무엇이 바뀌나
#35 — G1을 입력 계층으로 좁혔다
deny 패턴을 두 계층으로 나눴다.
원문은 기계 밖으로 나가지 않는다. 반출되는 건 G2가 새로 쓴 article이고 그건 G4가 같은 강도로 검사한다.
"대화 중에 파일 경로를 언급했다"는 이유로 세션을 버리면 보호는 그대로인데 회수만 잃는다.
D1을 G1에 남기는 근거는 다르다 — 시크릿은 모델에 넣는 순간 산출물에서 되살아날 수 있고,
시크릿이 담긴 세션은 드물어 폐기 비용이 낮다.
패턴도 정밀화했다.
git@github.com오탐은 특히 나빴다. SSH remote를 언급한 세션이 전부 죽었고,SKILL.md자체가git@github.com:myteam/team-wiki.git를 담고 있어서이 문서를 읽은 세션은 자기 자신 때문에 폐기됐다.
실측 — 최근 세션 30건에 적용:
폐기 사유는 전부 D6 홈 절대경로(8) · D2 이메일(2) · D4 내부 호스트명(1)이었다.
#36 · #38 — 커서를 transcript 단위로, 상태를 위키 밖으로
커서가
wiki/<subdir>/.transcript_cursor하나였다. 기본 설정에선 모든 repo가 같은per-user/를 쓰므로전 프로젝트가 커서를 공유했고, 재읽기(중복 article)와 구간 누락이 났다.
transcript 경로 해시로 커서를 나누고, 위치를
~/.wigtn/state/로 옮겼다.커서와 no-hooks 설정은 위키 콘텐츠 가 아니라 이 기계의 상태다. 위키 안에 두면 clone한 팀 repo
워킹카피가 지저분해지는데, 팀 repo에
.gitignore를 써 넣는 건 요청하지 않은 공유 저장소 변경이라 답이 아니다.no_hooks는 fail-closed로 바꿨다(#38). 설정 파일을 못 만들면--settings없이claude를 띄웠고,자식 세션이 같은 Stop 훅을 물고 재귀했다. 모듈의 다른 실패 경로는 전부 폐기다.
#39 — 덮어쓰기 제거
slugify가 한글 전용 제목에서 빈 문자열을 반환해 파일명이 전부note로 수렴했다.타임스탬프는 분 단위, 쓰기는
write_text— 같은 분에 끝난 두 세션이 경고 없이 서로를 덮어썼다.slug가 비면 제목 해시로 대체(결정론), 타임스탬프에 초 추가, 그래도 겹치면 접미사.
덮어쓰기는 어떤 경우에도 하지 않는다 — 잃어버린 쪽은 로그에도 "게시 성공"으로 남아
사라진 걸 알 방법이 없다.
#40 — push 경계를 코드로
include가 홈/루트 전체면remote가 있어도 push를 보류한다. 축적과 로컬 커밋은 계속하고 사유를 로그에 남긴다.remote한 줄을 추가하는 행위의 사용자 인지는 "팀 공유를 켰다"인데, 실제로 일어나는 일은"이 기계의 모든 repo 지식이 원격으로 나가기 시작했다"이다. 한 기계에서 여러 조직 일을 하면 그게 곧 유출 경로다.
축적까지 끄지 않은 건, 그러면
remote를 추가한 사용자가 아무 일도 안 일어나는 상태를 만나기 때문이다 —이 저장소가 방금 고친 실패 모드와 같은 모양이다.
include를 좁히는 순간 push가 켜진다.마커로 명시 opt-in한 repo는 범위가 좁으므로 보류 대상이 아니다.
#37 — 위험 명령 훅
정규식으로 쉘 인용을 다루려 하면 JSON→bash→grep 3중 이스케이프에서 관리가 안 된다.
매칭 전에 따옴표를 제거한다(
tr -d, octal escape로 인용 충돌 회피).그 위에 종료 문자에
/를 넣어$HOME/·~/를 잡되~/Dev/proj같은 타깃 삭제는 통과시키고,rm과-rf사이 플래그를 최대 3개까지 허용했다. 상한을 둔 건 백트래킹 폭발을 막기 위함이다 —이 훅은 모든 Bash 호출에서 돈다.
이전 정규식이 통과시키던 10건: 따옴표로 감싼 홈/루트 타깃,
$HOME/,${HOME}, 플래그를 앞에 끼운 형태.테스트
test_knowledge_wiki.pytest_danger_hook.sh둘 다 validate 워크플로에 연결했다.
테스트가 회귀를 잡는지 확인했다 — 구현을 되돌려 실패를 확인하는 방식으로:
scan_input = 전체 스캔) →G1 통과(원문 허용)4건 실패오탐 없음4건 실패 (SSH remote · noreply · macOS · Kafka)함수가 존재하는지가 아니라 동작이 맞는지를 검사한다.
문서
contracts/INGEST-POLICY.md§2에 두 계층 표를 넣고 §3 게이트 다이어그램과 §4 게시 경계를 갱신했다.SKILL.md의 게이트 설명·진단표·범위 안내를 코드와 맞췄다. 정책 정본과 코드가 어긋나면 정책이 거짓말이 된다.Test plan
validate_plugin.py·check_contracts.py(130) ·test_gate.sh(36) 통과test_knowledge_wiki.py76건 통과 ·test_danger_hook.sh42건 통과~/.wigtn/state/아래에만 생성됨을 확인Closes #35, closes #36, closes #37, closes #38, closes #39, closes #40