Commit Graph
97 Commits
Author SHA1 Message Date
DongHyeonkaandClaude Opus 5 2109f726fe feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:01:55 +09:00
DongHyeonkaandClaude Opus 5 d473609e0a fix(studio-save): 응답 껍데기를 벗기는 자리를 코드가 갖고, 계획마다 새 키가 나게 한다
① GET /documents/{id} 는 문서를 data.document 아래에 둔다(studio-v1.yaml:1831). A 가
   B-011 에서 data 를 문서로 보고 견줘 칸이 전부 「다르다」로 나왔다 — 벗기는 자리를
   코드가 갖고 있지 않아서 난 일이라 unwrap_document 를 둔다. 모양이 다르면 조용히
   넘기지 않고 거절한다. 잘못된 층을 견주는 것보다 멈추는 편이 낫다.

   그리고 updatedAt 을 서버가 붙이는 칸으로 옮겼다. 매 저장마다 「서버가 더 줬다」로
   나오면 그 칸을 아무도 안 읽게 된다. variantIds 는 안 옮겼다 — 계약이 입력으로 받는
   칸인데 이 어댑터가 안 보내므로 보여야 한다.

② CR-007 때문에 같은 키를 두 번 못 쓴다. 런 식별자를 계획 파일에 적고 키에 섞는다.
   같은 계획을 두 번 보내면 같은 키, 계획을 새로 만들면 다른 키다. 보낼 때 만들면
   재시도가 곧 중복 생성이 되므로 만드는 자리는 계획을 짜는 순간 하나뿐이다.
   --run-id 를 안 주면 예전 키 모양 그대로라 옛 계획을 안 깬다.

python3 -m unittest discover -s scripts/tests — Ran 291 · OK (skipped=13)
PIPELINE CONTRACT: PASS

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 13:18:30 +09:00
DongHyeonkaandClaude Opus 5 1be1c8ea6d fix(studio-save): 항목 id 를 다시 매기는지는 종류마다 다르다
A 가 B-011 운영 실행에서 봤다. PROJECT_DECISION 은 보낸 uuid5 여섯이 글자 그대로
돌아온다. QUESTION 은 서버가 uuid4 로 다시 발급한다(C · V-009 열 번째). 둘 다
OrderedText 배열인데 서버가 다르게 다룬다.

한 벌로 묶어 빼면 DECISION 에서 볼 수 있는 것을 안 보게 된다 — 서버가 언젠가
DECISION 도 재발급하기 시작해도 아무도 모른다. 그래서 종류로 갈랐다.

  QUESTION          다시 매긴다 → id 를 뺀다 (쟀다)
  PROJECT_DECISION  다시 안 매긴다 → 그대로 본다 (쟀다)
  그 밖              안 쟀다 → 빼지 않고 그대로 보고, 안 쟀다는 것을 값에 적는다

안 잰 채로 빼면 「안 봐도 되는 것」으로 굳는다. 「못 보는 것」과 「안 봐도 되는 것」은
다르다 — itemIdBehaviourUnmeasured 가 어느 칸을 왜 그대로 견줬는지 적는다.

python3 -m unittest discover -s scripts/tests — Ran 286 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 11:19:55 +09:00
DongHyeonkaandClaude Opus 5 325b6008ab fix(studio-save): 선택지 절의 세 가지 모양을 전부 읽는다
A 가 전수로 재서 알려 줬다. 내가 목록형 기록 하나를 보고 「이미 잰 값은 안 바뀐다」로
일반화한 것이 틀렸다 — 문단형인 openquestion-* 계열이 다섯 칸을 통째로 잃고 있었다.

`## 선택지` 를 쓰는 모양이 셋이다. `### N. 제목` · `**굵은 한 줄**` · 표시 없이 첫 줄이
제목인 문단. 첫째만 읽고 있었고, `_ordered` 의 「글이 있는데 항목 0 개면 거절」이 이 칸에는
안 걸려서 읽지도 않고 막지도 않는 상태였다.

전수로 다시 쟀다 (0d58881 대 지금):

  배열 칸이 있는 기록 150건 · 값이 달라진 것 83건 · 거절 0건
  종류별 — decision 29 · question 26 · reference 28

고친 뒤에도 0 인 칸이 24건 남는데 전부 REFERENCE 의 rules 다. 그것은 다음 배치 16번이고
설계 조건(「없는 규칙」과 「다른 모양으로 쓴 규칙」을 가른다)이 붙어 있어 안 건드렸다.

options 의 거절 가지는 이제 안 닿는다 — 세 번째 모양이 글이 있으면 언제나 항목을 하나는
만든다. MAX_KEY_LENGTH 와 같은 자리라 지우지 않고 그렇게 주석에 적었다.

python3 -m unittest discover -s scripts/tests — Ran 284 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 11:11:17 +09:00
DongHyeonkaandClaude Opus 5 252f14c88e fix(studio-save): 양쪽이 빈 칸을 「같다」에 섞지 않는다 (C 의 B-006 ② 수정안)
C 가 V-009 에서 걸렸다 — P-QUESTION-01 의 「되읽기 차이 0」이 배열 다섯이 전부 비어
견줄 것이 없어서 나온 값이었다. same: true 가 「13칸이 다 맞았다」로 읽히는데 실제로는
찬 칸만 맞은 것이다. R14 와 같은 모양이 대조기 안에 한 번 더 있었다.

- _is_empty 로 양쪽이 빈 칸을 갈라 emptyBoth 로 낸다. 한쪽만 비면 차이다
- comparedFields 에서도 빠지고 comparedCount 가 실제로 견준 수를 낸다
- 사람이 보는 한 줄에 「N칸 중 M칸을 견줬다. K칸은 양쪽이 비어 견줄 것이 없었다」

C 의 수정안은 comparedFields 를 개수로 바꿨는데 목록을 유지하고 개수를 따로 뒀다 —
이미 목록으로 읽는 회귀가 있고, 어느 칸을 못 봤는지는 이름이 있어야 안다.

C-B006 §4 의 대조를 돌렸다. 손으로 센 찬 칸과 도구 출력이 맞는다.

  CONCEPT   6 / 6      REFERENCE 10 / 10      QUESTION 11 / 11

묶음은 B 가 읽을 수 없어(계약 §3.2) 같은 종류의 기록으로 셌다.

python3 -m unittest discover -s scripts/tests — Ran 282 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 11:06:12 +09:00
DongHyeonkaandClaude Opus 5 f765a79d6f fix(studio-save): PROJECT_DECISION 의 칸을 계약에 맞추고 지울 수 있게 만든다
B-010 §4.1 에서 찾고 안 고친 것이다. 그때 안 고친 이유는 예산이 아니라 측정이었고,
이번에는 그 런에서 잰다.

- 결정문→statement · 영향→consequences(OrderedText 배열) · 판단 이유→rationale
- 근거→basis 를 뺐다. ProjectDecisionInput 에 그 칸이 없고 unevaluatedProperties:
  false 라 보내면 거절된다. 기록의 ## 근거 절은 그대로 둔다
- decisionStatus·decidedOn 을 frontmatter 에서 읽는다. enum 밖의 값은 지어내지 않고
  거절한다 — 운영에 초안을 만들어 놓고 422 를 받는 것보다 낫다

그리고 절의 모양이 둘이었다. 사실·가정은 `- ` 목록이고 DECISION 의 영향은 빈 줄로 나뉜
문단이다. 목록만 읽어서 문단으로 쓴 절이 조용히 [] 가 되고 있었다 — minItems 가 없어
그대로 저장되고 내용만 사라진다. 목록을 먼저 보고 없으면 문단으로 나누며, 글이 있는데
항목이 0 개면 거절한다. QUESTION·REFERENCE 의 이미 잰 값은 안 바뀐다(목록이라 첫 갈래에서
끝난다).

--harness-test 의 PROJECT_DECISION 거절은 우회하지 않고 값을 받게 했다. --project-id 는
사람이 Studio 목록에서 읽은 uuid 다 — 어댑터가 이름을 uuid 로 바꾸지 않는다. 없으면
여전히 거절한다. projectId 는 [string, "null"] 이고 계약이 저장 시점에는 강제하지 않으니
null 로도 만들어지고, 만들어지면 지울 수 없다. 되읽기가 그 값을 확인한다.

python3 -m unittest discover -s scripts/tests — Ran 278 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 11:03:03 +09:00
DongHyeonkaandClaude Opus 5 0d588814cc fix(studio-save): CONCEPT·REFERENCE 의 칸이 계약과 달랐다
C 가 V-009 에서 운영에 직접 걸어 찾았다. QUESTION 에서 고친 것과 같은 결함족이다.

