Files
artex/MAINTAINING.md
T
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

24 KiB

상류 동기화와 번역 드리프트 방지 (메인테이너 안내)

한국어 · English

이 문서는 메인테이너가 원본 저장소 Autumn-27/ARTEX의 변경을 따라잡으면서 한국어 현지화를 유지하는 절차를 정리한 것입니다. 기여 범위·법적 책임· 현지화 방침은 CONTRIBUTING.md에, 사용자용 안내는 README.md에 있으므로, 이 문서는 그 방침을 실제로 어떻게 집행하는지에만 집중합니다.

현지화의 핵심 목표는 한 문장으로 요약됩니다. 원본의 판단 성능을 그대로 보존하면서 사용자에게 보이는 산출물만 한국어로 바꾸는 것입니다. 상류가 갱신될 때마다 이 경계가 흐트러지기 쉬우므로, 아래 절차와 검사로 번역 드리프트를 막습니다.


1. 현지화 구조 한눈에 보기

이 저장소는 상류 ARTEX 를 포크해서 그 이력 위에 한국어 현지화 커밋을 쌓은 구조입니다. 상류 main 의 모든 커밋이 이 저장소의 이력에 포함되어 있고, 그 위에 현지화 커밋이 더해져 있습니다. 따라서 상류 변경을 가져오는 일은 "상류 main 과의 차이를 확인하고, 보존할 것과 번역할 것을 가려서 반영하는 일"이 됩니다.

산출물은 세 갈래로 나뉩니다.

  • 원문을 그대로 두는 자산(아래 2절). 번역하면 성능이나 상류 대조가 깨집니다.
  • 코드에 고정된 출력 언어 강제. agent/prompt.go 의 langDirective() 가 각 역할의 system 프롬프트 끝에 "사용자 노출 출력은 한국어로 작성하라"는 지시를 덧붙입니다.
  • 한국어로 번역하는 사용자 노출 문자열. UI 는 web/messages/ko.json 에, 서버의 사용자 응답 문구는 각 Go 파일의 명명 상수에 둡니다.

2. 원문을 보존하는 자산 (번역 금지)

다음 자산은 번역하지 않고 원문(중국어 또는 영어)을 유지합니다. 상류 변경이 이 자산에 닿으면 번역 없이 그대로 반영합니다.

  • 에이전트 내부 추론 프롬프트(두뇌 본문). agent/promptcatalog.go 와 DB 시드 agent_prompts 에 있는 행동 지침 본문입니다. 원문(중국어)으로 벤치마크된 동작을 유지해야 하므로 번역하면 판단에 드리프트가 생깁니다.
  • 표시와 에이전트 입력을 겸하는 문자열. 활동 타임라인에 보이면서 동시에 플래너·리포터의 입력 컨텍스트로 되먹여지는 일부 문구(작업 중단 사유, 가로채기 차단 메시지, 트래픽 증거 헬퍼 등)는 하나의 레코드가 두 용도를 겸하므로 원문을 보존합니다. 판정 근거는 work/DECISIONS-FOR-JIWOO.md 의 "두뇌 경계 기록"에 사안별로 적혀 있습니다.
  • 원본 중국어 문서·문자열. 문서는 README.zh.md, UI 문자열은 web/messages/zh.json 에 원문을 그대로 남겨 상류 변경과 대조하기 쉽게 합니다. 한국어 번역은 web/messages/ko.json 에만 채웁니다.
  • 명령·페이로드·코드·URL·식별자·로그 원문. 분석에 필요한 원본이므로 번역하지 않습니다. Go 코드 주석도 우선순위가 가장 낮아 상류 대조가 끝나는 시점까지 원문을 둡니다.

3. 상류 추적 베이스

상류 리모트가 다음과 같이 설정되어 있어야 합니다. 없다면 추가합니다.

git remote add upstream https://github.com/Autumn-27/ARTEX
git remote -v   # upstream 이 보이는지 확인

