내 패턴 · 파이프라인 검증 게이트와 영수증 (admit / gate / claim-card)

한 줄 요약

AI가 만든 결과물을 지식 창고에 넣기 전에 검문소(게이트) 가 규칙을 전수검사하고, 통과·거부·실패 사유를 전부 영수증으로 남기는 구조다. 왜 배우나: AI가 근거 없이 지어내는 것(자유생성)을 “잘 부탁한다”는 말이 아니라 코드로 물리적으로 막는 방법을 배워야, 믿을 수 있는 자동 파이프라인을 직접 설계할 수 있기 때문이다.

그림

flowchart TB
    P["프롬프트: AGENTS.md 규칙<br/>근거강제·창작금지"] --> W["워커: frontmatter+본문 생성<br/>임시 폴더에 스테이징"]
    W --> A{"admit.mjs 검문소<br/>계약 전수검사"}
    A -->|통과 exit0| WIKI["wiki/도메인/slug.md 입고<br/>배타생성으로 동시충돌 차단"]
    A -->|실패 exit1| R[거부 사유 JSON 출력]
    R --> W2[워커 스스로 고쳐 재시도 1회]
    W2 --> A
    A -->|2회 실패| Q["quarantine 격리실<br/>사람·오케스트레이터가 직접 확인"]
    WIKI --> G{gate.mjs 마일스톤 관문}
    G -->|미통과| X["exit2 — 다음 단계 물리 거부"]
    G -->|통과| GC["golden-check.mjs<br/>검색 품질 회귀검증"]
    GC --> LOG["작업 로그 샤드<br/>+ git 커밋 영수증"]

쉽게 풀기

이 패턴은 공항 입국심사라고 생각하면 가장 쉽다.

  1. 여권 규칙을 미리 알려준다 (프롬프트). AI 워커에게 일을 시키기 전에 “근거 없는 주장 금지, frontmatter(서류) 없는 파일 저장 금지, 만든 사람과 검사하는 사람은 달라야 함” 같은 규칙을 먼저 주입한다. 그래서 AI는 처음부터 규칙에 맞춰 결과물을 만든다.

  2. 입국심사대를 통과해야 들어온다 (admit 게이트). AI가 만든 파일은 바로 지식 창고(wiki)로 들어가지 못한다. admit.mjs라는 검문소가 여권(frontmatter)을 한 줄 한 줄 검사한다. 규칙에 다 맞으면 입국(입고)시키고, 한 군데라도 틀리면 입국장에 못 들어온다. 이때 파일을 지우지 않고 “어디가 틀렸는지” 사유를 적어준다.

  3. 틀리면 본인이 고쳐서 다시 온다. 거부당한 워커는 사유를 읽고 자기 출력을 고쳐 한 번 더 도전한다. 그래도 또 틀리면 별도 격리실(quarantine) 로 보내져, 그제서야 사람(또는 오케스트레이터 Claude)이 직접 들여다본다. 즉 “두 번 틀린 것만” 사람이 본다 — 사람의 시간을 아끼는 장치다.

  4. 단계마다 관문이 있다 (gate 마일스톤). 입국까지 끝나도 다음 단계로 바로 못 간다. “1단계 검증이 통과(pass=true)“라고 도장 찍힌 상태 파일이 없으면, 다음 단계 프로그램이 아예 exit 2로 멈춘다. 신호등이 초록불일 때만 건너는 것과 같다.

  5. 모든 일에 영수증을 남긴다. 통과/거부, 실패 이유, 어떤 결정을 왜 했는지를 로그와 git 커밋 꼬리표(trailer)에 기록한다. 나중에 “왜 이게 들어왔지?”를 추적할 수 있다.

핵심은 이 검문소가 사람이나 AI의 판단이 아니라 코드라는 점이다. 같은 입력이면 항상 같은 판정이 나온다(결정적). 그래서 “근거 인용이 없으면 자동으로 강도(strength)를 약함으로 강등”하고, “도메인 폴더와 서류가 안 맞으면 입고 거부” 같은 규칙이 빈틈없이 작동한다.

핵심 정리