① CONCEPT — basisVersion 이 required 인데(studio-v1.yaml:979) 어댑터가 절만 봤다.
   값은 frontmatter 에 있다. 상한 120자를 넘으면 조용히 자르지 않고 거절한다.

② REFERENCE — 셋이 한꺼번에 틀렸다(studio-v1.yaml:955-963).
   이름: appliesWhen 이 아니라 applyWhen 이다 — nextValidation 과 같은 자리다
   모양: rules 는 ReferenceRule 배열, applyWhen·exceptions·examples 는 OrderedText
         배열이다. purpose 만 문자열이다
   없는 칸: verifiedOn 이 required 인데 아예 없었다. 기록이 안 적었으면 null 로 둔다

③ 되읽기 비교가 항목 id 를 견주고 있었다. 보내는 것은 uuid5 이고 서버는 저장하면서
   uuid4 를 새로 발급한다 — 그대로 두면 모든 QUESTION·REFERENCE 저장이 「차이 있음」이
   된다. 칸 전체를 빼지 않고 id 만 뺐다. text·title·body·order 는 계속 대조하므로
   항목이 빠지거나 순서가 바뀌는 것은 여전히 걸린다. 무엇을 왜 안 보는지는
   itemIdsNotCompared 에 값으로 적는다.

python3 -m unittest discover -s scripts/tests — Ran 273 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 09:35:19 +09:00
DongHyeonkaandClaude Opus 5 14137382fe fix(tests): 새 시험의 픽스처를 TechLog 로 옮겨 PIPELINE CONTRACT 를 되돌린다
첫 제출에서 verify-pipeline 의 FORBIDDEN_LITERAL 이 새 시험을 걸었다. 픽스처가
옛 저장소 이름과 같은 이름의 docs 프로젝트를 가리키고 있었다.

글자를 쪼개 피하지 않았다. verify-pipeline.py:172 자신이 쓰는 수법이지만 그건 검사기가
물으려던 것에 답하는 게 아니라 글자만 피하는 것이다. 재는 것이 「비교를 돌렸나」이지 그
기록의 내용이 아니므로 프로젝트를 TechLog 로 옮겼다.

양성 대조를 함께 넣었다 — available: true 만 재면 픽스처가 조용히 같아졌을 때 시험이
초록인 채로 아무것도 안 재게 된다. warnings 가 비지 않았는지와 그중에 「유보 감소」가
있는지를 잰다.

검사기가 「옛 저장소 이름에 의존한다」와 「그 이름의 docs 프로젝트를 가리킨다」를 못 가르는
것은 고치지 않고 보고서 §7.6 에 발견으로 적었다. 검사기 수정은 C 몫이다.

  PIPELINE CONTRACT: PASS
  python3 -m unittest discover -s scripts/tests — Ran 266 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 09:25:03 +09:00
DongHyeonkaandClaude Opus 5 0f753c3be5 fix: TechLog 리비전 셋을 매니페스트에 맞추고, 「경고 0건」을 두 가지로 가른다
부채 ④. TechLog 의 tech-log-design-package 가 ca1bbfe·1aae8dc·ffa088b 를 적고 있었는데
매니페스트는 그 뒤 다시 만들어져 세 파일 모두를 tech-log-frontend @ 0d4d1e5 로 적는다.
옛 셋은 tech-log-frontend 에 없다 — 옛 매니페스트가 안 남아 그 사이 계약 내용이 바뀌었는지는
대조할 수 없고, 그 사실을 verified 에 적었다. 기록의 「검증 환경」이 적은 리비전은 그때 잰
조건이라 고치지 않는다. 파일 sha256 셋을 함께 적어 리비전 문자열이 아니라 내용에 못박는다.

  check_evidence --repo TechLog     exit 0 「문제 없음」 (3 → 0)
  review-package.py TechLog         exit 0 · 관문 10 전부 exit 0

R14. 이 묶음의 막는 경고는 전부 편집 전후 보존 비교에서 나오고(review-package.py 의
_warn 자리 둘이 모두 if preservation.get("available") 안이다) 그 비교는 --before 를 줘야
돈다. 빼면 검토 관문이 아무 줄도 안 남기고 통과한다.

- --before 를 필수로 만들지 않는다. 새로 쓴 기록엔 윤문 전 사본이 없어 첫 기록이 막힌다.
  없다는 것을 적고 출력한다
- preservation.reason 으로 두 원인을 가른다 — NO_BEFORE · CHECKER_UNREADABLE
- 묶음이 자기 입력을 적는다. 줬으면 before·beforeSha256, 안 줬으면 명시적 null.
  빠진 칸과 null 은 다르다
- 종료 코드는 양쪽 다 0 이다. 새 관문이 아니라 읽는 계약이라 갈리는 것은 문구다.
  _review_gate 의 stderr 와 계획 한 줄 요약 둘 다 가른다
- B-B004c-plans/verdict.json 을 지웠다. 옛 묶음 53ce6a5b… 에 묶여 있는데 지금 계획은
  0beb5420… 이라 읽히지 않는다 — 검토를 받은 것처럼 보이는 파일이 남는다

python3 -m unittest discover -s scripts/tests — Ran 266 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 09:18:45 +09:00
DongHyeonkaandClaude Opus 5 d2855d1e6c fix(studio-save): 정상 입력이 저장까지 못 가던 이유 셋을 고친다
C 가 운영 Studio 에 직접 걸어 찾았다. 셋 다 서버가 아니라 이 어댑터가 만든 요청의 문제다.

① 422 IDEMPOTENT_REQUEST_MISMATCH 가 어디에도 안 적혀 있었다. 코드만 보면 본문이
   틀렸다고 읽는다 — 서버 문구도 무슨 일이 있었는지만 말한다. SERVER_ERRORS 표를
   만들어 계획의 expect 에 실었다. 표에 없는 코드는 Refused 다.
   키 설계는 안 건드렸다. 본문 해시를 넣으면 「재시도가 문서를 둘 만들지 않는다」가
   깨진다 — idempotency_key 주석에 이 설계가 막는 것을 적어 두었다.

② --harness-test 가 제목만 바꾸고 slug 는 원본 그대로였다. slug 에 유일성 제약이
   있어 409 DB_UNIQUE_VIOLATION 이다. 시험 초안은 반드시 원본에서 나오므로 이 충돌은
   필연이다. 접두사는 두 번 붙지 않고, 길이 상한을 넘기면 자르지 않고 거절한다.

③ QUESTION 의 칸이 문자열이 아니었다. facts·assumptions·unknowns·constraints 는
   OrderedText 배열, options 는 QuestionOption 배열, 마지막 칸 이름은 nextValidation
   이다(studio-v1.yaml:1013-1040). id 는 uuid5 로 만든다 — 난수면 같은 기록을 다시
   계획할 때 본문이 달라져 저장 멱등 키가 흔들린다.

곁다리로 relations 가 [] 이지 None 이 아닌 이유를 주석에 남기고, 정리 단계의 값을
계약이 적은 400 이 아니라 C 가 관측한 422 로 고쳤다. 404 는 지워졌다는 뜻이 아니다 —
종류를 어긋나게 보내면 404 뒤 GET 이 200 이다.

python3 -m unittest discover -s scripts/tests — Ran 257 · OK (skipped=13)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 09:07:38 +09:00
DongHyeonkaandClaude Opus 5 05e96b5add fix(cabt): 기록이 인용한 것을 SSOT 가 안 담고 있었다
check_evidence --repo 이 clean-architecture-backend-template 에서 141건을 세고 있었다.
124건 전부를 고정 리비전 21234e38 에 대조했더니 날조는 0 이었다 — 인용은 맞고 없던
쪽이 SSOT 였다. 그래서 SSOT 를 보강한다.

넣는 것은 기록이 옮겨 적은 문장이 아니라 저장소 원문이다. 기록을 복사해 넣으면
검사기는 초록이 되지만 옮겨 적기가 어긋나도 더는 못 잡는다. 원문을 넣으면 어긋난
기록은 계속 걸린다.

- 코드 124건 → 리프 절 일곱 곳에 원문 30조각(300줄). 자리는 기록의 source 앵커와
  소스 파일의 모듈을 교차시켜 정했고, 둘이 갈린 다섯은 모듈을 따르고 그 사실을 적었다
- 식별자 13건 → spring.factories · MethodSecurityConfig 원문과 줄여 적힌 경로의 전체 경로
- source 앵커 4건 → 계약이 이미 적고 있던 SSOT 앵커를 기록 frontmatter 에 맞췄다.
  맨 앵커를 한 줄씩 앞에 둔다 — `_record_sources` 가 `- <공백 없는 한 덩어리>` 만 잇달아 읽는다