현재 현지화가 반영을 마친 상류 베이스 커밋은 다음과 같습니다.

  • 베이스 = d003372 (상류 main, 2026-10-03, PR #189 fix/sse-same-origin 병합).

이 값은 "이 커밋까지의 상류 변경은 전부 이 저장소에 녹아 있다"는 뜻입니다. 상류 변경을 새로 반영할 때마다 이 베이스를 7절의 방법으로 갱신합니다.


4. 상류 변경을 가져오는 절차

4.1 상류를 내려받고 차이를 확인합니다

git fetch upstream
git rev-list --count d003372..upstream/main        # 미반영 상류 커밋 수
git log --oneline d003372..upstream/main           # 미반영 커밋 목록

git fetch 는 상류의 원격 추적 브랜치만 갱신하므로 작업 트리와 HEAD 에는 영향을 주지 않습니다. 미반영 커밋이 0 이면 상류와 동기화된 상태이므로 더 할 일이 없습니다.

4.2 변경 파일을 분류합니다

미반영 커밋이 어떤 파일을 건드렸는지 보고, 2절의 보존 자산과 번역 대상으로 나눕니다.

git log --name-status --oneline d003372..upstream/main

분류 기준은 다음과 같습니다.

  • agent/promptcatalog.go·agent_prompts 시드, 그리고 2절의 표시 겸 입력 문자열이 바뀌었다면 → 번역 없이 그대로 반영합니다.
  • Go 백엔드 로직(db/·llmrec/·server/ 등)이 바뀌었다면 → 로직은 그대로 반영하되, 새로 생긴 사용자 응답 문구(writeErr 등)가 있는지 확인해서 한국어 상수로 번역합니다.
  • UI(web/src/**)가 바뀌어 새 화면 문자열이 생겼다면 → 하드코딩하지 말고 web/messages/zh.json(원문)과 web/messages/ko.json(번역)에 같은 키로 추가합니다.
  • 탐지 규칙이 고정한 상류 지표(enrich/enrich.go 의 프로버 User-Agent, selfupdate/ 의 자가 갱신 User-Agent, guard/guard.go 의 감사 마커, db/db.go 의 파괴명령 deny 목록, cmd/artex/main.go 의 기본 리슨·기록 프록시 포트)가 바뀌었다면 → detections/ 의 Sigma·Suricata 규칙과 ATT&CK 레이어, 그리고 detections/indicators/artex_indicators.csv 의 값도 새 값으로 맞춥니다. 이 지표는 번역 대상이 아니라 탐지의 근거라, 상류가 값을 바꾸면 규칙이 조용히 낡습니다. 5.4 의 지표 일치 테스트가 이 어긋남을 자동으로 잡습니다.

4.3 반영합니다

기능 단위로 병합하거나 선별 반영한 뒤, 4.2 에서 가려낸 새 문자열을 한국어로 번역합니다. 병합 과정에서 ko.json·zh.json 의 키가 어긋나거나 사용자 노출 자리에 원문이 새어 들어오기 쉬우므로, 반영 직후 반드시 5절의 검사를 돌립니다.

예시(2026-10-05 기준 미반영 커밋). git fetch upstream 결과 상류 main 이 b55ceb1 로 앞서 있고, 베이스 d003372 대비 커밋 2개(86729b6 모델 폴백 승인 토큰 계량 기능 + 병합 커밋 b55ceb1)가 미반영입니다. 이 커밋은 db/llm_usage.go·llmrec/llmrec.go· server/intercept.go·server/server.go 같은 Go 로직과 web/src/app/(main)/system/intercept/page.tsx· web/src/lib/api.ts·web/src/lib/mock/handler.ts·web/src/lib/types.ts 를 건드립니다. 따라서 메인테이너는 Go 로직은 그대로 반영하고, intercept 설정 페이지에 새로 생긴 화면 문자열만 ko.json·zh.json 키로 추출·번역하면 됩니다. (이 두 커밋은 이 문서를 쓴 시점에는 아직 반영하지 않았으므로 베이스는 d003372 로 둡니다.)


5. 번역 대칭과 드리프트 검사

상류 반영이나 번역 작업 뒤에 아래 세 가지를 확인합니다.

5.1 ko ↔ zh 메시지 대칭과 사용자 노출 CJK

ko.json 과 zh.json 의 키가 정확히 같고, ko.json 값에 중국어 한자가 남아 있지 않아야 합니다. 아래 스크립트가 세 수치를 출력합니다.

python3 - <<'PY'
import json, re
ko = json.load(open('web/messages/ko.json'))
zh = json.load(open('web/messages/zh.json'))
def flatten(d, p=''):
    out = {}
    if isinstance(d, dict):
        for k, v in d.items(): out.update(flatten(v, p + '/' + k))
    elif isinstance(d, list):
        for i, v in enumerate(d): out.update(flatten(v, p + '/' + str(i)))
    else: out[p] = d
    return out
fk, fz = flatten(ko), flatten(zh)
han = re.compile(r'[㐀-鿿]')
print('ko leaf keys :', len(fk))
print('zh leaf keys :', len(fz))
print('key symdiff  :', len(set(fk) ^ set(fz)))        # 0 이어야 함
print('ko vals w/CJK:', sum(1 for v in fk.values() if isinstance(v, str) and han.search(v)))  # 0 이어야 함
PY

기준값(2026-10-05): ko leaf keys = 2950, zh leaf keys = 2950, key symdiff = 0, ko vals w/CJK = 0. 키 수는 상류 반영으로 늘 수 있지만, ko 와 zh 는 항상 같아야 하고 key symdiff 와 ko vals w/CJK 는 항상 0 이어야 합니다.

5.2 두뇌 자산의 원문 보존 확인

두뇌 본문은 중국어 원문을 유지하므로, 아래 검사에서 한자 라인 수가 0 으로 떨어지면 오히려 두뇌가 실수로 번역돼 오염됐다는 신호입니다.

python3 -c "import re; han=re.compile(r'[㐀-鿿]'); t=open('agent/promptcatalog.go').read(); print('promptcatalog.go CJK lines =', sum(1 for l in t.splitlines() if han.search(l)))"

기준값(2026-10-05): promptcatalog.go CJK lines = 70. 이 수가 크게 줄면 두뇌 본문이 번역됐는지 확인합니다.

5.3 빌드 산출물에 원문이 새지 않는지

UI 를 정적으로 내보낸 뒤 프리렌더 HTML 에 중국어가 보이면 번역 누락입니다.

cd web && npm ci && NEXT_EXPORT=1 npm run build   # out/ 생성
# out/**/*.html 에서 가시 텍스트의 중국어 한자가 0 인지 확인

5.4 탐지 지표가 상류 소스와 여전히 맞는지

detections/ 의 규칙은 상류가 실제로 내보내는 문자열(프로버 User-Agent·자가 갱신 User-Agent·감사 마커·파괴명령 deny 목록)에 근거합니다. 상류 재동기화가 이 값을 바꾸면 번역 검사는 전부 통과하는데 배포된 규칙만 조용히 매칭을 멈춥니다. 아래 테스트가 각 지표가 상류 소스와 규칙 양쪽에 여전히 있는지 양방향으로 확인하므로, 재동기화 뒤에 함께 돌립니다.

detections/tests/indicators/run.sh   # Docker 로 격리 실행, RESULT: PASS 이면 일치

실패하면 어느 지표가 어긋났는지와 그 방향(상류 소스가 바뀌었는지, 규칙이 바뀌었는지)을 출력하므로, 4.2 의 마지막 분류 기준대로 규칙·레이어를 새 값에 맞춥니다. 이 테스트는 저장소 CI(.github/workflows/detections.yml)에서도 규칙 트리나 위 상류 소스 파일이 바뀐 푸시·PR 마다 자동으로 돌아, 재동기화 드리프트를 머지 게이트에서 잡습니다.

새 지표를 추가하면서 새 상류 소스 파일을 고정했다면(예: cmd/artex/main.go 의 포트 지표를 넣을 때처럼), 그 파일을 반드시 위 워크플로의 push·pull_request paths 필터에도 추가합니다. 빠뜨리면 그 소스만 바꾼 PR 은 지표 테스트를 발화시키지 못해, 드리프트가 머지 게이트를 조용히 통과합니다. 이 동기화 자체도 지표 테스트가 자동으로 확인합니다(다섯 번째 검사 "CI triggers this test when any pinned source changes"): 테스트가 읽는 모든 비 detections/ 소스가 양쪽 paths 블록에 열거돼 있지 않으면 테스트가 실패하므로, 소스 고정과 CI 발화 조건이 어긋난 채로 머지되지 않습니다.

5.5 탐지 테스트 도구 핀을 올릴 때

탐지 테스트는 sigma-cli·SigmaHQ 검증기 플러그인(pySigma-validators-sigmahq)·Suricata 이미지를 고정 버전으로 돌립니다(각 run.sh 의 기본값, 환경 변수로 덮어쓰기 가능). 이 핀을 올리면 상류 소스가 아니라 도구 쪽 드리프트가 생길 수 있습니다. 특히 SigmaHQ 검증기는 판올림마다 새 관례 검사를 추가하므로, detections/tests/sigma_lint/run.sh 가 새 이슈를 빨갛게 드러낼 수 있습니다. 그때는 규칙을 새 관례에 맞추거나, 단독 규칙 세트에 맞지 않는 관례라면 그 사유를 적어 detections/tests/sigma_lint/validators.yml 의 제외 목록에 추가합니다. 백엔드 플러그인이 지원을 바꾸면 sigma_backends 테스트가 같은 신호를 줍니다.


6. 빌드와 테스트로 마무리 검증

반영·번역 뒤에는 CONTRIBUTING.md 의 개발 환경 절차대로 백엔드와 프런트엔드를 검증합니다. 로컬에 Go 가 없으면 Docker 로 동일하게 돌릴 수 있습니다.

docker run --rm -v "$PWD":/src -w /src \
  -v artexko-gomod:/go/pkg/mod -v artexko-gocache:/root/.cache/go-build \
  golang:1.26 sh -c 'go build ./... && go vet ./... && go test ./... -count=1'

사용자 노출 문구를 번역할 때는 그 문구를 단언하는 회귀 테스트(*_localized_test.go)를 함께 두어, 나중에 상류 변경이 다시 중국어를 끌어와도 테스트가 잡게 합니다. 번역 검증은 반드시 역량 있는(프런티어급) 모델로 합니다. 저가·소형 모델은 출력이 원문으로 되돌아갈 수 있어 번역 적용 여부를 그 출력만으로 판단하면 안 됩니다.


7. 베이스 갱신 기록

상류 변경을 반영하고 검증까지 마쳤다면, 이 문서 3절의 베이스 커밋 값을 새 상류 커밋으로 갱신하고 그 변경을 같은 커밋 또는 뒤따르는 커밋에 포함합니다. 이렇게 해두면 다음 메인테이너가 "어디까지 반영됐는지"를 이 문서 한 곳에서 확인할 수 있습니다.

커밋 메시지는 CONTRIBUTING.md 의 커밋 메시지 규칙을 따릅니다. 예를 들어 상류 동기화 커밋은 다음과 같이 적습니다.

chore(upstream): 상류 d003372..b55ceb1 반영 (intercept 토큰 계량) + 신규 UI 문자열 번역

8. 검토·검증 수칙 (흔한 함정)

상류 반영·번역·문서 보강을 점검할 때 메인테이너가 반복해서 빠지는 함정 두 가지를 적어 둡니다. 둘 다 "검사 방법 자체가 틀려서 멀쩡한 것을 깨졌다고 오인하는" 경우라, 불필요한 되돌림을 막으려고 수칙으로 고정합니다.

8.1 저장소 CI 상태는 저장소를 지정해서 확인합니다

이 저장소는 상류 ARTEX 의 포크라서, 로컬 git remote 에 origin(jiwoochris/artex-ko)과 upstream(Autumn-27/ARTEX)이 함께 등록되어 있습니다(3절 참조). 이 상태에서 gh 명령에 저장소를 지정하지 않으면, gh 가 상류 저장소를 기본값으로 골라 우리 워크플로가 없는 상류의 실행 결과를 보여 줍니다. 그러면 상류 CI 가 초록인 것을 보고 우리 CI 가 통과했다고 착각하거나, 우리 워크플로(ci.yml·detections.yml)를 "HTTP 404 … not found" 로 잘못 판단할 수 있습니다.

그래서 CI 를 확인할 때는 항상 저장소를 명시합니다.

gh run list -R jiwoochris/artex-ko --workflow ci.yml --limit 5
gh run list -R jiwoochris/artex-ko --workflow detections.yml --limit 5

한 번 설정해 두면 -R 를 생략해도 우리 저장소를 기본으로 보도록 바꿀 수 있습니다. 다만 이 설정은 로컬 gh 설정이라 저장소에 커밋되지 않으므로, 새 머신이나 새 체크아웃에서는 다시 지정해야 합니다.

gh repo set-default jiwoochris/artex-ko
gh repo set-default --view   # jiwoochris/artex-ko 가 보이는지 확인

8.2 문서의 외부 링크는 브라우저처럼 GET 으로 확인합니다

방어 가이드(docs/defense-ko.md·defense-en.md)의 7절은 국내 공식 채널(boho.or.kr·fsec.or.kr·pipc.go.kr)의 링크를 싣습니다. 이 링크가 살아 있는지 확인할 때 curl -I(HEAD 요청)나 기본 User-Agent 로만 확인하면 멀쩡한 링크를 깨진 것으로 오인합니다. 국내 공공·보안 기관 사이트는 다음 세 가지 이유로 단순 확인을 거부하기 때문입니다.

  • HEAD 요청을 거부합니다. 예를 들어 fsec.or.kr 은 curl -I(HEAD)에 400 을 돌려줍니다.
  • 기본 curl User-Agent 를 차단합니다. fsec.or.kr 과 pipc.go.kr 은 기본 UA 로 보낸 GET 요청에도 400 을 돌려줍니다(브라우저 UA 로 보내면 200).
  • 다른 주소로 리다이렉트합니다. pipc.go.kr 은 www.pipc.go.kr 에서 pipc.go.kr/np/ 로 두 번 리다이렉트하므로, 리다이렉트를 따라가지 않으면 최종 상태를 놓칩니다.

따라서 링크 확인은 브라우저 User-Agent 로, GET 으로, 리다이렉트를 따라가며 합니다.

UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0 Safari/537.36'
for u in https://www.boho.or.kr https://www.fsec.or.kr https://www.pipc.go.kr; do
  curl -sS -L -A "$UA" -o /dev/null -w "$u -> %{http_code} %{url_effective}\n" "$u"
done

최종 상태 코드가 200 이면 링크는 유효합니다. 상태 코드가 400·403 으로 나오면 링크가 깨진 것이 아니라 확인 방법이 서버의 접근 정책에 막힌 것은 아닌지 먼저 의심하고, HEAD·기본 UA·리다이렉트 미추적 같은 요인을 하나씩 제거해 다시 확인합니다. (2026-10-06 확인 기준으로 세 링크 모두 위 방법에서 200 이며, pipc.go.kr 은 2회 리다이렉트 뒤 200 입니다.)

이 수동 절차는 scripts/check-external-links.py 가 그대로 자동화합니다. 추적되는 모든 .md 에서 코드펜스·인라인 코드 밖의 외부 링크를 모으고(예약·플레이스홀더 호스트는 제외), 위와 같이 브라우저 UA·GET·리다이렉트 추적으로 상태를 확인하며 네트워크 오류·5xx·429 는 재시도해 일시적 깜빡임과 진짜 장애를 가릅니다. 결과를 네 가지로 나눕니다: OK(2xx·3xx) · RESTRICTED(401·403·405·429, 호스트는 살아 있고 확인 방법만 막힘) · ALLOWED (scripts/external-links-allowlist.txt 에 적힌, 우리가 고칠 수 없는 상류 상속 죽은 링크) · DOWN(404·410·5xx·연결 오류, 깨졌을 가능성 높음).

  • 네트워크 없이 점검 대상만 미리 보기: python3 -I scripts/check-external-links.py --list
  • 릴리스·주기 점검(새로 깨진 링크가 있으면 비정상 종료): python3 -I scripts/check-external-links.py --strict

외부 링크 생존은 flaky 하므로 머지 게이트에 넣지 않습니다. 대신 비차단 워크플로 external-links 가 매주 월요일과 수동 실행으로 --strict 를 돌려, allowlist 에 없는 DOWN 이 새로 생기면 빨갛게 드러냅니다. 상류 원문 보존 파일이 물려받은 죽은 링크(예: CHANGELOG.zh.md 가 크레딧한, 사라진 기여자 계정)는 우리가 고칠 수 없으므로 allowlist 에 사유와 함께 적어 strict 점검에서 뺍니다.


9. 릴리스 발행 파이프라인

버전 태그(v*)를 밀면 .github/workflows/release.yml 이 다섯 플랫폼용 바이너리와, 조건을 만족할 때 멀티아키텍처 Docker 이미지를 만듭니다. 이 포크는 아직 릴리스 태그를 끊은 적이 없어 이 워크플로가 한 번도 실행되지 않았으므로, 이 절은 파이프라인이 무엇을 전제하고 무엇을 산출하는지, 그리고 그 전제가 지금 저장소 구조와 맞는지를 정리합니다. 태그를 밀면 공개 저장소에 GitHub Release 가 생기므로, 릴리스를 끊는 일은 발행 결정이 선 뒤에 합니다.

9.1 릴리스를 끊는 법

v 로 시작하는 태그를 밀면 워크플로가 발화합니다.

git tag v0.3.15
git push origin v0.3.15

9.2 파이프라인이 하는 일

워크플로는 잡 다섯 개로 나뉩니다.

  • frontend. 프런트엔드를 정적으로 한 번 내보내고(web/out) 그 산출물을 web-dist 아티팩트로 올립니다. 아래 binaries 잡이 대상마다 이 산출물을 다시 받아 재사용합니다.
  • binaries. 다섯 대상(linux amd64·arm64, darwin amd64·arm64, windows amd64)을 교차 컴파일하고 대상마다 zip 으로 묶습니다. linux amd64 바이너리에는 artex -h 스모크 테스트를 돌려 바이너리가 실제로 실행되는지 확인합니다.
  • release. 모든 zip 을 모아 SHA256SUMS 체크섬을 만들고, GitHub Release 를 생성해 zip 과 체크섬을 첨부합니다.
  • docker-gate. DOCKERHUB_USERNAME·DOCKERHUB_TOKEN 시크릿이 설정돼 있는지 확인해 그 결과를 다음 잡의 실행 조건으로 넘깁니다.
  • docker. 위 시크릿이 있을 때만 돌며, binaries 가 교차 컴파일한 linux 바이너리를 받아 멀티아키텍처 이미지를 빌드하고 Docker Hub 에 올립니다. 시크릿이 없으면 이 잡을 건너뛰어, 릴리스 CI 는 빨간 실패 없이 바이너리 릴리스만으로 끝납니다.

9.3 빌드 전제가 저장소 구조와 맞는가

파이프라인은 다음 세 가지 전제 위에서 동작하며, 이 전제가 지금 저장소 구조와 모두 맞는지를 로컬에서 binaries 잡을 직접 재현해 확인했습니다.

  • 프런트엔드 임베드. frontend 잡이 올린 web-dist(= web/out 의 내용)를 binaries 잡이 server/webui/dist 로 받고, server/webui_embed.go 의 //go:embed all:webui/dist 가 그 자리를 바이너리에 임베드합니다. 그래서 binaries 잡은 프런트엔드를 다시 빌드하지 않고 ARTEX_SKIP_FRONTEND=1 로 build.sh 를 호출합니다.
  • 바이너리·패키지 경로. build.sh --target <os>/<arch> 는 dist/artex-<os>-<arch>/artex 바이너리와 dist/ 아래 zip 패키지를 만듭니다. zip 에는 바이너리와 함께 시작 스크립트(리눅스· macOS 는 start.sh, 윈도우는 start.bat), skills/, config.example.json, README.md 가 들어갑니다.
  • Docker 이미지의 바이너리 복사. binaries 잡은 linux 바이너리를 bin-linux-<arch> 아티팩트로 따로 올리고, docker 잡이 이것을 dist/<arch>/artex 로 받습니다. Dockerfile 의 COPY dist/${TARGETARCH}/artex 가, 멀티아키텍처 빌드에서 buildx 가 각 플랫폼에 맞춰 채워 주는 TARGETARCH 로 그 경로를 집습니다. .dockerignore 는 dist/ 를 제외하지 않으므로 바이너리가 빌드 컨텍스트에 포함됩니다.

9.4 아직 결정 전인 것: Docker 이미지 네임스페이스

docker 잡은 현재 이미지 이름을 상류의 autumn27/artex 로 두고 있고, 이 포크를 어느 네임스페이스로 발행할지는 별도 결정 사안입니다(work/DECISIONS-FOR-JIWOO.md 8번 항목). 결정이 서기 전까지는 Docker Hub 시크릿을 두지 않으며, 그동안 릴리스는 바이너리 zip 과 체크섬만 발행합니다(docker 잡은 건너뜁니다).

9.5 태그 없이 로컬에서 미리 검증하기

공개 릴리스를 끊지 않고 파이프라인 전제만 확인하려면, binaries 잡을 로컬에서 재현합니다. 로컬에 Go 가 없으면 Docker 로 동일하게 돌릴 수 있습니다.

# 1) 프런트엔드 정적 내보내기(release.yml 의 frontend 잡에 해당)
cd web && npm ci && npm run build:static && cd ..
# 2) binaries 잡이 아티팩트를 받는 자리에 배치
rm -rf server/webui/dist && mkdir -p server/webui/dist && cp -a web/out/. server/webui/dist/
# 3) 한 대상만 binaries 잡과 같은 환경으로 빌드
docker run --rm -v "$PWD":/app -w /app \
  -e ARTEX_SKIP_FRONTEND=1 -e ARTEX_SKIP_NPM_CI=1 \
  -e ARTEX_COMPRESS=0 -e ARTEX_PACKAGE=1 -e ARTEX_PACKAGE_DIR=dist \
  -e ARTEX_BUILD_VERSION=v0.0.0-local \
  golang:1.26 bash -c 'apt-get update && apt-get install -y zip && ./build.sh --target linux/amd64'
# 4) 산출물 확인: dist/artex-linux-amd64/artex · dist/*.zip · dist/SHA256SUMS

dist/artex-linux-amd64/artex 는 정적 링크된 ELF 이고, -h 를 주면 사용법을 출력한 뒤 종료 코드 0 으로 끝납니다. 이것이 binaries 잡의 스모크 테스트가 확인하는 동작입니다. 빌드 산출물 (dist/·server/webui/dist/)은 저장소에 커밋하지 않습니다(.gitignore 로 제외됩니다).