이 패턴은 4개의 하위 구조로 이뤄진다. 표는 무엇을 검사하는지의 뼈대만 담고, 세부 규칙은 콜아웃으로 분리한다.

(1) admit — frontmatter 입고 계약 (필수 필드 핵심)

필드검사 내용틀리면
slug파일명과 일치 + wiki 전역 유일FAIL
domain11종 화이트리스트 + 폴더 위치와 일치FAIL
confidence0 < x ≤ 1 범위FAIL
sourcesstub/map/_ops 외에는 1개 이상 필수FAIL

admit 계약 세부 규칙

  • 필수 필드: slug title kind domain lang created/updated/last_confirmed/valid_from(날짜는 YYYY-MM 또는 YYYY-MM-DD만) valid_to(키 존재 필수·값은 null 가능) confidence maturity(확립/신흥/논쟁).
  • kind: concept / summary / model-card / map / curriculum / stub / decision / runbook / retrospective 중 하나.
  • 관계(relations) 타입 10종(+authored_by): summarizes uses depends_on part_of example_of evolved_into supersedes contradicts compares references.
  • 위험 관계 supersedes/contradictsevidence 없으면 FAIL. summarizes는 kind=summary 전용, compares는 kind=map 전용.
  • sourcesraw 경로는 실제 파일 존재 여부까지 검사.

(2) claim-card — 영수증형 주장 카드

필드검사 내용효과
strength강(RCT)/중(관찰)/약(사례)인용 없으면 자동 약
source_slug원본 자료 역참조출처 추적
page_hint페이지·문단 위치null이면 강도 하향

claim-card 나머지 필드

slug(={출처}__{토픽}__{문서id}__{문장요약}) · claim(한 카드=한 주장) · counter(반대 근거, 없으면 명시) · extracted_at(추출 시각) · needs_review(검증 필요 플래그).

(3) gate — 마일스톤 물리 게이트

필드검사 내용효과
passtrue 여부false면 후속 단계 exit 2
approved_by승인 주체(orchestrator/manual)책임 추적
at승인 시각(ISO)시점 기록

(4) git trailer — 커밋 영수증 (모두 선택, 결정 근거 보존용)

  • Constraint: — 결정을 제약한 조건
  • Rejected: — 검토 후 버린 대안 / 거부 사유
  • Confidence: — high / medium / low
  • Scope-risk: — narrow / moderate / broad
  • Not-tested: — 알려진 검증 공백

실제 예시

검문소의 판정 핵심부 — slug 검사와 배타 입고

// /mnt/d/akh2/pipeline/admit.mjs (L77~86, L126~141 발췌)
// 3) slug = 파일명, 전역 유일
const base = path.basename(file, '.md');
if (fm.slug && fm.slug !== base) failures.push({ check: 'slug-filename', slug: fm.slug, filename: base });
const known = new Set();
for (const d of fs.readdirSync(path.join(ROOT, 'wiki'))) {
  const dd = path.join(ROOT, 'wiki', d);
  if (!fs.statSync(dd).isDirectory()) continue;
  for (const f of fs.readdirSync(dd)) if (f.endsWith('.md') && !f.startsWith('_index')) known.add(f.replace(/\.md$/, ''));
}
if (fm.slug && known.has(fm.slug)) failures.push({ check: 'slug-unique', msg: `이미 존재: ${fm.slug}` });
 
// --- 판정 ---
const result = { pass: failures.length === 0, slug: fm.slug, failures, warnings, words };
if (result.pass && !dry) {
  const dest = path.join(ROOT, 'wiki', fm.domain, `${fm.slug}.md`);
  fs.mkdirSync(path.dirname(dest), { recursive: true });
  try {
    fs.copyFileSync(file, dest, fs.constants.COPYFILE_EXCL); // 배타 생성 — 동시 입고 race 차단
  } catch (e) {
    if (e.code === 'EEXIST') { console.log(JSON.stringify({ pass: false, slug: fm.slug, failures: [{ check: 'slug-unique', msg: `입고 직전 충돌: ${fm.slug}` }] })); process.exit(1); }
    throw e;
  }
  fs.unlinkSync(file);
  result.admitted = path.relative(ROOT, dest);
}
console.log(JSON.stringify(result));
process.exit(result.pass ? 0 : 1);