- 인용 4건 → concept-signed-cursor-structure 의 `{ ... }` 생략을 저장소 원문 형태로 고쳤다.
  생략한 자리는 `…` 로 남기고 무엇을 줄였는지 본문에 적는다

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 01:10:51 +09:00
DongHyeonkaandClaude Opus 5 96d89ec6da fix(studio-save): 정리 단계가 본문 없이 나가고 종류를 안 봤다
DELETE 가 본문을 요구한다. requestBody: required: true 이고 컨트롤러가 @RequestBody
ExpectedVersionRequest 를 받는다. 본문 없이 보내면 400 이고 초안이 남는다. expectedVersion
은 verify 가 읽은 값이어야 한다 — 만든 뒤 한 번 더 저장하므로 만들 때 version 이 아니다.

경로가 종류를 안 보고 무조건 cases 로 나갔다. 서버는 종류를 조회 조건에 넣고 그 이유를
코드에 적어 두었다 — 그렇게 하지 않으면 Case 경로로 Reference 를 지울 수 있게 되기
때문이다. 경로가 종류와 어긋나면 DOCUMENT_NOT_FOUND 가 나고 초안이 남는다.

시험 초안을 CASE 로 쓴 판단은 맞았지만 코드가 그 판단에 기대고 있었다. 종류별 경로를
표로 만들고, Decision 처럼 프로젝트 id 가 필요한 것과 모르는 종류는 거절한다 — 조용히
받으면 못 지우는 초안이 운영에 남는다.

CSRF 헤더 이름과 404 의 뜻도 계획에 적었다. X-XSRF-TOKEN 이면 403 이다.

이 고침은 계약에 맞춘 것이지 걸어 본 것이 아니다. 계획대로 보내 본 사람은 아직 없다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-11 00:36:29 +09:00
DongHyeonkaandClaude Opus 5 e1404f8a3d chore(skill): publishing-tech-log-to-studio 를 1.0.1 로 올린다
산문의 사실 하나가 틀려서 고쳤다. 내용이 바뀌면 버전이 따라 움직여야 통과 판정을 그
버전에 묶을 수 있다 — 그러라고 metadata.version 을 만들었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 19:19:13 +09:00
DongHyeonkaandClaude Opus 5 aae43f4f99 fix: 우연히 지켜지던 멱등성을 회귀로 못박고, 틀린 문장의 출처를 고친다
저장 멱등 키가 필드 순서에 안 흔들리는 것은 sort_keys=True 덕이고, 그것은 JSON 을
안정적으로 만들려고 넣은 것이지 멱등성을 노린 것이 아니다. 다시 직렬화한 재시도가 새
저장으로 잡히지 않게 지키고 있었는데 그 성질을 명시한 시험이 없었다. sort_keys 를 빼도
아무 시험이 안 깨졌다. 의도하지 않은 성질에 기대는 코드는 회귀로 못박지 않으면 다음
사람이 지운다.

대조군도 함께 넣었다. 순서에 안 흔들린다고 내용에도 안 흔들리면 그건 다른 결함이다.

그리고 「Decision 은 계약에 삭제 경로가 없다」의 출처가 스킬이었다. 내가 코드 주석과
보고서로 옮긴 문장이 거기서 왔다. 다섯 종류 전부의 삭제 경로를 세어 표로 바꾸고 확인한
리비전(tech-log-backend @ a000f87)을 함께 적었다 — 서버를 서술할 때 리비전이 없으면
서버가 바뀌어도 아무도 모른다.

지워지지 않는 것은 따로 있다. 게시한 적이 있으면 DOCUMENT_PUBLISHED, 관계로 참조되면
DOCUMENT_IN_USE 다. 그것이 원래 말하려던 것이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 19:19:01 +09:00
DongHyeonkaandClaude Opus 5 4e9226a57d fix(studio-save): 확인 없이 옮긴 문장을 고치고, 안 닿는 가지에 유효 범위를 적는다
「Decision 은 계약에 삭제 경로가 없다」고 적었는데 틀렸다. ManagementDocumentController
:123 에 DELETE /v1/studio/projects/{id}/decisions/{decisionId} 가 있다. 스킬의 문장을
확인 없이 옮겼다. 시험 초안을 CASE 로 고른 이유는 삭제 경로 때문이 아니라 그 기록이 이
배치가 만든 것이라 남의 것이 아니어서다.

멱등 키 길이 검사는 지금 정책에서 안 닿는다. 키가 studio-{op}-{32자}(+{32자})라 길이가
사실상 고정이고 저장 키가 77자다. 죽은 코드가 아니라 키 정책이 바뀌면 살아나는 방어선이라
지우지 않고, 언제 살아나는지를 주석에 적었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 19:16:55 +09:00
DongHyeonkaandClaude Opus 5 c66eee6cb2 feat(studio-save): 시험 초안 계획에 두 번째 저장과 정리 단계를 넣는다
만들기만 하면 이 런에서 expectedVersion·VERSION_CONFLICT 경로가 한 번도 안 돈다. 새로
만든 뒤 한 번 더 저장해 낙관적 락을 태운다. expectedVersion 은 만들기 응답이 준 version
이고, 모른 채 보내면 서버가 0 으로 채워 늘 충돌한다.

만든 시험 초안을 그 자리에서 지운다. DELETE /api/v1/studio/cases/{id} 가 실재하는 것을
소스와 openapi 에서 확인했고 게시 가드에 안 걸리는 것도 확인했다. 시험 초안의 종류를
CASE 로 고른 이유가 그것이다 — Decision 은 계약에 삭제 경로가 없다.

정리 단계는 --harness-test 일 때만 낸다. 진짜 기록에는 삭제 요청을 만들지 않는다.

삭제가 409 를 낼 수 있다 — 공개된 기록이거나 참조하는 곳이 있는 경우다. 둘 다 사람이
화면에서 처리하고, 계획에 그렇게 적었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 19:15:07 +09:00
DongHyeonkaandClaude Opus 5 f16785b93e feat(studio-save): 게시된 기록을 코드가 저장 대상에서 뺀다
사용자가 studio:publish 권한 분리를 유예하고 write 권한을 가진 실계정으로 저장하기로
했다. 설계의 명시적 예외이고, 설계가 프롬프트 통제를 인정하지 않는 이유가 여기 그대로
적용된다 — 「이미 게시된 게시물은 건드리지 않는다」를 문장이 아니라 코드로 만든다.

한 번이라도 게시한 문서는 게시를 취소해도 삭제가 409 로 거절된다. public 이 찼거나
status 가 게시 중이면 저장 대상에서 뺀다. 저장소에서 두 표시가 같은 17건을 가리키지만
하나만 차 있어도 게시로 본다 — 한쪽이 뒤늦게 채워지는 경우를 놓치지 않는다.

frontmatter 는 저장소가 아는 것이지 서버가 아는 것이 아니다. 그래서 계획의 첫 단계가
서버에 게시 상태를 묻고, PUBLISHED 면 저장 단계로 넘어가지 않는다. 새 문서를 만드는
계획에는 그 단계가 없다 — 아직 없는 문서에는 물어볼 게시 상태가 없다.

시험 초안에 [HARNESS-TEST] 접두사를 붙일 수 있게 했다. 나중에 사람이 눈으로 가린다.

무인 저장은 그대로 꺼져 있다. CR-001 이 유예됐다는 것은 무인 저장을 켠다는 뜻이 아니다 —
사람이 보는 앞에서 저장하는 것과 사람 없이 저장하는 것은 다른 이야기다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 19:11:25 +09:00
DongHyeonkaandClaude Opus 5 87d70e7c80 feat(check-preservation): 편집이 핵심 칸에 측정 주장을 새로 더했는지 본다
check-core-support 는 근거 목록이 비었는데 측정을 주장하는 것을 본다. 근거가 차 있는
기록에 그 근거가 지지하지 않는 결론을 더하는 편집은 그 규칙 밖이다 — 출처가 있다는 것과
그 출처가 그 주장을 지지한다는 것은 다르다.

「측정 주장이 있으면 경고」로 가지 않았다. 그건 저장소의 정상 기록 다수에 걸리고, 모든
기록에 걸리는 경고는 어느 기록에 대해서도 아무 말을 하지 않는다. 편집이 핵심 칸에 측정
주장을 새로 더했는지만 본다 — 적용 범위 검출과 같은 모양이고 더해진 것을 본다.

판정은 check-core-support 의 목록을 그대로 불러 쓴다. 두 곳에 두면 갈린다.

판정하지 않는다. 근거가 이 주장을 지지하는지는 근거를 읽어야 알고, 그것은 검토의 몫이다.

