라운드 3 통합
- 작성일: 2026-10-19
- 관련 문서: WBS와 마일스톤 (M2, 3.1), 시스템 구조와 데이터 모델 (Testing 공개 함수), 화면설계서 (S1, S2, 부록 C), 워커 보고 -2 Testing 실행 경로, -3 룰 7종, -4 적재와 실행 화면, -5 AI 클라이언트
목표
라운드 3 워커 4명(A 실행 경로, B 룰 7종, C S3/S4 화면, D AI 클라이언트)의 결과를 검토해 하나씩 병합하고, 그동안 메인 세션은 워커 결과에 기대는 S1, S2 화면(WBS 2.5, 2.6)을 만든다. 워커 A 는 이 문서를 처음 쓸 때 아직 작업 중이었다. A 병합은 2026-10-20 에 이 문서에 이어 적었다 ("A 병합" 절).
한 일
병합 (메인, 워커 결과 검토 뒤)
| 순서 | 워커 | 병합 커밋 | 병합 뒤 mix precommit |
|---|---|---|---|
| 1 | B 룰 7종, 정답 라벨 직접 대조 | ff95ad8 | 293 passed |
| 2 | C S3 ImportLive, S4 TestRunLive, TickmarkWeb.Format |
af65bb3 | 317 passed |
| 3 | D AI Client, Fake, Anthropic, 트리아지 프롬프트 (오탐 수정 90f6184 포함) | 3706032 | 347 passed |
라운드 시작 전 선행 작업 뒤가 267 passed 였다.
- D 는 첫 보고에서 계약 항목(설정 키, 코드에 API 키 없음, Req 옵션, Fake 모델 이름,
input_hash/1)은 모두 맞았다. 문제는 금지 표현 검사였다. 메인 세션이@forbidden_expressions정규식을 정상적인 감사절차 문장에 직접 돌려 보니 "승인이 필요한 금액 기준을 확인한다", "거래에 문제가 없는지 증빙과 대조한다" 같은 6개 문장이 거절됐다. 실제 키로 돌리면 대부분의 제안이invalid_output(fatal) 로 끝나고 재시도도 없다(도메인 4.4). 통과해야 할 문장, 계속 거절할 문장을 적어 D 에게 후속 작업으로 돌려보냈다. D 는 종결형(단정)만 걸도록 패턴을 좁히고, 정상 문장 11개를 모든 출력 필드에서, 기존 거절 문장 15개를 회귀 시험으로 확인했다(90f6184).
S1 최근 테스트, 검토 큐 열 (WBS 2.5, 메인)
Testing.latest_run_summaries/1(4bac346): 내가 멤버인 감사 대상마다 가장 최근 실행의 상태와 검토 큐 수를 돌려준다.DISTINCT ON으로 최근 실행을 고르고, 검토 큐는 실행마다 저장된triage_threshold로 한 번에 센다. 쿼리는 2번이다. 실행 중이면 검토 큐는nil.- EngagementLive.Index 에 두 열을 더했다(1283c6d). 실행 상태 표시는 S2 에서도 쓰도록
TestRunLive.Components.run_status/1로 뒀다. 318 passed.
S2 통계, 실행 목록, 테스트 실행 단추 (WBS 2.6, 메인, 5c7e560)
Testing.count_findings/2에approved(검토 큐 안의 승인 수) 를 더했다. 화면설계서 C-07 은 이것으로 해결했다.EngagementLive.Components.stat_row/1: 전표, 라인(최근 성공 적재 기준), 검토 큐, 경미 적중, 승인 / 검토 큐(최근에 끝난 실행 기준). 끝난 실행이 없으면 "-" 와 안내 문구.- 실행 표(
#test-runs스트림): 시작 시각, 상태, 끝난 룰 / 전체 룰, 적중 전표, 검토 큐, 열기(S4). 실행 중이면 적중과 검토 큐는 "-". - 적재 표에 행동 열: 작성자에게만, 적재가 끝난 GL 에만 "테스트 실행" 단추가 보인다. 그 GL 에 실행 중인 테스트가 있으면 단추 대신 "실행 중" 문구.
{:test_run_updated, ...}를 받으면 실행 행, 통계, 그 GL 의 단추를 다시 그린다. 351 passed.- 단추를 눌러 S4 로 가는 테스트는 아직 없다.
start_test_run/3이 A 병합 전까지 Runner 스텁에서 예외를 내기 때문이다.
문서
- WBS 2.2, 2.3, 2.5, 3.1 완료, 2.4, 2.6 진행 중으로 갱신. TASKS.md, 색인, 화면설계서(v1.2 구현 상태, C-07 해결) 갱신.
A 병합 (2026-10-20 이어 씀)
- 워커 A 보고: 실행 경로 완료, 새 테스트 34개, A 브랜치의
mix precommit298 passed. 메인이runner.ex,rule_worker.ex,failure_handler.ex, 동시성 테스트를 직접 읽고, finding 기본값 (status open, triage_status none) 과application.ex의FailureHandler.attach/0호출이 있는지 확인한 뒤--no-ff로 병합했다 (fadb51c). - A 가 작업 중에 계약 5.4 의 순서 변경을 물었다. 성공 경로가 "rule_results insert -> test_run FOR UPDATE" 이면 두 RuleWorker 가 동시에 끝날 때 교착이 난다. insert 의 FK 검사가 부모 test_run 에 FOR KEY SHARE 를 걸고, 그 뒤 FOR UPDATE 를 서로 기다리기 때문이다. "FOR UPDATE -> mark_rule -> insert" 로 바꾸는 것을 승인하고 계약에 적었다. 라운드 4 의 finding 자식 행 (ai_triages, conclusions, review_decisions) 에도 같은 lock-first 규칙을 적용한다.
측정
| 시점 | mix test |
|---|---|
| 선행 작업 뒤 | 267 passed |
| B 병합 | 293 passed |
| C 병합 | 317 passed |
| S1 (1283c6d) | 318 passed |
| D 병합 | 347 passed |
| S2 (5c7e560) | 351 passed (4.6초, 이 문서 작성 시 다시 실행) |
| A 병합 (fadb51c) | 385 passed (7.6초) |
결정과 트레이드오프
- C-07 은 새 함수 대신
count_findings/2를 넓혔다. 대안은 실행 요약 전용 함수(run_summary/2등)였다.count_findings/2는 이미 S4 탭 숫자에 쓰고, Ectofilter/2로 칸 하나를 더하면 쿼리 수가 그대로다. 반환 맵에 키가 하나 늘어서 S4 기본값과 기존 테스트도 같이 고쳤다. - S2 실행 표의 룰 열은 실패한 룰도 끝난 룰로 센다. S4 진행 요약(
test_progress/1)이 이미 그렇게 센다. 화면설계서 S2.2 상태 변형은 일부 실패를 "7 / 8" 로 그렸지만, 같은 실행이 S2 에서 7 / 8, S4 에서 8 / 8 로 보이는 것이 더 헷갈린다고 봤다. 실패 여부는 상태 열("일부 실패")이 따로 보여준다. 화면설계서 S2 영역 8 비고에 적었다. - S1 은 실시간이 아니다. 화면설계서 S1.3 대로 아무 토픽도 구독하지 않고 mount 때 한 번 읽는다. 감사 대상이 많아도 토픽 수가 늘지 않는다. 대신 목록을 다시 열어야 최신 상태가 보인다.
- 단추를 숨겨도 서버 검사는 그대로 둔다. 화면은 실행 중인 GL 의 단추를 숨기지만, 다른 탭에서 동시에 누르는 경우는
start_test_run/3의:run_in_progress가 막는다. 이 오류와:import_not_ready,:forbidden은 각각 한국어 flash 로 바꿨고 changeset 오류는 내부 내용을 보이지 않는 일반 문구로 둔다. - D 의 오탐은 메인이 고치지 않고 D 에게 돌려보냈다.
anthropic.ex는 D 소유 파일이고, 메인이 고치면 D 의 테스트와 devlog 가 실제 코드와 어긋난다. 대신 통과/거절 문장 목록을 계약처럼 넘겨 판단 기준을 메인이 정했다.
막힌 점과 해결
-
스트림 행을 테스트가 못 찾았다.
stream(:test_runs, ...)의 기본 dom_id 는"test_runs-<id>"다(스트림 이름 그대로). 화면설계서의 id 규칙은 하이픈이라stream/4에dom_id: &"test-runs-#{&1.id}"를 줬다. -
약한 단언 두 개. "2 / 1" 이 맞게 나온 것은 fixture 의
rule_count기본값 때문이었다.finished_run/3이rule_count: 2를 넘기게 고쳤다. 검토자에게 행동 열이 없음을 확인하는refute가#imports th를 봤는데#imports는 tbody 라 언제나 통과했다.#imports-section th로 고쳤다. -
임의 id 브로드캐스트. 기존 "다른 토픽 메시지" 테스트가 아무 id 로
test_run_updated를 보냈다. 이제 핸들러가get_test_run!을 부르므로 프로세스가 죽는다. 그 테스트에서 빼고, 실제 실행으로 다시 그리는 경우를 새 테스트로 덮었다. -
승인 발견을 공용 setup 에 넣으면 다른 목록 테스트가 깨졌다. 승인 개수 테스트를 따로 만들어 그 안에서만 넣었다.
-
Orca 터미널. 세 가지가 있었다.
- 워커 A 의 첫 실행에서 프롬프트가 전달되지 않아 다시 띄웠다.
git worktree add로 만든 worktree 의 터미널은 Orca 에 등록되지 않아orphaned로 남고 화면에 안 보였다.orca worktree create로 만들고--worktree name:으로 띄우도록 CLAUDE.md 에 적었다(801d680).terminal_worktree_mismatch는--worktree path:를 줘서 풀었다.
-
정답 라벨 시험의 간헐 timeout. A 병합 뒤 라운드 4 선행 작업 중 전체 실행에서
RulesLabelTest2건이 15초 timeout 으로 실패했다 (387/389, 36.8초). 단독으로 돌리면 4.8초에 통과했고 다시 전체를 돌리면 389 passed 였다. DB 에 남은 행이나 다른 활동은 없었다.DuplicateEntry의 그룹 쿼리가 부하가 걸린 DB 에서 느려지는 것으로 보고, 원인 조사 (EXPLAIN ANALYZE) 는 라운드 4 W5 에 맡겼다.
Elixir 학습 포인트
- LiveView 스트림은 서버에 행을 남기지 않는다. 렌더한 뒤 행 데이터는 클라이언트 DOM 에만 있다. 그래서 다른 assign(
running_import_ids) 이 바뀌어도 이미 그린 적재 행의 단추는 저절로 바뀌지 않는다. 같은 항목을stream_insert/3로 다시 넣으면 같은 dom_id 의 행이 그 자리에서 바뀐다. 실행 행은at: 0으로 넣어 새 실행이 위에 온다. - 스트림 항목은 스키마가 아니어도 된다.
%{id: ..., run: ..., done: ..., review_queue: ...}처럼:id가 있는 맵이면 된다. 행마다 미리 계산한 값을 들고 다니게 해서 템플릿에서 쿼리를 하지 않는다. - HEEx
:let은 컴포넌트와 슬롯에만 쓴다.<tr :let={...}>는 컴파일 오류다. 행마다 계산이 필요하면 헬퍼 함수(summary_for/2) 를 부른다.:if,:for는 일반 태그에도 된다. - Ecto
filter/2집계.filter(count(f.id), f.score >= ^threshold and f.status == :approved)는 SQLCOUNT(...) FILTER (WHERE ...)가 된다. 한 번의 쿼리로 검토 큐, 경미, 승인 세 칸을 센다. DISTINCT ON으로 그룹별 최신 행.distinct: r.engagement_id, order_by: [desc: r.started_at, desc: r.id]는 감사 대상마다 가장 최근 실행 한 건을 고른다. Ecto 가 distinct 열을 ORDER BY 맨 앞에 넣어 주므로order_by에는 "무엇이 최신인가" 만 쓴다.r.id는 같은 시각일 때 결과를 고정한다. PostgreSQL 전용 기능이다.
다음 할 일
- (완료) 워커 A 병합과 해제.
- 라운드 4 W5: S2 단추 클릭 테스트(성공 시 S4 로 이동,
:run_in_progressflash), Runner 를 거친 정답 라벨 시험(2.4). 메인: S1-S4 브라우저 확인. - 라운드 4 W1: FailureHandler 에 Triage 분기. 라운드 4b 메인: S4 AI 열. 이어서 라운드 4 계약과 통합.
- 사용자 결정 대기: PR #2 병합(1.19), 배포 대상(5.1).
관련 커밋
- 9f0261b, d61146d, 456da8d: 라운드 3 선행 작업과 계약
- 4bac346, daa9ce9:
latest_run_summaries/1과 계약 반영 - 801d680: Orca 터미널 규칙
- ff95ad8: 워커 B 병합 (24c2536, 1f6257b)
- af65bb3: 워커 C 병합 (c62a65c, 04986da)
- 90f6184, 3706032: D 오탐 수정과 워커 D 병합 (e4c1cd1, cd4497e)
- 1283c6d: S1 두 열
- 5c7e560: S2 통계, 실행 목록, 단추
- fadb51c: 워커 A 병합 (1d28488, 2436619, aba197b)