마일스톤 관문의 물리 거부 — exit 2

// /mnt/d/akh2/pipeline/gate.mjs (L19~32 발췌)
export function requireMilestone(name) {
  const st = milestoneState(name);
  if (!st || st.pass !== true) {
    console.error(`[gate] 마일스톤 '${name}' 미통과 — 실행 거부. 통과 후 재시도: node pipeline/gate.mjs pass ${name}`);
    process.exit(2);
  }
  return st;
}
export function completeMilestone(name, stats = {}, approvedBy = 'orchestrator') {
  const rec = { pass: true, stats, approved_by: approvedBy, at: new Date().toISOString() };
  fs.writeFileSync(path.join(DIR, `${name}.json`), JSON.stringify(rec, null, 2));
  return rec;
}

산출물 영수증 — 인용 없는 주장이 자동으로 강등된 모습

# /mnt/d/human-token-workflow/04_claims/academic__productivity-rct__2105.02782__...md (frontmatter 발췌)
slug: academic__productivity-rct__2105.02782__in-the-following-proposition-2-...
claim: "In the following proposition (2) we show why these findings hold for the TS model..."
strength:           # page_hint=null 이므로 강도 자동 하향
page_hint: null
counter: []
needs_review: true

산출물 영수증 — git 커밋 꼬리표(trailer)

# /home/seunghyeong/.claude/plugins/marketplaces/omc/skills/omc-reference/SKILL.md (L130~141 발췌)
feat(docs): reduce always-loaded OMC instruction footprint
 
Move reference-only orchestration content into a native Claude skill so
session-start guidance stays small while detailed OMC reference remains available.
 
Constraint: Preserve CLAUDE.md marker-based installation flow
Rejected: Sync all built-in skills in legacy install | broader behavior change than issue requires
Confidence: high
Scope-risk: narrow
Not-tested: End-to-end plugin marketplace install in a fresh Claude profile

직접 만들 때 — 복붙 가능한 최소 게이트

// my-admit.mjs — 최소 입고 게이트
import fs from 'node:fs'; import path from 'node:path';
const ALLOWED_DOMAIN = new Set(['concept','model-card','runbook']);
const file = process.argv[2];
const text = fs.readFileSync(file,'utf8');
const fm = JSON.parse(text.split('\n---\n')[0].replace(/^---\n/,'')); // 예시: 간이 파서
const failures = [];
// 1) 필수 필드
for (const k of ['slug','title','domain','confidence']) if (!fm[k]) failures.push({check:'required',field:k});
// 2) 화이트리스트
if (fm.domain && !ALLOWED_DOMAIN.has(fm.domain)) failures.push({check:'domain',got:fm.domain});
// 3) slug=파일명 & 전역유일
const base = path.basename(file,'.md');
if (fm.slug !== base) failures.push({check:'slug-filename'});
const dest = path.join('wiki', fm.domain, `${fm.slug}.md`);
if (fs.existsSync(dest)) failures.push({check:'slug-unique'});
// 4) 근거 강제
if (!fm.sources || fm.sources.length === 0) failures.push({check:'sources-required'});
const pass = failures.length === 0;
if (pass) { fs.mkdirSync(path.dirname(dest),{recursive:true});
  fs.copyFileSync(file, dest, fs.constants.COPYFILE_EXCL); fs.unlinkSync(file); }
console.log(JSON.stringify({pass, failures}));   // ← 영수증 출력
process.exit(pass ? 0 : 1);