채택 편집 100쌍에 한 번도 안 걸린다. 그 쌍들은 문제·결론 칸이 없는 조각이라 대상이
아니고, 회귀에 그것도 넣었다.

곁들여 check-core-support 에 --file 을 더했다. 다른 검사기들이 이미 받은 것이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 17:40:02 +09:00
DongHyeonkaandClaude Opus 5 ad055fb3b9 feat(scripts): 핵심이 측정을 주장하는데 근거 목록이 비었는지 본다
후보를 둘 버렸다. 처음에 「핵심의 수치가 근거에 없으면」으로 만들었는데 사례에 아라비아
숫자가 하나도 없다 — 「응답시간의 p99 가 절반이 됐다」. 275건에 돌려 오탐 0 · 검출 0 이었다.

두 번째는 비교 낱말만 봤다. 275건에서 둘이 걸렸고 둘 다 오탐이었다 — 「계약의 절반은
라우트 레지스트리에서 유도한다」·「가드는 절반만 존재합니다」. 한국어에서 「절반」은
측정이 아닌 쓰임이 흔하다.

그래서 성능을 재는 명사와 비교하는 말이 함께 나올 때만 측정 주장으로 본다. 그 주장을
하면서 evidence 가 비어 있으면 핵심에 근거가 없는 모양이다. 근거를 대면 통과한다 —
막는 것은 주장이 아니라 근거 없는 주장이다.

경고를 만드는 것으로 끝내지 않고 review-package 의 관문에 넣었다. 관문이 exit≠0 이면
studio-save 의 approved() 가 그 묶음을 거절한다. 만드는 것과 막는 것은 둘 다 있어야
한 쌍이다.

저장소 실제 기록 275건에 하나도 안 걸린다. 문제·결론에 수치를 적는 건 정상 기록이 늘
하는 일이라 여기가 과잉 차단이 가장 나기 쉬운 자리다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 17:20:47 +09:00
DongHyeonkaandClaude Opus 5 e3230ce5ed feat(scripts): 경고를 읽는 장치가 아니라 만드는 장치를 더한다
남은 셋은 묶음 경고가 0건이라 게이트를 아무리 조여도 지나간다. 뿌리가 하나다 — 지금
검사기는 사라진 것과 새로 생긴 보호 구간을 보고, 범위가 넓어진 것과 정본을 안 거친 것을
보는 자리가 없었다.

그림이 정본을 거쳤는지 본다. SVG 는 바뀌었는데 spec.json 은 그대로면 그 그림은 정본에서
나온 것이 아니고, 화살표 뒤집기가 그 모양이다. spec 의 간선과 SVG 의 경로를 직접 견주려면
렌더러가 id 를 어떻게 붙이는지 알아야 하는데, 정본을 거쳤는지만 보면 몰라도 된다.
작업 트리와 이력 두 자리를 본다. 정본이 없는 그림은 볼 것이 아니라 세기만 한다.

적용 범위를 넓히는 말이 새로 들어왔는지 본다. 로컬에서 확인했다에 운영 환경에서도를
더하기만 하면 유보도 보호 구간도 안 바뀐다 — 지운 것이 없기 때문이다. 유보 감소의
반대편이고, 판정하지 않고 경고로 올린다.

경고 생산자를 더하는 것이 과잉 차단이 생기는 자리라 후보마다 채택 편집 100쌍을 돌렸다.
error 도 경고도 안 늘었다. 정상 편집에 경고가 붙으면 그것도 사실상 차단이다.

목록에 운영·항상 같은 흔한 말이 들어가서, 원래 있던 말을 두고 주변만 고쳐도 걸리는지
보는 대조군을 회귀에 넣었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 16:33:47 +09:00
DongHyeonkaandClaude Opus 5 16cbe141a0 fix: 고친 자리에 같은 버그를 다시 넣었고, 게이트가 관측 창을 덮었다
한글 수사의 뒤 경계가 세는 말 다음의 조사를 낱말의 일부로 봤다. 「다섯 개다」·「다섯 건을」·
「여섯 장이」가 전부 안 걸린다. 아라비아 숫자 쪽에서 같은 이유로 「500행」·「5개다」를
놓쳤던 것을 고쳤는데, 그 고침을 한글로 옮기면서 다시 넣었다. 회귀가 초록이었던 건 시험
문구가 전부 조사 없이 끝나서다.

뒤 경계를 풀었더니 채택된 편집 둘이 새로 막혔다. 「여덟 자리 → 여덟 곳」이다. 숫자가 안
바뀌었고 세는 말이 바뀌었다 — spatial-metaphor 를 고치는 정상 편집이다. 그래서 잡는 것을
수사로 좁히고 세는 말은 문맥으로만 본다. 대안도 긴 것부터로 정렬했다.

대조군을 주변이 바뀌는 쌍으로 다시 짰다. 같은 문자열은 어떤 검사기든 조용해서 대조가 되지
않는다. 그 대조군이 없었으면 위 오탐을 못 봤다.

그리고 저장 게이트가 그림 붙은 기록을 전부 막고 있었다. 저장소의 그림에 종류 표시가 하나도
없어서 그림이 붙으면 무조건 경고가 하나 붙는다. 게이트가 틀린 게 아니라 그 경고가 어느
기록에 대한 정보도 아니다 — 모든 기록에 걸리는 경고는 어느 기록에 대해서도 아무 말을 하지
않는다. 저장소 전체의 미비는 세고 보고하되 저장을 막지 않는다. 조용히 빼지도 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 16:15:04 +09:00
DongHyeonkaandClaude Opus 5 20c8535169 feat(studio-save): 검토로 보낸다는 것은 저장이 막힌다는 뜻이어야 한다
시도 1 이 「8건이 PASS 로 나가지 않는다」를 세웠는데 코드 3건에 대해서만 성립했다. 경고는
실렸는데 그것을 읽고 막는 코드가 없었다. 검토로 라우팅한다는 것이 실제로는 경고를 붙여서
통과시키는 것이었다. 막지 않으면 라우팅이 아니라 주석이다.

경고가 있으면 항목마다 판정을 받고 전부 PASS 일 때만 저장이 나간다. 판정 파일이 없으면
거절한다 — 검토를 안 받은 것과 검토가 통과시킨 것은 같은 결과일 수 없다. 판정이 안 붙은
경고가 있어도 거절한다 — 빠뜨린 것과 통과시킨 것은 다르다.

FAIL 과 UNKNOWN 은 둘 다 막되 문구가 갈린다. 근거가 모자라 판정을 못 한 것과 근거를 읽고
틀렸다고 본 것은 다음에 할 일이 다르다.

판정이 어느 경고에 붙은 것인지 정하려고 경고에 키를 붙였고, 그 키를 읽는 코드를 같은
변경에 넣었다. 내용이 바뀌면 키도 바뀌어 옛 판정이 다른 경고에 붙지 않는다.

경고가 없으면 판정 파일 없이 그대로 나간다. 정상은 이 게이트에 안 걸린다 — 채택 편집
100쌍에서 91쌍이 판정 없이 나가고 9쌍이 판정을 받아야 한다. 판정을 받아야 한다는 것은
막힌다는 뜻이 아니다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 15:53:26 +09:00
DongHyeonkaandClaude Opus 5 845ee89054 feat(scripts): 놓친 8건을 코드로 막거나 검토로 보낸다
먼저 쟀다. 경고를 일괄 차단으로 올리면 채택된 편집 9건이 막힌다 — V-004 에서 고친 바로
그 9건이다. 그래서 경고는 검토로 보내고 차단은 검토가 판정할 때 일어나게 뒀다.

코드로 막은 것 셋. code[] 리비전 — check_evidence 는 리비전이 있는지만 보고 인용한 코드가
그 리비전에서 왔는지는 안 본다. 이 배치에서 실제로 났고 사람이 손으로 잡았다. 한글 수사와
그 경계 — 「다섯 개 → 여섯 개」는 결정적이다.

앵커 검사기의 오탐을 0 으로 만드는 데 시간의 절반이 갔다. 처음 판이 저장소 전체에서
401건을 냈고 전부 오탐이었다. code[] 는 한 모양이 아니다 — 심볼, 축약 경로, 줄 범위,
호스트 절대 경로, 설정 키가 섞여 있다. 축약 경로를 「없다」로 세면 있는 코드를 없다고 하는
것이고 그게 채택된 편집 아홉 건을 막았던 실패와 같은 모양이다. 판정할 수 있는 것만
판정하고 못 보는 것은 세어서 낸다.

검토로 보낸 것 다섯. D2 는 아무 계수도 안 움직이던 자리였다 — 수치도 인용도 없이 산문만
더하면 보호 구간 비교에 잡힐 것이 없다. 1인칭 표지가 늘어난 것만 보고 그 문장을 짚어 준다.
판정이 아니라 라우팅이다.

만들다 버그를 찾았다. 경고가 인용하는 문장이 파일 첫 문단에서 한 글자씩 깎이고 있었다.
rfind 가 -1 을 낼 때 +2 를 해서 1 이 됐다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 15:47:54 +09:00
DongHyeonkaandClaude Opus 5 230ad874ca fix(tests): 원장 회귀가 프로젝트 이름을 적지 않게 한다
verify-pipeline.py 의 FORBIDDEN_LITERAL 가드가 scripts/ 안에서 저장소
체크아웃 이름을 금지한다. B-005 의 회귀가 그 이름으로 실제 원장을 찾다가
PIPELINE CONTRACT 를 FAIL 로 만들었다. glob 으로 아무 원장이나 고르게
바꿨고 시험의 뜻은 그대로다 — 더한 칸이 있어도 검사기가 읽는가.

병합에서 드러난 계약 충돌이라 통합 브랜치에서 조정한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GksxvQvM6A85xy8viYtWk6
2026-09-10 15:39:14 +09:00
DongHyeonka fdb38e9012 Merge branch 'harness/B-implementation' into harness/A-integration 2026-09-10 15:37:56 +09:00
DongHyeonkaandClaude Opus 5 96d7fbc57a fix(run-ledger): 파일은 안 깨지는데 두 세션의 기록이 섞였다
이어받은 단계에 앞 세션이 관문을 써도 그대로 들어갔다. 원자성 문제가 아니다 — 파일은
온전한 채로 두 세션의 기록이 섞인다. 그리고 관문마다 누가 적었는지가 없어서 나중에
원장을 읽어도 가릴 수 없었다.

begin 이 세대를 올리고 주인을 적는다. 낮은 세대나 다른 주인의 쓰기는 거절한다.
bin/task.py 의 attempt 와 같은 자리다.

gate 와 end 도 --session 을 받아 적는다. 관문마다 session·generation·at 이 남고
end 는 finishedBy 를 남긴다.

주인이 없는 단계는 그대로 받는다. 세션을 안 쓰는 단일 세션 사용이 깨지지 않는다.
회귀에 그 대조를 넣었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 13:33:10 +09:00
DongHyeonkaandClaude Opus 5 8ca822dc4d feat(scripts): 런 원장을 원자적으로 쓰고 끊긴 자리에서 잇는다
원장을 손으로 써 왔다. 그래서 셋이 없었다. 쓰는 도중에 끊기면 반쪽 파일이 남고, 단계마다
시작·끝이 없어 「돌다 말았다」와 「아직 안 시작했다」가 PENDING 하나로 같아 보이고, 런 도중에
끼어든 일이 어디에도 안 남는다.

bin/task.py 가 이 계약의 초안이다 — flock 을 잡고 임시 파일에 완성한 뒤 os.replace 로 바꾸고
startedAt 부터 지금까지를 누적에 더한다. 그 규약을 runs/ 쪽으로 옮겼다. 새로 만든 것이 아니다.

끊긴 단계를 새 세션이 다시 열면 그 구간을 닫고 잇되, 누적에만 더하지 않고 interruptions 에
따로 적는다. 닫은 구간에는 세션이 죽어 있던 시간이 섞인다 — 「이 단계가 오래 걸렸다」와
「중간에 끊겼다」는 다른 말이다.

라이더에 자리를 만들었다. 이 배치에서 검증 한 작업의 누적 24분 가운데 17분 50초가 라이더였는데
상태 파일에 그 구분이 없었다. 단계가 아니라서 어느 칸에도 안 남던 것이다.

상태 전이를 거절한다 — 이미 있는 런을 덮어쓰기, 단계 겹쳐 열기, 건너뛸 수 없는 단계 건너뛰기,
사유 없는 SKIPPED, 영수증 없는 DONE, 종료 코드 없는 관문.

더한 칸이 있어도 기존 검사기가 그대로 읽는다. 통과한 원장에 새 칸을 더해도 통과하는 것을
확인했다. 손으로 쓴 원장도 계속 유효하고 이 도구는 선택적으로 부른다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 13:23:04 +09:00
DongHyeonka f216fce5e6 Merge branch 'harness/C-verification' into harness/A-integration 2026-09-10 11:37:21 +09:00
DongHyeonkaandClaude Opus 5 14afe94d76 검사: 관문이 어느 대상에 돌았는지 보고, 필수 내용 검사를 전체 훑기에 넣는다
R5 — 원장은 관문의 명령과 종료 코드를 적는데 **그 명령이 어느 대상에 돌았는지는 아무도
안 봤다.** 다른 프로젝트에 돌려 받은 exit 0 을 적어도 통과로 셌다. `_wrong_target()` 이
`docs/<이름>/` 경로와 명령 인자의 프로젝트 이름 둘 다 본다.

  정상 원장 넷            새 error 0 (B 의 document-haness 원장 PASS · warn 3)
  프로젝트 인자를 바꾼 원장  FAIL 「관문이 다른 대상에 돌았다」
  기록 경로를 바꾼 원장     FAIL

**유효 범위를 docstring 에 적었다** — 「다른 프로젝트」만 본다. 같은 프로젝트의 다른 기록에
돌린 관문은 안 잡는다. 기록 단위까지 보려면 관문마다 대상 단위를 계약이 먼저 정해야 한다.

R12 — `check-required-content.py` 를 `OUTPUT_CHECKS` 에 더한다. B-003 이 병합돼 이제 된다.
세 상태 표를 지키는 것을 확인했다 — `ca-tmpl` exit 2 · `keycloak-session-store` exit 0 ·
없는 프로젝트 exit 2.

`OUTPUT CHECKS` 의 error 가 5 → 7 이 된다. **부채가 늘어난 것이 아니라 세는 자리가 늘었다** —
`ca-tmpl` 에 계약이 없다는 한 사실이 `check_evidence` · `check-required-content` ·
`TECH LOG TREES` 세 곳에서 셈된다. 같은 사실을 세 번 세지 않는다.

회귀 `scripts/tests/test_run_target.py` 2건 (123 → 125). **「정상 원장은 그대로 통과한다」
대조를 함께 넣는다** — 무조건 거절로 성공률을 올리는 것이 R-003 이 든 실패다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp9jNbePAmWc5jQwCYhK9v
2026-09-10 11:36:45 +09:00
DongHyeonkaandClaude Opus 5 a5215eda31 feat(studio-save): 그림 쪽에도 다리를 놓는다
문장 쪽에는 놨는데 그림 쪽에는 안 놨다. 화살표를 뒤집어도 관문이 전부 0 이고 경고도 비어
자동 통과했다.

설계가 「SVG 의 스크립트·외부 리소스는 저장 단계의 허용 정책으로 제한한다」고 적었는데 그
정책이 코드로 어디에도 없었다. 화살표 방향은 사람이 봐야 하지만 스크립트가 들어 있는지는
문자열로 판정된다 — 그림 쪽에서 기계가 할 수 있는 유일한 일이라 error 로 막는다.
script·foreignObject·이벤트 처리기·바깥 href·바깥 리소스·@import 여섯이다.
#fragment 와 data: 는 바깥으로 안 나가므로 막지 않는다.

그림이 검토 뒤에 바뀌면 경고로 올린다. 무엇이 바뀌었는지는 기계가 못 말하지만 바뀌었으니
보라는 말할 수 있다. 이것이 들어가면서 읽기 계약이 그림에도 걸린다.

실제 흐름과 개념 설명을 data-figure-kind 로 가른다. 표시가 없으면 error 가 아니라
warning 이다 — 지금 저장소의 그림 272장에 이 표시가 하나도 없고, 없다고 전부 막으면
정상을 막는 쪽으로 넘어간다. 설명용이라 밝힌 그림도 막지 않는다.

검사기를 조일 때마다 정상이 통과하는 대조를 같이 넣는다. 이번 대조군은 평범한 그림,
설명용이라 밝힌 그림, 내부·data 참조, 그리고 이 저장소의 그림 272장 전부다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:32:51 +09:00
DongHyeonkaandClaude Opus 5 f1637ee532 fix(check-preservation): 절을 통째로 지우면 아무것도 안 움직이던 자리
「확인하지 못한 것」 절을 통째로 지운 편집이 경고를 비운 채 지나갔다. 배관 문제가 아니라
실을 것이 없었다 — 지운 절에 보호 구간이 없었고 그 절의 문장이 유보 목록에 없었다.
그래서 「경고가 비면 자동 통과」라는 읽기 계약으로도 그대로 통과한다.