직접 만들 때 빠뜨리면 안 되는 것

  • 결정적 검사: 게이트는 LLM이 아니라 코드. 같은 입력 → 같은 판정.
  • slug 전역 유일 + 파일명 일치 + 배타 생성(COPYFILE_EXCL)으로 동시 입고 충돌 차단.
  • 도메인=폴더 일치 강제(frontmatter는 검증용, 폴더가 단일 진실원천).
  • 관계/타입 화이트리스트 + 위험 관계(supersedes/contradicts)는 evidence 필수.
  • 근거 없으면 강등: sources/page_hint 없으면 strength=약 또는 FAIL.
  • 2회 실패 → quarantine 격리 + 사유 보존(자유생성 차단의 마지막 방어선).
  • 영수증 3종: ① 실패 로그(failed.jsonl+FAILED_TO_FETCH.md) ② 작업 샤드 로그(phase/input/decision/output/gate/next_action) ③ git trailer.
  • 중복 방지: 입력 자료는 SHA256(manifest.jsonl)로 대조.
  • 회귀 검증: golden-queries에 정답셋 박제 → 변경 후 신구 비교.
  • 물리 게이트: 선행 마일스톤 미통과 시 후속을 exit 2로 거부.
  • writer ≠ reviewer: 자체 승인 금지.

요약 & 셀프체크

  • 게이트는 AI 출력 뒤에 붙는 결정적 코드 검문소다 — 통과해야만 지식 창고에 들어간다.
  • 모든 통과/거부/실패/결정은 영수증(로그·git trailer) 으로 남아 추적 가능하고, 근거 없는 주장은 자동 강등되거나 거부된다.
  • 오케스트레이터는 워커의 방대한 산출물을 다 읽지 않고 PASS/FAIL과 영수증만 신뢰 신호로 읽어 컨텍스트를 아끼며, 2회 실패한 격리(quarantine)만 직접 본다.

스스로 답해보기:

  1. 같은 파일을 두 워커가 동시에 입고하려 할 때, 충돌을 막는 장치는 무엇인가? (힌트: COPYFILE_EXCL)
  2. page_hintnull이면 claim-card의 strength는 왜 자동으로 낮아지는가?
  3. 마일스톤이 통과되지 않은 상태에서 다음 단계가 실행되면 어떤 일이 일어나는가? (힌트: exit 2)

연결

MINE_개요 · _분석축_루브릭

근거 파일

  • /mnt/d/akh2/pipeline/admit.mjs — frontmatter 입고 계약 검증, PASS 입고/FAIL 거부, COPYFILE_EXCL race 차단
  • /mnt/d/akh2/pipeline/gate.mjs — 마일스톤 물리 게이트(exit 2), requireMilestone/completeMilestone
  • /mnt/d/akh2/pipeline/golden-queries.json — 검색 회귀 골든셋(쿼리 20종, captured_at 2026-06-11)
  • /mnt/d/akh2/CLAUDE.md — 2회 실패→quarantine, 로그 채널 물리분리, 폴더=도메인 SSoT, M3 2층 검증
  • /mnt/d/akh2/pipeline/ — golden-check.mjs/validate.mjs/runner.mjs 등 파이프라인 도구 목록(실존 확인)
  • /mnt/d/human-token-workflow/AGENTS.md — 근거강제·창작금지, writer≠reviewer, failed.jsonl/FAILED_TO_FETCH.md, manifest SHA256
  • /mnt/d/human-token-workflow/00_framework/accounting-model.md — 인용 페이지 역참조·원문보존·이중회계 스키마
  • /mnt/d/human-token-workflow/04_claims/academic__productivity-rct__2105.02782__...md — 실제 claim-card(page_hint=null→strength=약)
  • /mnt/d/human-token-workflow/logs/FAILED_TO_FETCH.md, logs/claim_mine.jsonl, 01_sources/internal-dd/manifest.jsonl — 실패/작업/중복방지 영수증
  • /mnt/d/ai-knowledge-hub/_logs/agent-08-harness-engineering/decision-log.md — 샤드별 작업 영수증(phase/input/decision/output/gate/next_action)
  • /mnt/d/ai-knowledge-hub/_logs/gate-2.json{pass, failures, retry_targets} 게이트 상태
  • /home/seunghyeong/.claude/plugins/marketplaces/omc/skills/omc-reference/SKILL.md (§Commit Protocol) — git trailer 영수증 형식·예시

Codex 교차검증

(원문 노트에는 별도 Codex 교차검증 섹션이 없었음 — 재작성 시 신규 사실을 추가하지 않고 보존 차원에서 비워 둠. 추후 검증 결과가 생기면 이 콜아웃에 누적한다.)