유보 목록이 추정·가능성 쪽에만 몰려 있었다. 한계를 밝히는 말은 대개 「안 했다」 모양인데
그쪽이 얇았다. 두 갈래로 나누고 뒤쪽을 채웠다.

그리고 `##` 절이 통째로 사라지면 그 자체를 경고에 올린다. 무엇이 사라졌는지는 절 제목으로
충분하다.

error 를 늘리지 않았다. 판정하는 것이 아니라 보이게 하는 것이다. 절을 덜어 낸 것인지
한계를 지운 것인지는 근거를 읽어야 안다.

채택된 편집 100쌍을 다시 돌려 막은 쌍이 1 그대로인 것을 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:27:49 +09:00
DongHyeonkaandClaude Opus 5 4fae5b5398 fix(check-preservation): 삭제를 코드가 판정하지 않는다. 날조는 판정한다
사람이 채택한 편집 100쌍 중 9건을 막고 있었다. 막은 것이 전부 자료가 뒷받침하지 않는
덧붙인 이득과 되풀이를 지운 편집이고, 지운 문장 안에 숫자나 인용부호가 있었다는 이유로
막혔다. 반대로 「확인하지 못한 것」 절을 통째로 지운 편집은 통과했다.

부수 문장 삭제와 조건 삭제는 같은 연산이다. 지운 문장에 숫자가 있었는지로는 안 갈린다.
그래서 사라진 것은 warning 으로 내리고 새로 생긴 것만 error 로 둔다. 없던 수치·인용·코드를
더한 것은 날조이고 그것은 코드가 판정할 수 있다.

경계도 고쳤다. `(?![\w.-])` 의 `\w` 가 한글도 낱말 문자로 세어 `500행`·`5개다` 처럼 조사나
명사가 붙으면 보호가 통째로 풀렸다. 한국어에서 숫자는 거의 항상 뭔가가 바로 붙으므로
보호가 가장 필요한 자리에서 가장 안 걸렸다.

경고마다 그 값이 있던 문장을 함께 낸다. 기제와 수치까지만 있으면 검토자가 다시 찾아야 한다.

review-package 의 reviewerNotes 맨 위에 읽는 계약을 넣었다 — gates 가 전부 0 이어도 그것만
으로 통과가 아니고, warnings 가 비어 있어야 자동 통과다.

막은 쌍 9 → 1. 남은 하나는 편집이 인라인 코드를 새로 넣은 쌍이라 규칙이 제대로 도는 것이다.
0 으로 만들려면 그 규칙을 풀어야 하고 그러면 지어낸 식별자가 함께 통과한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:19:11 +09:00
DongHyeonkaandClaude Opus 5 32a369f335 feat(scripts): Studio 저장 어댑터 — 게시를 코드가 막고 무인 저장은 꺼 둔다
서버 계약은 tech-log-backend @ a000f87 의 openapi 와 컨트롤러에서 읽었다.
Idempotency-Key 는 필수이고 200자 이하다. expectedVersion 은 스키마가 minimum 1 로
못 박으므로 빈 값은 「검사 안 함」이 아니라 「반드시 충돌」이다.

멱등 키를 기록 경로에서 유도한다. 프런트는 호출마다 새 uuid 를 만들어서 재시도가 문서를
둘 만든다. 만들기는 경로만으로 키를 만들어 몇 번을 다시 시도해도 문서가 하나이고,
저장은 보낼 내용을 키에 넣는다 — 경로만 쓰면 두 번째 저장이 첫 결과로 조용히 재생된다.

게시 요청을 만드는 코드가 없고, 계획이 게시 경로를 가리키면 거절한다. 지금까지 게시를
막고 있던 것은 스킬 문서의 문장 하나였다.

무인 저장은 꺼져 있다. 서버 권한이 studio:read·studio:write 둘뿐이라 저장 계정이 게시도
할 수 있다. 켜는 것은 studio:publish 가 갈라진 뒤의 판단이지 이 파일의 기본값이 아니다.

저장 뒤 되읽어 견줄 때 문자열 하나로 판정하지 않는다. 정규화가 지운 차이를 통과로 세면
비교 틀이 결함을 만들어 내거나 지운다. 정규화 뒤에만 같아지는 것은 whitespaceOnly 로,
되읽은 값이 보낸 값의 앞부분인 것은 truncated 로 따로 낸다. 못 보는 칸은 사유와 함께
적는다 — 조용히 건너뛰면 「전부 같다」가 「본 것만 같다」를 가린다.

회귀의 첫 줄은 대조군이다. 손대지 않은 쌍이 「같다」로 나오지 않으면 나머지 「잡았다」가
전부 틀 탓이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:13:47 +09:00
DongHyeonka 5472751894 Merge branch 'harness/C-verification' into harness/A-integration 2026-09-10 11:07:35 +09:00
DongHyeonka 03c21c119f Merge branch 'harness/B-implementation' into harness/A-integration 2026-09-10 11:07:35 +09:00
DongHyeonkaandClaude Opus 5 bed1fd933f docs(CLAUDE): 검사기가 「볼 것이 없어서 통과」를 「문제 없음」으로 쓰지 않게 한다
겹침 검사기의 적용 범위를 적는다 — 판정에 쓰는 것은 <rect> 뿐이라
techviz 가 만든 SVG 에서만 유효하다. 손댄 SVG 는 배경 사각형이 안
따라 바뀌어 겹침이 있어도 없다고 답한다.

그리고 관문의 종료 코드를 셋으로 가른다. 「대상이 성립하지 않는다」를
exit 2 로 두어 오타 한 번에 관문이 조용히 무효가 되는 것을 막는다.

__pycache__ 는 .gitignore 에 이미 있는데 이 .pyc 하나만 추적되고 있어
두 세션의 작업 트리를 계속 더럽혔다. 추적에서 뺀다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GksxvQvM6A85xy8viYtWk6
2026-09-10 11:07:35 +09:00
DongHyeonkaandClaude Opus 5 e00c1a2b76 검사: 볼 것이 없어서 통과한 것을 「문제 없음」이라고 쓰지 않는다
R4 · R13 · R6 · R3. 네 가지가 같은 자리를 본다 — 검사기가 대상을 못 찾았을 때 무엇을
내는가.

세 상태를 가른다 (CLAUDE.md 「검사」 절의 표).

  봤고 괜찮다              exit 0   문제 없음 · error 0
  대상이 성립하지 않는다   exit 2   대상이 성립하지 않는다 — <이유>
  볼 것이 아직 없다        exit 0   기록 0건 — 아직 쓴 기록이 없다

R4 — 없는 프로젝트를 주면 여섯 중 넷이 초록을 냈다. `verify-tech-log-tree.py` 는 그것을
「프로젝트 1 · error 0 · PASS」로 셌다 — 오타 한 번이면 검사를 다 돈 것처럼 보인다.
판정은 `techlog.check_targets()` 하나로 모은다. 여섯 곳에 같은 규칙을 따로 쓰면 다음에
하나만 어긋난다. `check_evidence.mjs` 는 이미 exit 2 라 문구만 맞춘다.

R13 — 프로젝트 이름 쪽만 고치면 `--file` 로 오타를 내는 순간 다시 조용히 0건이 된다.
`techlog.check_files()` 로 같은 자리에 둔다. `preview-figure.py` 는 인자가 아예 없을 때
exit 1 을 냈는데 그것도 「대상이 성립하지 않는다」다.

R6 — 두 자리를 함께 고쳐야 했다.
  (a) `verify_projects()` 가 `docs/*/tech-log-studio` 만 훑어 계약 없는 프로젝트가
      목록에서 사라졌다. 기준을 `final/document.md` 로 바꾼다 — SSOT 가 있으면 대상이다.
  (b) `verify-tech-log-tree.py:194` 가 「분해 계약 없음」을 warn 으로 냈다. CLAUDE.md 는
      「계약 미채택도 error 다 — 경고로 두면 옛 스키마로 남아 있는 한 검사를 피한다」고
      적어 두었는데, 경고로 두었더니 실제로 그렇게 됐다.

R3 — `verify-pipeline.py` 가 `check-figure-text.py` 와 `check_evidence.mjs --repo` 를
프로젝트마다 돌린다(`OUTPUT CHECKS`). 게시 전에 돌리라고 적어 둔 검사인데 전체 훑기가
부르지 않아 결함이 있는 채로 PASS 로 보고됐다. `check-required-content.py` 자리는 주석으로
남겨 둔다 — 그 파일이 들어온 뒤에 더한다.

회귀 `scripts/tests/test_no_target.py` 7건 (92 → 99). 「실재하는 경로는 통과한다」 대조를
함께 넣는다 — 무조건 거절로 성공률을 올리는 것이 R-003 이 든 실패다. 판정을 일부러
되돌려 FAILED 가 나는 것을 확인한 뒤 복구했다.

알려진 부채는 그대로 둔다. `verify-pipeline.py` 는 exit 1 이다 — 미준수 런 1건과
`OUTPUT CHECKS` 5건, 그리고 `ca-tmpl` 의 계약 없음이 이제 `TECH LOG TREES` 에서도 보인다.
같은 사실이고 두 번 세지 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp9jNbePAmWc5jQwCYhK9v
2026-09-10 11:07:24 +09:00
DongHyeonkaandClaude Opus 5 c2742a66cc pipeline: 원장 검사기를 문서 계약에 맞추고 터미널 증거 회귀를 잇는다
R2 — `stage-contracts.md` 「관문 요약」이 정본인데 `STAGES` 가 넷을 빠뜨리고 있었다.
S4 `preview-figure.py` · S5 `check_evidence.mjs` · S6 `check_body.mjs`·`check_evidence.mjs` ·
S7 `build-tech-log-tree.py` 를 더한다.

그리고 `style_profile.mjs` 는 관문이 아니라 측정이다 — 문서 계약이 「error 0」을 붙인 것은
`check_prose` 뿐이다(`stage-contracts.md:178`·`:252`). 그런데 원장 검사기가 exit≠0 을 전부
error 로 세어서, 정직하게 exit 1 로 적은 원장은 무조건 실패하고 0 으로 고쳐 적으면 그건
지어낸 것이 된다. `MEASUREMENT_GATES` 로 갈라 **돌았다는 것만 요구하고 종료 코드 0 은
요구하지 않는다.** 대신 `exit` 칸이 없으면 error 다 — 안 돌리고 넘어가는 것을 막는다.
곁증명(`_side_proof`)에도 같은 규칙을 넣는다.

대조군 셋으로 확인했다.
  preview-figure 를 뺀 원장          → FAIL 「관문이 빠졌다」
  style_profile 을 exit 1 로 적은 원장 → PASS
  style_profile 의 exit 칸을 지운 원장 → FAIL 「측정 관문의 종료 코드가 없다」

이 변경으로 `runs/virtualization/2026-09-08-1958/run.json` 이 빨개진다. S6 에서
`check_evidence.mjs` 를 실제로 안 돌린 원장이라 맞는 결과다. 낮춰서 초록으로 만들지 않는다.

R10 — `scripts/terminal-evidence/tests/` 가 문서에 적힌
`unittest discover -s scripts/tests` 범위 밖이고, 직접 `discover` 를 걸면 `render_terminal`
import 경로가 `sys.path` 에 없어 깨진다. 회귀가 초록인데 아무도 안 부르는 상태였다.
`scripts/tests/test_terminal_evidence.py` 가 `load_tests` 로 경로를 얹고 끌어온다 (86 → 92).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wp9jNbePAmWc5jQwCYhK9v
2026-09-10 11:06:59 +09:00
DongHyeonkaandClaude Opus 5 230e1b20cb docs(document-haness): 관문의 종료 코드 — CASE 한 편을 근거까지 잇고 런 원장을 남긴다
이 저장소 자신의 검사 층을 대상으로 삼았다. 파이프 뒤의 $? 를 읽고 검사기 열넷이
전부 --help 를 받는다고 적었다가, 파이프를 걷고 다시 재니 둘이 exit 1 이었던 일.
관찰·원인·조치가 한 사건으로 닫히고 조치가 capture-evidence.py 다.

증거 여덟 건은 전부 그 도구가 수집했다. proves/doesNotProve 로 「이 출력이 뒷받침하는
것」과 「뒷받침하지 못하는 것」을 증거 쪽에 적어 두었다 — 본문이 그 경계를 넘었는지
대조할 것이 생긴다.

함께 올린 Question 은 「검사할 것이 없을 때 관문은 무엇을 내야 하는가」다. 현상은
재현했고 조치가 없어 Case 로 올리지 않았다. 조치 없이 쓰면 관찰만 있고 결과가 없는
글이 된다.

런 원장은 단계마다 서브에이전트를 하나씩 띄운 기록이다. 넷이 실제로 무언가를 잡았다 —
S3 이 「argparse 를 쓰지 않는 셋」이 증거 원문과 어긋나는 것을(넷이다), S1 이 그
뿌리를 SSOT·앵커·증거 메타에서, S5 가 「증거 여섯 개」가 frontmatter 의 다섯과
어긋나는 것을, S6 이 계약의 「한 번밖에 안 써서」가 메타 여덟 건과 어긋나는 것을.

셋 다 한 세션이 일곱 단계를 겸했으면 안 나왔다. 내가 쓴 글을 내가 다시 읽는 것이기
때문이다.

style_profile 은 exit 1 로 그대로 적었다. 관문이 아니라 측정이고, 돌리지 않은 값을
0 으로 적는 것이 이 원장이 막으려는 바로 그것이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:06:50 +09:00
DongHyeonkaandClaude Opus 5 cf3996711f feat(scripts): 형식으로 판정 가능한 것만 코드로 막고 나머지는 검토로 넘긴다
형식 관문 여덟이 확신 승격·수치 조작·화살표 뒤집기·필수 칸 삭제를 하나도 못 막는
것이 재현됐다. 그 가운데 결정적으로 판정 가능한 것을 코드로 옮긴다.

check-required-content.py — 종류가 요구하는 칸이 없거나 비었는지 본다.
audit-records.py 는 평문 칸 안에 마크업이 있는지만 보고 칸이 있는지는 안 센다.
고정 목차·자료 개수·답은 강제하지 않는다. 답이 없는 QUESTION 은 정상이고
「다음 검증」이 빈 것만 결함이다.
대상이 성립하지 않으면(프로젝트 없음·계약 없음) exit 2 로 막고, 계약은 있고 기록이
0건이면 통과시키되 초록으로 두지 않는다. 「봤고 괜찮다」와 「볼 것이 없어서 통과」는
다르다.

check-preservation.py — 윤문 전후를 견준다. 지금 관문 가운데 편집 전후를 보는 것이
하나도 없어 수치를 바꾸거나 유보를 지운 편집이 그대로 통과했다.
사라진 것과 새로 생긴 것을 따로 센다. 새로 생긴 수치는 지어낸 값일 수 있다.
유보 표현이 줄면 내되 옳은지는 판정하지 않는다. 늘어난 것은 세지 않는다.

review-package.py — 아무것도 판정하지 않는다. 판정할 사람이 받을 것을 모은다.
해시·검사기 버전·여기서 실제로 돌린 관문·주장 후보·판정 기준. 종료 코드로 안 걸리는
것은 warnings 로 따로 올린다 — 확신 승격이 딱 그 모양이라 안 실으면 아무도 못 본다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:06:32 +09:00
DongHyeonkaandClaude Opus 5 7fc7c69157 fix(terminal-evidence): 마스킹이 자격증명을 덮으면서 명령을 고치고 있었다
두 가지가 겹쳐 있었다.

줄 맨 앞 앵커 때문에 `curl -H "Authorization: Bearer ..."` 처럼 명령 인자 안에 든
자격증명을 놓쳤다. 터미널 증거에서 Bearer 가 가장 흔히 나오는 자리가 그 명령줄이다.

그리고 키워드 패턴의 값이 `[^\s,;]+` 라 공백까지 먹어 닫는 따옴표를 넘어갔다.
`curl -H "X-Api-Key: TESTONLY-x" https://...` 가
`curl -H "X-Api-Key: [REDACTED] https://...` 가 된다. 다중 -H 에서는 다음 인자의
경계까지 무너진다. 증거에 실린 명령이 실제로 돌린 명령과 달라진다.

값의 끝을 따옴표 앞에서 막되, 감싼 따옴표는 되돌려 놓는다. 문자 집합만 좁히면
`TOKEN="eyJ..."` 가 여는 따옴표에서 막혀 아예 안 가려진다.

함께 메운 것: Authorization/Proxy-Authorization 의 Basic, `curl -u`/`--user`
(사용자 이름은 남긴다 — 어느 계정으로 붙었는지가 증거의 일부다), 그리고 JWT.

회귀는 「가려졌는가」만 묻지 않는다. 원문과 따옴표 수가 같은지 함께 본다.
앞선 회귀가 그것을 안 물어서 이 결함을 통과시켰다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:06:32 +09:00
DongHyeonkaandClaude Opus 5 edd45dfec6 feat(scripts): 종료 코드를 손으로 적을 수 없게 만들고 스킬에 버전을 붙인다
capture-evidence.py 는 명령을 subprocess 로 직접 돌리고 그 프로세스의 반환값을
그대로 메타에 적는다. 종료 코드를 인자로 받지 않으므로 손으로 적을 경로가 없다.
raw 원문과 실행 메타가 같은 이름으로 함께 떨어져 「raw 는 있는데 meta 가 없다」가
구조적으로 안 생긴다.

skill-versions.py 는 스킬 9개의 metadata.version 과 검사기 17개의 내용 해시를
한 장으로 낸다. 통과 판정을 검증기 버전에 묶으려면 묶을 값이 있어야 한다.
버전 칸이 없던 스킬 여덟에 1.0.0 을 붙였다. 산문은 한 줄도 안 바꿨다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
2026-09-10 11:06:07 +09:00
DongHyeonka 43e1aadef0 feat: 가상화 문서들 추가 2026-09-10 08:54:05 +09:00
DongHyeonkaandClaude Opus 5 e9f6a93327 docs(TechLog): 도메인 규칙을 기록에 엮는다
§1.4 로 세운 도메인·비즈니스 규칙을 그것이 실제로 설명하는 기록에 넣었다.

  프로젝트가 문서 게시 파이프라인을 안 타는 이유 → 화면 다섯이 비어 있던 Case
  홈 focus 설정이 FK 없이 사는 설계 → 「열린 질문이 없습니다」 Case
  결정이 자기 화면을 안 갖는 이유 → 목록이 문서 전체를 실어야 했던 Case
  게시가 단계마다 다른 코드로 거절하는 설계 → 화면이 추측 셋을 출력한 Case (반대 사례)
  종류마다 애그리거트와 테이블이 다르다 → 매퍼가 종류를 판정해야 하는 Case
  축을 지우면 연결만 끊고 주제를 지우면 거절하는 이유 → 축 Concept
  개념이 문서 테이블에 얹힌다 → 열세 곳 Case
  화면 상태와 도메인 상태가 원래 갈려 있었다 → 이름을 두 번 바꾼 Case

Case 본문 중앙값 675 → 1,342 자. 검사 넷 전부 통과한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:37:13 +09:00
DongHyeonkaandClaude Opus 5 4769e52e48 docs(TechLog): SSOT 에 도메인과 비즈니스 규칙을 넣는다
이 문서는 결함 카탈로그로만 있었고 이 시스템이 무엇을 하는지가 없었다. 두 저장소의
도메인·유스케이스에서 확인해 §1.4 를 세웠다.

  종류 다섯이 각자 자기 애그리거트와 테이블을 갖는다 (ADR-003) — 개념은 문서 테이블에
  얹히고 concept_detail 에 기준 버전만 따로 둔다. §3 의 열세 건과 §14 의 외래키 없는
  축 표가 여기서 나온다
  화면 상태와 도메인 상태가 다르다 — 질문의 OPEN·INVESTIGATING·PAUSED 가 화면에서 하나로
  접히고 결정의 ACCEPTED 가 ADOPTED 로 보인다
  NextAction 판정 순서 — 서버가 조회 시점에 계산하고 저장하지 않는다. 프론트가 여러
  endpoint 를 조합해 workflow 를 재추론하지 않게 하려는 것이다
  검증·미리보기가 버려지지 않는 산출물인 이유와 그것이 유효한 조건 셋
  게시가 단계마다 다른 코드로 거절하는 이유 — 고칠 것과 다시 검증할 것이 다르다
  저장할 때와 공개할 때의 요구가 다르다 · 문서가 아닌 것은 다른 경로로 공개된다 ·
  주제는 참조가 있으면 안 지우고 축은 연결만 끊는다 · 없는 것을 가리키는 설정을 막는다

SSOT 62,643 → 71,870 자. 인용은 전부 저장소의 javadoc 과 코드에서 옮겼다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:35:11 +09:00
DongHyeonkaandClaude Opus 5 fd221353a3 docs(TechLog): 얇은 Case 열 편과 Question 셋을 저장소 실물로 채운다
SSOT 를 저장소에서 확인해 더 보강하고 그것으로 다시 썼다.

  §7.2   참조 검사 SQL 을 문자열로 조립하는 실제 코드 — 컴파일러가 표 이름도
         컬럼 이름도 보지 않는다는 것이 그 모양에서 드러난다
  §11.2  section-heading-rank 가 미디어 쿼리 값을 먼저 걷어내는 이유(테스트 주석)
  §16.1  질문 삭제는 참조가 둘뿐이라 같은 문제가 덜하다는 대조

Case 열 편과 Question 셋을 다시 썼다. Question 은 사실·가정·미지수·제약을 갈라
채우고 선택지마다 무엇을 감수하는지 적었다 — 오류 코드를 나누면 계약과 반입한 두
저장소가 함께 움직인다는 것처럼.

SSOT 62,643 → 68,319 자. 검사 넷 전부 통과한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:18:37 +09:00
DongHyeonkaandClaude Opus 5 6917ce2420 docs(TechLog): 남은 주제를 다시 쓰고 SSOT 를 저장소 실물로 더 보강한다
주제 11~13 을 다시 쓰고, Case 가 얇은 것들을 저장소에서 실물을 확인해 채웠다.

  §13.4  ManagementClientSafeMessages — 삭제 관련 코드 여섯의 고정 문구와
         원문 메시지를 내보내지 않는 이유(javadoc)
  §16.1  다섯 참조가 전부 DOCUMENT_IN_USE 하나로 나가고, SSOT 가 인용한 영어 문장은
         DeleteDocumentDraftUseCase 안에 남는 진단 메시지라 밖으로 나가지 않는다
  §13.6  romanizeSyllable 실물과 음운 변동을 뺀 이유, 문서 slug 와 같은 정규식을 쓰는 이유
  §15.4  check:types 가 도는 tsconfig 여섯 — app·node·test·recipes·web-worker·service-worker

SSOT 62,643 → 67,526 자. 인용한 코드는 전부 저장소에서 찾아 대조했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:12:37 +09:00
DongHyeonkaandClaude Opus 5 b1653dbba8 docs(TechLog): 주제 7~10 을 다시 쓴다
주소가 게시 시점에 굳어 저장되는 구조, 축 링크를 두 번 옮긴 순서, 한글 slug 가
간헐적으로 보인 두 가지 어긋남을 표로 갈랐다. 화면이 실패를 없음으로 그릴 때 작성
도구에서 왜 더 오래 숨는지, Promise.all 이 거절과 던짐에서 다른 경로를 타는 이유를
채웠다. CSS module 이 왜 전역 규칙에 닿지 않는지, 403 과 404 가 원인을 어떻게
좁혔는지도 적었다.

link-audit.py 를 감사 Case 의 evidence 로 걸어 배정한 증거 하나를 메웠다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:06:20 +09:00
DongHyeonkaandClaude Opus 5 193da20d09 docs(TechLog): Reference 15편의 규칙 표기를 게시된 기록에 맞추고 주제 6 을 다시 쓴다
게시된 Reference 15편이 전부 규칙을 `### N. 제목` 으로 쓰고 적용 조건·예외·예시를 항목으로
쓴다. 내 15편은 규칙을 `**굵게**` 로, 나머지 셋을 문단으로 쓰고 있었다 — Studio 의
rules[]·applyWhen[]·exceptions[]·examples[] 는 배열이라 문단으로 두면 항목이 하나로 접힌다.

  규칙 68개를 `### N. 제목` 으로 바꿨다 (편당 3~7개, 게시된 것은 4~10개)
  적용 조건·예외·예시를 항목으로 갈랐다. 한 항목뿐이던 아홉 편은 조건을 나눠 적었다

주제 6 은 본문을 다시 썼다 — location = 이 정확히 일치하는 경로만 잡아 27개가 얼어붙은
구조, 여덟 곳이 우는 시점을 셋으로 가른 표, digest 를 다시 계산할 때 옛 값을 먼저
재현하는 이유.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 19:01:44 +09:00
DongHyeonkaandClaude Opus 5 53537e37e8 docs(TechLog): 주제 4·5 를 스킬대로 다시 쓴다
what-the-compiler-lets-through  중앙값 2,096 → 2,590 자
  seams-no-test-crosses                    → 3,091 자

bivariance 가 왜 느슨한 판정을 받는지, never 캐스트가 왜 아무것도 요구하지 않는지처럼
「이름을 댔으면 왜 있는지도 댄다」를 채웠다. 네 가지가 각각 무엇을 통과시키고 어디서
드러났는지를 표로 갈랐다. 검사 넷 중 무엇이 SQL 을 실제로 돌리는지도 표로 세웠다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:57:59 +09:00