본문으로 건너뛰기
Tickmark 화면설계서

Tickmark 화면설계서

S1-S6 6개 화면을 영역 번호, 상태 변형, 사용자 행동, 권한, 감사 로그 기준으로 정리했다. 디자인 시스템 시안이 화면마다 대표 상태 하나를 보여 준다면, 이 문서는 구현할 때 만들어야 하는 상태를 화면별로 모두 보여 준다. 값이나 규칙이 다르면 디자인 시스템 문서와 설계 문서가 우선한다.

0. 문서 정보

이 문서는 MVP(2026-10-23) 구현과 리뷰의 기준 화면이다. 구현이 바뀌면 같은 파일을 고치고 맨 아래 Changelog에 남긴다.

문서
2026-10-17-13-화면설계서
버전
v1.1, 2026-10-19
대상 범위
MVP 화면 S1-S6, 데스크톱 1024px 이상
데이터
합성 데이터. 누리정밀 주식회사 FY2025, 시드 20251231

읽는 법

  • 전체 프레임은 1024px 캔버스에 상단 바부터 그린 화면이다. 좁은 창에서는 프레임 안에서만 가로로 스크롤한다.
  • 부분 프레임은 같은 화면에서 상태에 따라 달라지는 영역만 그린다. 나머지 영역은 전체 프레임과 같다.
  • 표식은 프레임 바로 아래 영역 표의 번호다. 상단 "영역 번호" 버튼으로 숨길 수 있다.
  • 프레임 막대의 "김작성으로 로그인"은 그 화면을 보는 사람과 역할이다. 같은 화면도 역할에 따라 버튼이 다르다.
  • S1, S2는 구현된 코드의 문구를 옮겼다. S3-S6은 아직 스텁이라 설계 계약을 기준으로 한 제안이고, phx- 이벤트 이름은 구현할 때 확정한다.

등장인물 (합성)

등장인물
이름이메일역할이 문서에서 하는 일
김작성kim.writer@example.com작성자감사 대상을 만든 사람. 멤버 추가, GL 적재, 테스트 실행, AI 트리아지 요청, 결론 제출
이작성lee.writer@example.com작성자추가된 작성자. 생성자가 아니므로 멤버 추가 폼이 없다
박검토park.reviewer@example.com검토자승인, 반려. 적재, 실행, 트리아지, 결론 버튼이 없다
비멤버-없음-없음로그인했지만 이 감사 대상의 멤버가 아닌 사용자. S2-S6 주소로 들어오면 404

1. 화면 목록

라우트는 모두 로그인 필요 live_session :require_authenticated_user 안에 있다. S2-S6은 get_engagement!/2가 멤버가 아니면 Ecto.NoResultsError를 내서 404가 된다.

화면 목록
IDLiveView (action)누가 쓰나구독 토픽주요 컨텍스트 함수상태
S1EngagementLive.Index
(:index, :new)
로그인 사용자 전원없음Engagements.list_engagements/1
create_engagement/2, change_engagement/2
구현됨
S2EngagementLive.Show
(:show)
멤버 (추가 폼은 생성자만)engagement:#{id}get_engagement!/2, list_members/2, add_member/4
Ledger.list_imports/2, Testing.list_test_runs/2, start_test_run/3
일부
S3ImportLive.New
(:new)
작성자engagement:#{id}Ledger.create_import/3, generate_sample_import/3, get_import!/2구현됨
S4TestRunLive.Show
(:show)
멤버 (트리아지는 작성자)test_run:#{run_id}
engagement:#{id}
Testing.get_test_run!/2, subscribe_test_run/2, list_findings/3
Triage.request_triage/2
일부
S5FindingLive.Show
(:show)
작성자 (판단), 검토자 (서명)engagement:#{id}
engagement:#{id}:presence
Testing.get_finding!/2, Triage.request_finding_triage/2
Review.submit_conclusion/3, decide/4
스텁
S6AuditLive.Index
(:index)
멤버 (읽기 전용)engagement:#{id}AuditTrail.list_events/3스텁

presence는 S2-S6 모든 engagement 하위 화면이 engagement:#{id}:presence에 들어간다. meta는 %{email, role, view}이고, S5만 view = "finding:#{id}"로 같은 전표를 보는 사람을 거른다.

2. 흐름

한 감사 대상에서 GL을 적재하고 승인까지 가는 경로다. 점선 상자는 AI, 회색 상자는 백그라운드 작업이다. 상자 안의 코드는 그 단계에서 남는 감사 로그 action이다.

2.1 주 흐름

시스템
Oban
ImportWorker 파싱, 적재ledger_import.succeeded또는failed RuleWorker 8개, 동시 4개test_run_rule.failed(실패 시) finding 집계, 점수test_run.completed
AI
TriageWorker
위험도, 추가 절차, 부족한 정보 제안ai_triage.completed또는failed 제안만 한다. finding.status를 바꾸지 않는다.

2.2 상태 기계

finding.status (Review만 바꾼다)

finding.status 전이
전이행위화면 표시
open -> concluded작성자 결론 제출미판단 -> 판단 배지 + 검토 대기
concluded -> concluded검토 전 재제출 (새 행 추가)판단 배지만 바뀜
concluded -> approved검토자 승인 (끝 상태)승인, 잠금 안내
concluded -> rejected검토자 반려, 사유 필수반려 + 사유
rejected -> concluded작성자 재제출검토 대기
approved -> 없음제출, 결정 모두 :invalid_state폼 대신 읽기 전용

finding.triage_status (Triage만 바꾼다)

triage_status 전이
전이원인화면 표시
none -> queuedS4 일괄 요청 또는 S5 건별 요청분석 중
queued -> queued429, 5xx 등 재시도 가능한 오류 (Oban backoff)분석 중 유지
queued -> succeededai_triages 행 저장위험도 배지
queued -> failed스키마 불일치, 금지 표현, 4xx 등AI 실패 + 다시 요청
failed -> queuedS5 다시 요청 (새 ai_triages 행)분석 중

ledger_imports.status

배지: 대기 (clock), 적재 중 (arrow-path, 회전), 완료 (check), 실패 (x-circle). 실패한 적재로는 테스트를 실행할 수 없다 (:import_not_ready).

test_runs.status, test_run_rules.status

룰 하나라도 failed면 실행은 completed_with_errors. skipped는 실행 조건 미달(예: 희귀 계정 조합의 모집단 부족)이고 0점으로 본다. 같은 적재에 running 실행이 있으면 새 실행은 :run_in_progress.

3. 공통

여러 화면에 똑같이 적용되는 상단 바, 권한, 공통 상태, 실시간 갱신, 반응형, 값 형식을 여기서 정한다. S1부터 S6까지의 절에는 여기와 다른 점만 적는다.

3.1 상단 바

상단 바는 48px 높이로 위에 붙어 있다. 들어 있는 항목은 감사 대상 안인지 밖인지에 따라 달라진다. 번호는 아래 영역 표의 번호다. 화면 위 오른쪽의 "영역 번호" 버튼으로 번호를 숨길 수 있다.

감사 대상 안 (S2, S3, S4, S6)김작성과 박검토가 같은 감사 대상을 보고 있다
Tickmark
지금 보는 사람: 김작성(나), 박검토kim.writer@example.com
감사 대상 밖 (S1)감사 대상을 고르기 전
Tickmark
kim.writer@example.com
전표 검토 (S5)상단 바에는 presence가 없다. 같은 전표를 보는 사람은 페이지 머리에 보인다
상단 바 영역
번호영역내용과 데이터권한, 조건
브랜드로고와 Tickmark. 누르면 S1(/engagements)로 간다.모든 화면
감사 대상 이름"회사명 FY연도". 누르면 S2로 간다. S2에서는 aria-current="page".감사 대상 안에서만
감사 로그누르면 S6으로 간다. S6에서는 aria-current="page".멤버 전원 (:view)
presenceengagement:#{id}:presence. 탭을 여러 개 열어도 사람마다 아바타 1개. 최대 4개, 넘으면 "+N". 내 아바타는 avatar-self. 이름 목록은 sr-only로 읽힌다. 들어오고 나가면 바로 바뀐다.S2, S3, S4, S6. S5는 페이지 머리로 옮긴다
테마시스템, 라이트, 다크. localStorage의 tk:theme에 저장한다. 저장할 수 없으면 이번 페이지에만 적용한다.모든 화면
사용자 메뉴내 이메일과 펼침 단추. 항목은 설정(/users/settings)과 로그아웃(DELETE /users/log-out). 한글 문구는 확인 필요.로그인 사용자

아직 구현되지 않았다. 지금은 Phoenix 기본 헤더(Layouts.app의 Website, GitHub, Get Started)가 보이고, 루트 레이아웃의 Settings, Log out은 영어다. 부록 C의 C-01.

3.2 권한

역할은 감사 대상마다 따로 정해진다. 한 사람은 한 감사 대상에서 한 역할만 맡는다. 아래 표의 세 사람은 누리정밀 FY2025의 멤버다.

역할별 권한 행렬
행동김작성
생성자, 작성자
이작성
작성자
박검토
검토자
비멤버권한 키거부될 때
S1 목록 보기가능가능가능가능로그인만목록에는 내가 멤버인 감사 대상만 보인다
S1 감사 대상 만들기가능가능가능가능로그인만만든 사람이 작성자 멤버가 된다
S2-S6 보기가능가능가능-불가:view404. 없는 감사 대상과 구별되지 않는다
멤버 추가 (S2)가능-불가-불가-불가:manage_members
(created_by)
폼을 그리지 않는다. 이벤트를 직접 보내면 flash "멤버를 추가할 권한이 없습니다."
GL 불러오기 (S2, S3)가능가능-불가-불가:import_ledger단추를 숨긴다. S3 주소로 바로 오면 S2로 보내고 flash (제안)
테스트 실행 (S2)가능가능-불가-불가:start_test_run단추를 숨긴다
AI 트리아지 요청 (S4, S5)가능가능-불가-불가:request_triage자리는 두고 비활성. 이유 "작성자만 요청할 수 있습니다"
결론 제출 (S5)가능가능-불가-불가:submit_conclusion판단 폼 대신 읽기 전용 요약
승인, 반려 (S5)-불가-불가가능-불가:review_conclusion자리는 두고 비활성. 이유 "검토자만 승인할 수 있습니다"
감사 로그 보기 (S6)가능가능가능-불가:view404
권한을 막는 계층
계층무엇을 막나방법
라우터로그인하지 않은 사용자live_session, require_authenticated_user
컨텍스트비멤버, 역할 밖 행동authorize/3가 Policy.allowed?/2를 부른다
컨텍스트자기 결론 승인decide/4의 self_review 검사와 review.denied 기록
DB우회 코드CHECK reviewer_id <> preparer_id, 복합 FK
DB한 사람이 두 역할unique (engagement_id, user_id)
LiveView단추 노출UX일 뿐이다. 보안 경계가 아니다

3.3 공통 상태

flash 성공:info, role=status
감사 대상을 만들었습니다.
화면 오른쪽 위에 뜬다. 누르면 닫힌다 (Phoenix 기본 동작).
flash 오류:error, role=alert
내부 원인(:forbidden 같은 값)은 문구에 넣지 않는다.
연결 끊김LiveView 소켓 재연결
지금은 영어(We can't find the internet, Something went wrong!, Attempting to reconnect). C-02.
404없는 주소, 비멤버
페이지를 찾을 수 없습니다없는 주소이거나 내가 멤버가 아닌 감사 대상입니다.감사 대상 목록으로
지금은 "Not Found" 한 줄이다. C-03.
빈 목록만들 수 있는 사람에게만 행동 단추
아직 불러온 GL이 없습니다.GL 불러오기
문구는 "아직 ...이 없습니다." 형식으로 쓴다.
필터 결과 없음조건 때문에 비었을 때
조건에 맞는 전표가 없습니다.
데이터가 없는 것과 필터로 숨은 것을 다른 문구로 구별한다.
불러오는 중비동기 영역만
전표
라인
검토 큐
skeleton은 통계, AI 분석처럼 늦게 오는 영역에만 쓴다. 페이지 전체를 skeleton으로 덮지 않는다.
비활성 행동권한이 없어 핵심 행동이 막혔을 때

작성자만 요청할 수 있습니다

disabled와 이유 문구를 함께 둔다. 툴팁에만 이유를 숨기지 않는다.

3.4 실시간 갱신

화면은 새로고침 없이 바뀐다. 다른 사람의 행동으로 내가 보던 내용이 바뀌어도 입력하던 내용과 스크롤 위치는 그대로 둔다.

PubSub 토픽과 화면 변화
토픽메시지받는 화면화면 변화
engagement:#{id}import_updated, %{import_id}S2, S3S2는 적재 행을 바꾼다(stream_insert, at: 0). S3는 진행 패널을 결과 패널로 바꾼다
engagement:#{id}test_run_updatedS2실행 행과 통계를 바꾼다. 지금은 받기만 하고 무시한다
engagement:#{id}finding_updatedS4, S5발견 행의 AI, 판단, 검토 배지. S5는 큐 항목과 이력
engagement:#{id}audit_appendedS6목록 위에 "새 이벤트 N건" 단추만 띄운다. 누르면 그때 넣는다
test_run:#{id}rule_progressS4룰별 진행 목록과 진행률
test_run:#{id}run_completedS4진행 패널을 접고 발견 표를 연다
engagement:#{id}:presencepresence_diffS2-S6상단 바 아바타. S5는 같은 전표를 보는 사람(meta view가 finding:#{id})

3.5 반응형

기준 폭은 1024px다. 아래 폭 구간은 제안이고, 구현하면서 확정한다.

폭별 규칙
폭규칙
1024px 이상기준 레이아웃. S5는 왼쪽 큐 232px + 본문
768-1023px표는 tbl-wrap 안에서만 가로로 스크롤한다. 페이지 전체는 가로로 스크롤하지 않는다. 상단 바의 감사 대상 이름은 말줄임
767px 이하S5 큐는 본문 위의 접는 목록(details)으로 옮긴다. 통계는 2열. 폼은 1열. 앱 영역 좌우 여백 16px

좁은 화면의 S5는 S5.4에 그렸다.

3.6 값 형식

값 형식
값형식예
금액천 단위 쉼표, 오른쪽 정렬, 단위는 열 머리에 "(원)"386,214,905,000
0인 금액, 0건"-"를 보이고 sr-only로 "0"-0
값 없음"-"를 보이고 sr-only로 "없음"-없음
건수천 단위 쉼표, 단위는 작게8,341건
시각KST로 보이고, datetime은 UTC ISO 8601, title은 "YYYY-MM-DD HH:MM:SS UTC"
날짜ISO 형식2025-12-31
회계연도, 회계기간FY + 연도. 13기는 "13(결산조정)"FY2025, 13(결산조정)
전표 번호, 코드, 해시고정폭. 해시는 앞 8자JE-000123, 3f9a0c7e
점수숫자, 5칸 미터(점수/20 올림), 구간 이름55상
사람이름. 감사 로그에서는 이름과 역할김작성, 박검토

지금의 kst_time은 title도 ISO 8601이다 (C-04). 사람 이름은 users 테이블에 없다. 지금은 이메일만 있으므로 users.display_name을 더할지 정해야 한다 (C-05).

S1. 감사 대상 목록

경로
/engagements, /engagements/new
LiveView
EngagementLive.Index
권한
로그인 사용자 전원
데이터
Engagements.list_engagements/1
구독
없음
구현
구현됨 (최근 테스트, 검토 큐 열은 WBS 2.5)

S1.1 기본 화면

S1/engagementsEngagementLive.Index :index, 김작성으로 로그인
Tickmark
kim.writer@example.com

감사 대상

내가 속한 감사 대상
회사명회계연도결산일수행중요성(원) 최근 테스트 검토 큐(건) 내 역할
누리정밀 주식회사FY20252025-12-31225,000,000완료52작성자
한빛소재 주식회사FY20252025-12-31410,000,000실행 중-없음검토자
다온식품 주식회사FY20242024-12-3196,000,000일부 실패31작성자
세운물산 주식회사FY20252025-12-3158,000,000-없음-없음작성자
S1 영역
번호영역내용과 데이터권한, 조건
페이지 제목"감사 대상". 페이지의 h1.-
새 감사 대상patch로 /engagements/new. 목록 위에 인라인 패널을 연다. 모달이 아니다. 패널이 열려 있으면 단추를 숨긴다.로그인 사용자 전원
목록 표내가 멤버인 감사 대상만. 최근에 만든 순(inserted_at 내림차순). 회사명을 누르면 S2.-
최근 테스트가장 최근 실행의 상태. 실행 중, 완료, 일부 실패(completed_with_errors). 실행이 없으면 "-".Testing.latest_run_summaries/1
검토 큐(건)최근 실행에서 점수가 triage_threshold(25) 이상인 발견 수. 실행 중이거나 실행이 없으면 "-".Testing.latest_run_summaries/1
내 역할작성자 또는 검토자. 테두리 배지.-

S1.2 상태 변형

빈 목록감사 대상 0건
감사 대상이 없습니다.새 감사 대상 만들기
구현은 표 안의 한 행(colspan)에 문구와 링크를 둔다. 첫 로그인 직후에 보인다.
만들기 패널/engagements/new

새 감사 대상

만든 사람은 이 감사 대상의 작성자 멤버가 됩니다. 검토자는 상세 화면에서 추가합니다.

패널이 열리면 회사명 칸에 포커스를 준다 (제안). 취소하면 입력을 버리고 /engagements로 돌아간다.
입력 오류건드린 칸과 제출 뒤에만 오류를 보인다

새 감사 대상

목표 문구다. 지금은 Ecto 기본 영어(can't be blank, must be greater than 0 등)가 보인다. 문구는 부록 A, 차이는 C-06.
만드는 중제출 직후
phx-disable-with="만드는 중". 두 번 눌러도 한 번만 만들어진다.
만든 직후S2로 이동
감사 대상을 만들었습니다.

감사 대상세운물산 주식회사 FY2025

push_navigate로 새 감사 대상의 S2를 연다. 목록으로 돌아오면 맨 위에 있다.

S1.3 이벤트

S1 이벤트
트리거처리성공실패감사 로그
"새 감사 대상"patch /engagements/new, handle_params :new패널을 연다--
취소patch /engagements패널을 닫고 입력을 버린다--
입력 (phx-change="validate")Engagements.change_engagement/2, action :validate칸별 오류--
만들기 (phx-submit="save")Engagements.create_engagement/2flash "감사 대상을 만들었습니다.", S2로 이동changeset 오류를 칸 아래에 보인다engagement.created. 생성자를 작성자 멤버로 넣는 일은 같은 트랜잭션이고 따로 이벤트를 남기지 않는다
회사명 누름navigate S2---

S2. 감사 대상 상세

경로
/engagements/:id
LiveView
EngagementLive.Show
권한
멤버 (:view). 멤버 추가는 생성자만
데이터
Engagements.get_engagement!/2, list_members/2, Ledger.list_imports/2, Testing.list_test_runs/2
구독
engagement:#{id}
구현
구현됨 (통계, 실행 목록, 행 행동은 WBS 2.6). presence는 아직 없다

S2.1 기본 화면

S2/engagements/:idEngagementLive.Show, 김작성(생성자, 작성자)
Tickmark
지금 보는 사람: 김작성(나), 박검토kim.writer@example.com

감사 대상누리정밀 주식회사 FY2025

누리정밀 주식회사 FY2025

결산일 2025-12-31수행중요성 225,000,000원

전표8,341건
라인19,804건
검토 큐52건점수 25 이상
경미 적중213건점수 25 미만
승인4/ 52

멤버

멤버
이름이메일역할
김작성 (나)kim.writer@example.com작성자
이작성lee.writer@example.com작성자
박검토park.reviewer@example.com검토자

GL 적재

GL 적재 목록
출처상태전표라인차변 합계(원)대변 합계(원)오류경고해시적재 시각행동
합성 GL, 시드 20251231완료8,34119,804386,214,905,000386,214,905,000-0-03f9a0c7e
gl_2025_draft.csv실패-없음-없음-없음-없음41b71d02e5

테스트 실행

테스트 실행 목록
시작 시각상태룰적중 전표검토 큐행동
완료8 / 826552열기
S2 영역
번호영역내용과 데이터권한, 조건
이동 경로감사 대상 / 회사명 FY연도. 첫 항목은 S1 링크.-
제목과 개요회사명과 회계연도가 h1. 아래 줄에 결산일과 수행중요성.-
머리 행동감사 로그(보조, 멤버 전원)와 GL 불러오기(primary, S3).GL 불러오기는 :import_ledger
통계전표와 라인은 가장 최근에 성공한 적재 기준. 검토 큐, 경미 적중, 승인은 가장 최근에 끝난 실행 기준. 기준값은 실행 시점의 triage_threshold 스냅샷. 끝난 실행이 없으면 "-".Testing.count_findings/2 (C-07 해결)
멤버 표이름, 이메일, 역할. 내 행에 "(나)". 추가한 순서(inserted_at 오름차순).이름 열은 C-05 결정에 따른다
멤버 추가 폼이메일, 역할(기본값 검토자), 추가. 제출 중에는 "추가 중".생성자만 (:manage_members)
GL 적재 표새 적재가 위. 출처는 CSV면 파일명, 합성이면 "합성 GL, 시드 N". 상태는 대기, 적재 중, 완료, 실패. 실패 행의 파일명은 S3 결과로 가는 링크이고 오류 수는 빨간 숫자. 완료 행에 테스트 실행 단추.단추는 :start_test_run, 그 적재에 실행 중인 테스트가 없을 때만
테스트 실행 표새 실행이 위. 시작 시각, 상태(실행 중, 완료, 일부 실패), 끝난 룰 / 전체 룰, 적중 전표, 검토 큐, 열기(S4).룰 열은 실패한 룰도 끝난 룰로 센다 (S4 진행 요약과 같은 기준). S2.2 "실행 일부 실패"의 7 / 8은 구현에서 8 / 8로 보인다

S2.2 상태 변형

검토자가 볼 때박검토. 멤버 추가 폼, GL 불러오기, 테스트 실행이 없다

누리정밀 주식회사 FY2025

결산일 2025-12-31수행중요성 225,000,000원

감사 로그
멤버, 검토자 시점
이름역할
김작성작성자
이작성작성자
박검토 (나)검토자
숨긴 자리는 비워 두지 않고 당겨 채운다. GL 적재 표의 행동 열도 그리지 않는다.
첫 진입적재 0건, 실행 0건
전표-없음
검토 큐-없음
승인-없음
아직 불러온 GL이 없습니다.GL 불러오기
아직 테스트 실행이 없습니다.
GL 불러오기 단추는 작성자에게만 보인다. 검토자에게는 문구만 보인다.
적재 중ImportWorker가 처리하는 동안
적재 중인 행
출처상태전표오류적재 시각
gl_2025_v2.csv적재 중-없음-없음
gl_2025_v3.csv대기-없음-없음
끝나면 import_updated를 받아 같은 행이 완료나 실패로 바뀐다. 새로고침하지 않는다.
실행 중같은 적재에 running 실행이 있을 때
실행 중
시작 시각상태룰행동
실행 중5 / 8열기

이 GL에 실행 중인 테스트가 있습니다.

GL 적재 행의 테스트 실행 단추 자리에 이 문구가 대신 보인다(:run_in_progress).
실행 일부 실패룰 하나 이상 failed
일부 실패
상태룰적중 전표
일부 실패7 / 8249
실패한 룰은 0점으로 계산한다. 어떤 룰이 실패했는지는 S4에서 본다.
멤버 추가 오류가입하지 않은 이메일
이미 멤버인 이메일이면 같은 자리에 "이미 이 감사 대상의 멤버입니다.". 그 밖의 changeset 오류는 역할 칸에 "역할을 다시 선택하세요.".
멤버 추가 성공flash와 새 행
멤버를 추가했습니다.
추가된 멤버
박검토park.reviewer@example.com검토자
폼은 비우고 역할은 검토자로 되돌린다. 추가된 사람의 화면에는 다음 목록 조회 때 S1에 나타난다.

S2.3 이벤트

S2 이벤트
트리거처리성공실패감사 로그
mountget_engagement!/2. 연결된 mount에서만 Engagements.subscribe/2화면 표시비멤버는 Ecto.NoResultsError, 404-
추가 (phx-submit="add_member")Engagements.add_member/4flash "멤버를 추가했습니다.", 표에 행 추가:forbidden flash. :user_not_found와 unique 위반은 이메일 칸. 그 밖은 역할 칸engagement.member_added (user_id, role)
GL 불러오기navigate S3---
감사 로그navigate S6---
테스트 실행 (제안: phx-click="start_run")Testing.start_test_run(scope, import, :all)S4로 push_navigate:import_not_ready "적재가 끝난 GL만 테스트할 수 있습니다.", :run_in_progress "이 GL에 실행 중인 테스트가 있습니다.", :forbidden "테스트를 실행할 권한이 없습니다."test_run.started
import_updatedLedger.get_import!/2, stream_insert at: 0행 상태와 합계가 바뀐다--
test_run_updated (제안)Testing.get_test_run!/2, stream_insert실행 행과 통계가 바뀐다--
열기navigate S4---

S3. GL 불러오기

경로
/engagements/:id/imports/new
LiveView
ImportLive.New
권한
작성자 (:import_ledger)
데이터
Ledger.create_import/3, Ledger.generate_sample_import/3, Ledger.get_import!/2, Testing.start_test_run/3
구독
engagement:#{id}
구현
구현됨 (WBS 2.3)

S3.1 기본 화면

S3/engagements/:id/imports/newImportLive.New, CSV 업로드 탭, 파일을 고르기 전
Tickmark
지금 보는 사람: 김작성(나)kim.writer@example.com

GL 불러오기

CSV 파일을 끌어 놓거나 선택.csv 1개, 20MB, 100,000행까지

컬럼 16개는 이름으로 찾으므로 순서는 상관없습니다. UTF-8(BOM 허용), 쉼표 구분, 금액은 원 단위 정수입니다.

파일을 선택하면 업로드할 수 있습니다
S3 영역
번호영역내용과 데이터권한, 조건
이동 경로와 제목감사 대상 / 회사명 FY연도 / GL 불러오기. 제목 "GL 불러오기".-
적재 방식 탭CSV 업로드와 합성 GL 생성. 탭은 patch로 ?source=upload, ?source=generated를 바꾼다 (제안). 진행 중인 업로드가 있으면 탭을 바꿔도 취소하지 않는다.-
파일 놓는 곳allow_upload(:gl, accept: ~w(.csv), max_entries: 1, max_file_size: 20_000_000). 끌어 놓기와 파일 선택 둘 다. 키보드로는 Enter, Space로 파일 선택 창을 연다.-
형식 안내컬럼 규칙 요약. 전체 계약은 도메인 문서의 GL 계약.-
업로드파일이 없으면 비활성이고 이유를 옆에 둔다. 누르면 업로드를 마치고 적재를 맡긴 뒤 같은 화면에 결과 패널을 연다.-

S3.2 적재 실패 결과

검증에서 하나라도 오류가 나면 아무 전표도 적재하지 않는다. 결과 패널은 업로드 폼 아래에 붙는다.

S3/engagements/:id/imports/new검증 실패, 오류 4건과 경고 1건
Tickmark
지금 보는 사람: 김작성(나)kim.writer@example.com
gl_2025_draft.csv실패
, 해시 b71d02e5
적재 오류
행컬럼코드내용
214-없음both_sides한 행에는 차변과 대변 중 하나만 있어야 합니다
1,032entered_atinvalid_date날짜 또는 일시 형식이 잘못되었습니다
3,377-없음unbalanced_entry전표의 차변 합계와 대변 합계가 다릅니다
5,881entry_numbernon_contiguous_entry같은 전표의 행이 이어져 있지 않습니다
경고 1건
적재 경고
행컬럼코드내용
7,410fiscal_periodperiod_mismatch전기일의 월과 회계기간이 다릅니다
S3 결과 영역
번호영역내용과 데이터권한, 조건
적재 머리파일명(업로드) 또는 "합성 GL, 시드 N"(생성), 상태 배지, 등록 시각, 해시 앞 8자.-
요약 문구오류 수(error_count)를 넣은 고정 문구.-
오류 표행, 컬럼, 코드, 내용. 저장된 순서(파일 순서). 최대 100건이고, error_count가 더 크면 표 아래에 "그 밖에 N건". 내용은 코드별 고정 문구다.-
경고 표접힌 채로 둔다. 경고만 있으면 적재는 성공하고 완료 요약 아래에 같은 모양으로 붙는다.-

"행"은 머리글을 1로 센 CSV 레코드 순번이다. 따옴표 안의 줄바꿈은 행으로 세지 않는다. 컬럼이 없는 코드(both_sides, unbalanced_entry, parse_error, too_many_rows, unknown_column)는 컬럼 칸에 "-"를 쓴다. 시안은 이 자리에 debit, entry_number를 썼고, 경고 컬럼을 posting_date로 썼다. 구현 기준은 이 표다.

S3.3 상태 변형

파일을 고름업로드 전, 진행률은 브라우저가 올리는 동안
gl_2025_v2.csv8.4MB
62%
live_file_input의 entry 진행률. 취소는 cancel_upload.
파일 거부브라우저에서 바로 막힌다. 서버에 적재 행이 생기지 않는다
CSV 파일을 끌어 놓거나 선택
:too_large. :not_accepted는 ".csv 파일만 올릴 수 있습니다.", :too_many_files는 "파일은 한 번에 1개만 올릴 수 있습니다.". 서버의 :file_too_large도 첫 문구를 쓴다.
대기적재 행이 생기고 작업이 큐에 있다
gl_2025_v2.csv대기

앞의 적재가 끝나면 시작합니다. 이 화면을 떠나도 계속됩니다.

ingest 큐. 시작하면 import_updated를 받아 적재 중으로 바뀐다.
적재 중ImportWorker가 검증하고 넣는 중
gl_2025_v2.csv적재 중

검증하고 적재하는 중입니다. 이 화면을 떠나도 계속됩니다.

행 단위 진행률은 보내지 않는다. 시작과 끝만 알린다.
완료업로드 CSV, 경고 1건
gl_2025_v2.csv완료
, 해시 c40e9b12
전표8,338건
라인19,797건
경고1건
룰 8개를 실행하고 S4로 이동합니다
차변 합계 = 대변 합계 통계는 아래 합성 GL 완료와 같다. 경고 표는 접힌 채로 그 아래에 붙는다.
오류가 100건을 넘음저장은 100건, 수는 전체
오류 표 끝
8,912-없음unbalanced_entry전표의 차변 합계와 대변 합계가 다릅니다

그 밖에 1,240건

요약 문구도 전체 수(1,340건)를 쓴다.
시스템 오류로 실패worker_failed, file_missing
gl_2025_v2.csv실패
오류 표가 없다(error_count 0). 원인은 서버 로그에만 남긴다. 작업은 3번까지 다시 시도한 뒤 실패로 바꾼다.
검토자가 주소로 들어옴:import_ledger 없음 (제안)

감사 대상누리정밀 주식회사 FY2025

mount에서 S2로 push_navigate하고 flash를 남긴다. 비멤버는 404.

S3.4 합성 GL 생성

S3/engagements/:id/imports/new합성 GL 생성 탭, 생성이 끝난 상태
Tickmark
지금 보는 사람: 김작성(나)kim.writer@example.com

감사 대상누리정밀 주식회사 FY2025GL 불러오기

GL 불러오기

같은 시드면 같은 GL이 나옵니다. 룰 테스트의 기대 건수는 시드 20251231 기준입니다.

합성 GL, 시드 20251231완료
, 해시 3f9a0c7e
전표8,341건
라인19,804건
차변 합계 = 대변 합계386,214,905,000원
경고-0건
룰 8개를 실행하고 S4로 이동합니다
S3 합성 GL 영역
번호영역내용과 데이터권한, 조건
시드정수만. 비우면 Generator.default_seed/0(20251231). 정수가 아니면 "정수로 입력하세요.".-
재현성 안내같은 시드면 같은 GL. 도메인 문서 7장.-
생성generate_sample_import/3에 seed:만 넘긴다. 생성기는 라인 수를 받지 않으므로 시안의 "목표 라인 수" 칸은 없앤다.-
결과 머리출처 이름은 "합성 GL, 시드 N". 저장 파일명은 synthetic-gl-seed-N.csv지만 화면에는 쓰지 않는다.-
테스트 실행완료 상태에서만. S2의 같은 단추와 같은 이벤트.:start_test_run

S3.5 오류 코드와 문구

적재 오류 코드
코드행컬럼내용비고
missing_column1빠진 컬럼 이름필수 컬럼이 없습니다빈 파일이면 16건
unknown_column1-없음알 수 없거나 겹친 컬럼이 있습니다헤더 이름은 다시 쓰지 않는다
required해당 행해당 컬럼필수 값이 비어 있습니다-
too_long해당 행해당 컬럼값이 너무 깁니다적요 500자, 그 밖 200자
invalid_value해당 행source허용되지 않는 값입니다manual, system만
invalid_date해당 행해당 컬럼날짜 또는 일시 형식이 잘못되었습니다일시는 오프셋 없는 로컬 시각
invalid_number해당 행해당 컬럼금액은 원 단위 정수여야 합니다회계기간 1-13 밖도 이 코드. 문구 확인 필요
both_sides해당 행-없음한 행에는 차변과 대변 중 하나만 있어야 합니다둘 다 0이어도 이 코드
unbalanced_entry전표의 첫 행-없음전표의 차변 합계와 대변 합계가 다릅니다-
inconsistent_header다른 행마다다른 컬럼마다같은 전표의 헤더 값이 행마다 다릅니다-
non_contiguous_entry돌아온 행entry_number같은 전표의 행이 이어져 있지 않습니다-
duplicate_line_number해당 행line_number같은 전표에 같은 라인 번호가 있습니다-
too_many_rows100,002-없음행 수가 상한(100,000)을 넘었습니다이 오류 1건만 남는다
parse_error멈춘 행-없음 또는 해당 컬럼CSV 형식을 읽을 수 없습니다따옴표, 칸 수, 인코딩, NUL
period_mismatch (경고)전표의 첫 행fiscal_period전기일의 월과 회계기간이 다릅니다13(결산조정)은 12월이면 경고 없음

디자인 시스템 문서 5.4의 표에는 unknown_column, too_long, invalid_value, duplicate_line_number가 없다. 이 표로 맞춘다.

S3.6 이벤트

S3 이벤트
트리거처리성공실패감사 로그
mountget_engagement!/2, authorize(:import_ledger), allow_upload. 연결된 mount에서 구독폼 표시비멤버 404. 검토자는 S2로 (제안)-
파일 고름 (phx-change="validate")upload_errors/2이름, 크기, 진행률거부 문구-
선택 취소cancel_upload/3폼을 비운다--
업로드 (phx-submit="save")consume_uploaded_entries 안에서 Ledger.create_import/3결과 패널(대기):forbidden flash, :file_too_large 문구ledger_import.created (source_kind, original_filename, byte_size, sha256)
생성 (phx-submit="generate")Ledger.generate_sample_import/3결과 패널(대기)시드 칸 오류ledger_import.created
import_updated보고 있는 적재면 get_import!/2패널이 적재 중, 완료, 실패로 바뀐다-시스템이 ledger_import.succeeded 또는 ledger_import.failed (reason)
테스트 실행Testing.start_test_run(scope, import, :all)S4로 push_navigateS2와 같은 문구test_run.started

S4. 테스트 실행

경로
/engagements/:id/runs/:run_id
LiveView
TestRunLive.Show
권한
멤버 (:view). 트리아지 요청은 작성자
데이터
Testing.get_test_run!/2, Testing.list_findings/3, Triage.request_triage/2
구독
test_run:#{run_id}, engagement:#{id}
구현
구현됨 (WBS 2.3). AI 위험도, 작성자 판단 열은 M3, M4에서 채운다

S4.1 기본 화면

실행이 끝나고 트리아지를 한 번 요청한 뒤의 화면이다. 배지 종류가 모두 보이도록 골랐다.

S4/engagements/:id/runs/:run_idTestRunLive.Show, 검토 큐 탭, 김작성(작성자)
Tickmark
지금 보는 사람: 김작성(나), 이작성, 박검토kim.writer@example.com

감사 대상누리정밀 주식회사 FY2025테스트 실행

테스트 실행

합성 GL, 시드 20251231기준 점수 25실행 김작성

완료 8 / 8 룰, 적중 전표 265건 -
100%
  • 완료주말/공휴일 전기46건
  • 완료업무시간 외 입력42건
  • 완료라운드 금액68건
  • 완료기말 수기 전표45건
  • 완료승인 통제 이탈40건
  • 완료증빙일 괴리29건
  • 완료희귀 계정 조합16건
  • 완료중복 전표 의심15건
AI 트리아지검토 큐 52건: 완료 50, 분석 중 1, 실패 1, 미요청 0
모든 항목을 요청했습니다. 실패 항목은 전표 화면에서 다시 요청합니다

1회 최대 100건까지 요청합니다. 요청하면 감사 로그에 요청자가 남습니다. 결과가 오는 대로 표의 AI 열이 바뀝니다.

검토 큐, 점수 내림차순
전표전기일금액(원)점수적중 룰 AI 판단검토
JE-0001232025-12-3040,000,00055상기말 수기, 라운드, 승인 이탈높음미판단-검토 전
JE-0044152025-12-3130,000,00055상기말 수기, 라운드, 승인 이탈높음예외검토 대기
JE-0029612025-08-1425,000,00050상증빙일, 라운드, 승인 이탈중간이상 없음승인
JE-0077022025-12-2918,350,00050상희귀 조합, 기말 수기, 시간 외높음추가 확인 필요반려
JE-0018462025-03-1112,840,00025중승인 이탈낮음미판단-검토 전
JE-0033072025-06-174,730,00025중승인 이탈분석 중미판단-검토 전
JE-0055902025-09-221,260,00025중승인 이탈AI 실패미판단-검토 전
JE-0061182025-10-042,480,00025중휴일, 시간 외낮음이상 없음승인
S4 영역
번호영역내용과 데이터권한, 조건
제목과 개요"테스트 실행" + 시작 시각(KST). 아래 줄에 적재 출처, 기준 점수(triage_threshold 스냅샷), 실행한 사람.-
룰 진행룰 8개. 동시에 최대 4개(rules 큐). 상태 문구는 aria-live="polite". 끝난 뒤에도 룰별 적중 수가 남는다. 시각은 시작과 끝, 초까지.-
트리아지 바검토 큐의 triage_status 집계: 완료(succeeded), 분석 중(queued), 실패(failed), 미요청(none). 미요청이 있으면 단추가 켜진다. 실행이 끝나기 전에는 비활성.요청은 :request_triage. 검토자에게는 비활성과 이유
목록 탭검토 큐(점수가 기준 이상), 경미 적중(기준 미만). 숫자는 건수. 탭은 URL의 ?tab=queue, ?tab=minor (제안).-
검토 상태 필터전체, 미판단(open), 검토 대기(concluded), 승인(approved), 반려(rejected). 한 번에 하나. URL ?status=. 숫자는 탭 안의 건수.-
발견 표전표번호(S5 링크), 전기일, 금액(차변 합계), 점수, 적중 룰(짧은 이름, 전체 이름은 title). 점수 내림차순, 같으면 전표번호 오름차순.-
AI 열높음, 중간, 낮음, 분석 중, AI 실패, "-"(미요청). 검토 큐 탭에만 있다.-
판단, 검토 열판단: 미판단, 이상 없음, 추가 확인 필요, 예외. 검토: "-", 검토 대기, 승인, 반려.-
더 보기50건씩 이어 그린다 (제안). 계약의 list_findings/3에는 페이지 옵션이 없다 (C-08).-

S4.2 상태 변형

실행 중5 / 8, 룰 3개가 돌고 있다
실행 중 5 / 8 룰 시작
62%
  • 완료주말/공휴일 전기46건
  • 완료업무시간 외 입력42건
  • 완료라운드 금액68건
  • 완료기말 수기 전표45건
  • 완료승인 통제 이탈40건
  • 실행 중증빙일 괴리집계 중
  • 실행 중희귀 계정 조합집계 중
  • 실행 중중복 전표 의심집계 중
룰이 모두 끝나면 검토 큐가 열립니다.
점수는 모든 룰이 끝난 뒤 한 번에 계산한다. 그 전에는 표를 그리지 않는다. 트리아지 바는 "실행이 끝나면 요청할 수 있습니다"와 함께 비활성.
일부 실패룰 1개 failed, 1개 skipped
일부 실패 6 / 8 룰, 적중 전표 231건
100%
  • 완료주말/공휴일 전기46건
  • 완료업무시간 외 입력42건
  • 완료라운드 금액68건
  • 완료기말 수기 전표45건
  • 완료승인 통제 이탈40건
  • 실패증빙일 괴리실행 실패, 로그 확인
  • 미실행희귀 계정 조합미실행
  • 완료중복 전표 의심15건
룰 1개를 실행하지 못했습니다. 그 룰은 점수에 넣지 않았습니다. 같은 GL로 다시 실행하면 처음부터 다시 계산합니다.
미실행(skipped)은 실패가 아니다. 모집단이 500건 미만이면 희귀 계정 조합을 돌리지 않는다. 그 이유는 title로 보인다. 실패 이유는 감사 로그(test_run_rule.failed)에 있다.
트리아지 요청 전작성자, 미요청 52건
AI 트리아지검토 큐 52건 중 AI 미요청 52건

1회 최대 100건까지 요청합니다. 요청하면 감사 로그에 요청자가 남습니다.

AI는 위험도와 추가 절차를 제안만 한다. 판단 배지는 바뀌지 않는다.
요청 직후52건이 queued
AI 트리아지 52건을 요청했습니다.
AI 트리아지검토 큐 52건: 완료 0, 분석 중 52, 실패 0, 미요청 0
요청 직후 행
JE-0001232025-12-3040,000,00055상기말 수기, 라운드, 승인 이탈분석 중미판단-검토 전
llm 큐 동시 2개. finding_updated가 올 때마다 그 행의 AI 배지와 바의 숫자가 바뀐다.
100건을 넘는 검토 큐미요청 140건
검토 큐 140건 중 AI 미요청 140건

이번에는 점수가 높은 순으로 100건을 요청합니다. 남은 40건은 다시 눌러 요청합니다.

점수 내림차순, 같으면 금액 내림차순으로 100건. 한 번에 큰 비용이 나가지 않게 막는 상한이다.
검토자가 볼 때박검토
검토 큐 52건 중 AI 미요청 52건
작성자만 요청할 수 있습니다
표와 필터는 작성자와 같다. 검토자는 주로 "검토 대기" 필터로 들어간다.
경미 적중 탭점수 25 미만, AI 열 없음
경미 적중
전표전기일금액(원)점수적중 룰판단
JE-0022302025-12-313,120,00020경미기말 수기미판단
JE-0008712025-02-09640,00015경미휴일이상 없음
JE-0011022025-04-305,000,00010경미라운드미판단
트리아지 바도 없다. 결론 제출은 경미 적중에도 할 수 있다.
필터 결과 없음반려 0건
조건에 맞는 전표가 없습니다.
필터 초기화는 ?status=를 지운다. 탭은 그대로 둔다.

S4.3 이벤트

S4 이벤트
트리거처리성공실패감사 로그
mountget_test_run!/2(룰 preload). 연결된 mount에서 subscribe_test_run/2, Engagements.subscribe/2화면 표시비멤버, 다른 감사 대상의 실행은 404-
탭, 필터 (patch)handle_params에서 list_findings(scope, run_id, %{status, min_score})표를 다시 그린다--
rule_progress%{rule_id, status, hit_count, done, total}로 그 룰 줄과 진행률만 바꾼다진행 표시-시스템이 룰 실패 때 test_run_rule.failed
run_completedget_test_run!/2, list_findings/3진행 패널은 요약으로, 표가 열린다-시스템이 test_run.completed
AI 트리아지 요청Triage.request_triage(scope, run_id)%{enqueued: n}로 flash "AI 트리아지 n건을 요청했습니다.". n이 0이면 info "새로 요청할 항목이 없습니다.":forbidden flash "AI 트리아지를 요청할 권한이 없습니다."ai_triage.requested (subject는 test_run)
finding_updated보이는 행이면 다시 읽어 stream_insert, 트리아지 바 다시 집계배지가 바뀐다-AI가 ai_triage.completed, 실패면 ai_triage.failed
전표번호 누름navigate S5, 현재 ?tab, ?status를 붙인다S5 큐가 같은 목록을 이어받는다--

S5. 전표 검토

경로
/engagements/:id/findings/:finding_id
LiveView
FindingLive.Show
권한
멤버 (:view). 결론은 작성자, 승인과 반려는 검토자
데이터
Testing.get_finding!/2, Triage.request_finding_triage/2, Review.submit_conclusion/3, Review.decide/4
구독
engagement:#{id}, engagement:#{id}:presence
구현
자리만 있다

S5.1 기본 화면

작성자가 AI 제안을 받은 뒤 아직 결론을 내지 않은 전표다. 위에서 아래 순서가 판단 순서다: 사실, 룰 근거, AI 제안(점선), 사람 판단(실선), 서명.

S5/engagements/:id/findings/:finding_idFindingLive.Show, JE-000123, 김작성(작성자)

감사 대상누리정밀 주식회사 FY2025테스트 실행 2026-10-19 10:02JE-000123

JE-000123

55상높음미판단-검토 전
이 전표를 보는 사람: 김작성(나), 박검토

전표 사실

전기일
2025-12-30
증빙일
2025-12-24
입력 일시 (KST)
2025-12-30 17:42:10
회계기간
12
입력자
acct03
승인자
acct03입력자와 같음
구분
수기기말 수기 전표 근거
거래처
V012
적요
선급금 지급

전표 행

JE-000123 전표 행
행계정적요차변(원)대변(원)
L11310 선급금선급금 지급40,000,000-0
L21020 보통예금선급금 지급-040,000,000
차대 일치40,000,00040,000,000

룰 적중

  • +20
    기말 수기 전표period_end_manual

    결산일(2025-12-31) 3일 이내에 수기로 입력된 전표입니다.

    근거 값
    posting_date
    2025-12-30
    source
    manual
    period_end_window_days
    3
  • +10
    라운드 금액round_amount

    금액 40,000,000이 1,000,000 단위로 떨어지고 기준(10,000,000) 이상입니다.

    근거 값
    entry_amount
    40,000,000
    round_unit
    1,000,000
    round_min_amount
    10,000,000
  • +25
    승인 통제 이탈approval_control_gap

    승인자가 입력자와 같습니다 (acct03).

    근거 값
    entered_by
    acct03
    approved_by
    acct03
    entry_amount
    40,000,000
    approval_min_amount
    1,000,000

점수55 = 20 + 10 + 25

AI 제안높음

이 제안은 판단이 아닙니다. 결론은 작성자가 입력합니다.

근거

결산일 하루 전에 수기로 입력된 40,000,000원 선급금 지급 전표이며, 입력자와 승인자가 같은 사용자(acct03)로 기록되어 있습니다. 금액이 1,000,000원 단위로 떨어지고 수행중요성(225,000,000원)의 약 18% 수준입니다. 기말 수기 입력, 라운드 금액, 승인 통제 이탈이 겹쳐 있어 통제 우회 가능성을 배제하기 위한 추가 절차가 필요해 보입니다. 거래의 실재성과 승인 경위는 이 전표만으로 확인되지 않습니다.

추가 절차 제안

  1. 자기승인 경위를 입력자와 승인 권한자에게 질문하고 승인 권한표와 대조합니다.확보할 증거: 결재 문서, 승인 권한표
  2. 선급금 지급 근거를 계약 조건과 대사합니다.확보할 증거: V012 계약서, 지급 요청서, 이체 확인서
  3. 기말 이후 원재료 대체 여부를 확인합니다.확보할 증거: 2026년 1-2월 입고 내역, 대체 전표

AI가 판단에 부족하다고 본 정보

  • V012와의 계약 조건
  • 선급금 대체 예정 시기
생성 정보
model
claude-opus-5-5
prompt_version
triage-v1
input_hash
9c41e2b7
inserted_at
2026-10-19 10:43:18 KST
작성자 판단
AI 제안을 본 상태로 제출하면 그 제안이 결론에 연결됩니다. Ctrl + Enter로 제출0 / 2000

이력

아직 결론이 없습니다.

작성자는 승인, 반려할 수 없습니다 (직무분리)

S5 영역
번호영역내용과 데이터권한, 조건
검토 큐S4의 탭과 필터(?tab, ?status)를 이어받은 목록. 현재 항목은 aria-current. "다음 미판단"은 현재 항목 뒤의 첫 open 항목, 없으면 비활성.-
헤더전표번호, 점수, AI 위험도, 판단, 검토 배지. 오른쪽은 이 전표를 보는 사람(presence view = "finding:#{id}"). 상단 바의 presence는 이 화면에서 쓰지 않는다.-
전표 사실전기일, 증빙일, 입력 일시, 회계기간, 입력자, 승인자, 구분, 거래처, 적요. 룰 근거가 된 값은 경고 아이콘과 sr-only 설명. 적요와 거래처는 원문 그대로 이스케이프해 보인다.-
전표 행행, 계정(코드 + 이름), 적요, 차변, 대변. 합계 줄과 "차대 일치". 룰이 특정 행을 가리키면 그 행을 강조한다.-
룰 적중rule_results. 가중치, 이름, rule_id, 사람이 읽는 이유, 근거 값(접힘). 맨 아래 점수 식.-
AI 제안최신 ai_triages 1건. 위험도, 근거, 추가 절차(1-4개), 부족한 정보(0-5개), 생성 정보(접힘). response_model이 model과 다르면 둘 다 보인다. 점선 상자.요청 단추는 작성자
작성자 판단conclusion_form. 라디오 3개는 미리 고르지 않는다. 코멘트 1-2000자 필수. 숨은 ai_triage_id는 화면에 보인 최신 제안 id(없으면 빈 값). 실선 상자.edit: 작성자 + open, concluded, rejected. read_only: 검토자. locked: approved
이력결론과 검토 결정을 시간순으로. 이전 결론도 지우지 않는다. 비면 "아직 결론이 없습니다."-
서명 바화면 아래 고정. can_decide는 서버가 계산한다. 이유 문구는 aria-describedby로 단추에 연결.결정: 검토자 + concluded

S5.2 상태 변형

AI 제안 카드

AI 미요청작성자, triage_status none
AI 제안-미요청

룰 근거와 전표 사실을 바탕으로 위험도, 추가 절차, 부족한 정보를 제안합니다. 결론은 내리지 않습니다.

요청자가 감사 로그에 남습니다
검토자에게는 단추 없이 "작성자가 요청하면 여기에 보입니다."만 보인다.
분석 중queued
AI 제안분석 중
결과가 오면 이 카드만 다시 그린다. 쓰고 있던 판단과 코멘트는 그대로 둔다.
AI 실패failed
AI 제안AI 실패

AI 제안을 받지 못했습니다.

판단은 AI 제안 없이도 진행할 수 있습니다.

오류 원문(HTTP 상태, stop_reason)은 보이지 않는다. 원인은 감사 로그의 ai_triage.failed로 본다.
이미 요청됨:already_queued
이미 AI가 분석 중입니다.
두 탭에서 동시에 누른 경우다. 카드는 분석 중으로 바뀐다.
경미 적중 전표JE-002230, 점수 20
AI 제안-미요청

점수가 기준(25)보다 낮아 AI 트리아지 대상이 아닙니다.

결론은 받는다. 큐 머리는 "경미 적중 213건". 건별 요청 함수는 기준 미만을 거부하지 않는다 (C-09).

작성자 판단

검증 오류판단 없음, 코멘트 빈 값
작성자 판단

판단을 선택하세요

코멘트를 입력하세요 (1-2000자)

changeset 오류를 필드 아래에 보인다. 영어 기본 문구를 쓰지 않는다 (C-06과 같은 문제).
제출 중phx-submit-loading

작성자 판단

예외, 코멘트 64자

두 번 눌러 결론이 두 건 쌓이지 않게 단추를 잠근다.
제출 직후작성자, concluded
결론을 제출했습니다. 검토자에게 검토 대기로 보입니다.

작성자 판단

예외검토 대기

선급금 지급 결재 문서가 없고 승인자가 입력자와 같습니다. 대체 전표도 확인되지 않아 예외로 판단합니다.

김작성,

concluded에서 다시 내면 새 결론이 쌓이고 검토자는 최신 결론을 결정한다. "다시 쓰기"를 눌러야 입력란이 열린다 (제안. 디자인 문서 4.2는 바로 edit).
반려된 항목이작성, JE-007702 rejected
반려 사유, 박검토,

급여대장과 대사한 결과를 받은 뒤 다시 제출해 주세요. 지금 코멘트만으로는 추가 절차의 범위를 알 수 없습니다.

새 판단
이전 결론은 이력에 남는다. 새로 내면 rejected에서 concluded로 돌아간다.
승인된 항목JE-002961 approved

작성자 판단

이상 없음

7월 계약분의 8월 지급 전표이며 증빙일은 계약일로 입력되었습니다. 승인자 공란은 전자결재 승인 기록(mgr01)으로 확인했습니다.

승인된 항목은 수정할 수 없습니다

입력란이 없다. 서버도 :invalid_state로 막는다.
다른 사람이 먼저 제출finding_updated, 입력 중
이작성이 방금 결론을 제출했습니다. 제출하면 새 결론으로 남습니다.
쓰던 내용은 지우지 않는다. 헤더 배지와 이력만 다시 읽는다.

서명 바와 이력

결정 가능박검토, JE-004415 concluded

작성자 판단

예외

선급금 지급 결재 문서가 없고 승인자가 입력자와 같습니다. 2026년 1-2월 대체 전표도 확인되지 않아 예외로 판단합니다.

김작성,

작성자 판단예외김작성,
검토자에게 판단은 read_only다.
승인 확인2단계
작성자 판단예외
승인하시겠습니까?
같은 줄에서 확인한다. 모달을 쓰지 않는다.
반려 사유 없음changeset 오류

반려 사유를 입력하세요

DB CHECK comment_required_on_reject가 공백 사유도 막는다.
결론 전검토자, open

작성자 결론이 제출되지 않았습니다

승인됨approved
승인박검토,
단추가 없다.
상태가 바뀜:invalid_state
다른 검토자가 먼저 결정한 경우다. 결정은 결론 1건당 1건(unique conclusion_id).
검토 중에 결론이 바뀜박검토, finding_updated
김작성이 결론을 다시 제출했습니다. 최신 결론을 확인한 뒤 결정하세요.
작성자 판단추가 확인 필요김작성,
승인 확인 단계는 처음으로 되돌린다. 그래도 decide/4는 검토자가 본 결론을 받지 않아, 다시 그리기 전에 누른 승인은 새 결론에 붙는다 (C-11).
이력JE-007702, 반려 후
  1. 이작성결론 제출추가 확인 필요

    12월 급여 미지급분 계상으로 보이나 급여대장을 받지 못했습니다. 인사팀에 요청했습니다.

  2. 박검토검토반려

    급여대장과 대사한 결과를 받은 뒤 다시 제출해 주세요. 지금 코멘트만으로는 추가 절차의 범위를 알 수 없습니다.

다시 내면 아래에 새 줄이 붙고 위 줄은 그대로 남는다.

S5.3 좁은 화면

폭 375-420px. 큐는 접고, 서명 바는 아래에 고정한다.

S5375px작성자, 큐 접힘
Tickmark
kim.writer@example.com
검토 큐 52건, JE-000123 (1/52)

JE-000123

55상높음미판단

전표 사실

전기일
2025-12-30
증빙일
2025-12-24
입력자
acct03
승인자
acct03입력자와 같음
적요
선급금 지급
JE-000123 전표 행
계정차변(원)대변(원)
1310 선급금40,000,000-0
1020 보통예금-040,000,000
AI 제안높음

이 제안은 판단이 아닙니다. 결론은 작성자가 입력합니다.

결산일 하루 전에 수기로 입력된 40,000,000원 선급금 지급 전표이며, 입력자와 승인자가 같습니다.

작성자 판단

작성자는 승인, 반려할 수 없습니다

S5.4 이벤트

S5 이벤트
트리거처리성공실패감사 로그
mountget_finding!/2. 연결된 mount에서 presence track(view: "finding:#{id}"), Engagements.subscribe/2화면 표시비멤버, 다른 감사 대상의 항목은 404-
AI 제안 요청Triage.request_finding_triage(scope, id)카드가 분석 중으로:already_queued info "이미 AI가 분석 중입니다.", :forbidden "AI 제안을 요청할 권한이 없습니다."ai_triage.requested (subject는 finding)
판단 입력 (validate)changeset만 검사글자 수 갱신필드 오류-
결론 제출 (Ctrl+Enter 포함)Review.submit_conclusion(scope, finding, %{outcome, comment, ai_triage_id})flash "결론을 제출했습니다. 검토자에게 검토 대기로 보입니다."changeset은 필드 오류, :invalid_state "승인된 항목은 수정할 수 없습니다.", :forbidden "결론을 제출할 권한이 없습니다."conclusion.submitted
승인 확인Review.decide(scope, finding, :approved, nil)서명 바가 승인으로:invalid_state "이 항목은 이미 처리되었습니다. 최신 상태로 다시 불러왔습니다.", :forbidden "승인, 반려할 권한이 없습니다."review.approved
반려 확정Review.decide(scope, finding, :rejected, comment)서명 바가 반려로, 이력에 사유changeset "반려 사유를 입력하세요", 나머지는 승인과 같다review.rejected
:self_review본인 결론을 결정하려 할 때-flash "본인이 제출한 결론은 승인, 반려할 수 없습니다."review.denied. 역할이 바뀐 사용자만 닿는다 (C-10)
finding_updated (같은 항목)get_finding!/2 다시 읽기. 작성자가 입력 중이면 경고만, 검토자는 확인 단계를 되돌린다배지, AI 카드, 이력 갱신--
presence_diff같은 view만 거른다헤더 아바타 갱신--
다음 미판단push_navigate, ?tab, ?status 유지다음 항목없으면 단추 비활성-

S6. 감사 로그

경로
/engagements/:id/audit
LiveView
AuditLive.Index
권한
멤버 (:view). 모두 읽기 전용
데이터
AuditTrail.list_events(scope, engagement_id, limit:, before_seq:, subject:, actor_type:)
구독
engagement:#{id} (audit_appended)
구현
자리만 있다

S6.1 기본 화면

검토가 한창인 11:21 무렵의 로그다. 사람, AI, 시스템 행위자가 섞여 있다. 시안의 #74 review.denied는 지금 계약으로는 생길 수 없어서 승인 이벤트로 바꿨다 (C-10).

S6/engagements/:id/auditAuditLive.Index, 필터 없음, 김작성
Tickmark
지금 보는 사람: 김작성(나)kim.writer@example.com

감사 대상누리정밀 주식회사 FY2025감사 로그

감사 로그

읽기 전용. 기록은 수정하거나 삭제할 수 없습니다.

  1. #80김작성결론 제출conclusion.submitted
    대상JE-004415예외
    payload
    conclusion_id
    5e0b9c7a-3d2f-4b61-9a8e-1c7f4d2b6e90
    outcome
    "exception"
  2. #79김작성AI 제안 요청ai_triage.requested
    대상JE-003307다시 요청
  3. #78triage:claude-opus-5-5AI 제안 실패ai_triage.failed
    대상JE-005590
  4. #77김작성AI 제안 요청ai_triage.requested
    대상JE-005590다시 요청
  5. #76박검토반려review.rejected
    대상JE-007702급여대장과 대사한 결과를 받은 뒤 다시 제출해 주세요.
  6. #75이작성결론 제출conclusion.submitted
    대상JE-000958이상 없음
    payload
    conclusion_id
    a2c4e6f8-1b3d-4f5a-8c7e-9d0b2a4c6e81
    outcome
    "no_exception"
  7. #74박검토승인review.approved
    대상JE-003871
  8. #73이작성결론 제출conclusion.submitted
    대상JE-006640추가 확인 필요
    payload
    conclusion_id
    7f1e3d5c-9b2a-4c8e-a6f4-0d2b4e6a8c13
    outcome
    "needs_follow_up"
  9. #72김작성결론 제출conclusion.submitted
    대상JE-003871예외
    payload
    conclusion_id
    c8a0e2b4-6d1f-4a3c-9e5b-7f2d4a6c8e05
    outcome
    "exception"
  10. #71박검토승인review.approved
    대상JE-005033
  11. #70박검토승인review.approved
    대상JE-001207
  12. #69박검토승인review.approved
    대상JE-002961
S6 영역
번호영역내용과 데이터권한, 조건
헤더제목과 읽기 전용 안내. 수정, 삭제 단추는 어디에도 없다. DB 트리거도 UPDATE와 DELETE를 거부한다.-
행위자 필터전체, 사람, AI, 시스템. aria-pressed로 하나만 켠다. URL은 ?actor=(제안, 값은 user, agent, system), 함수에는 actor_type:로 넘긴다.-
대상 필터전표번호를 받는다. 전표번호를 subject로 바꾸는 방법이 계약에 없다 (C-13). URL은 ?subject=(제안).-
새 이벤트audit_appended를 셀 뿐 목록에 바로 끼우지 않는다. 읽던 위치가 밀리지 않게 하려는 것이다. 0이면 숨긴다.-
이벤트 목록최신순. 번호(seq), 시각(KST, title은 UTC), 행위자(아이콘 + 이름), 한글 동작 이름과 action 코드, 대상 링크와 한 줄 요약, payload(접힘). 반려 사유 요약은 payload에 없어 지금은 보일 수 없다 (C-12).-
더 보기마지막 번호를 before_seq로 넘겨 50건씩 더 읽는다. 받은 수가 50보다 적으면 목록 끝이다.-

행위자 이름은 사람이면 표시 이름(C-05), AI면 triage:<response_model>, 시스템이면 작업 이름(ledger.import_worker, testing.rule_worker)이다. AI 응답 없이 실패한 경우 행위자가 agent인지 system인지는 계약 4.2에 "agent / system"으로만 적혀 있다 (확인 필요).

S6.2 상태 변형

AI만?actor=agent
  1. #78triage:claude-opus-5-5AI 제안 실패ai_triage.failed
    대상JE-005590
  2. #62triage:claude-opus-5-5AI 제안 완료ai_triage.completed
    대상JE-006640위험도 중간
  3. #61triage:claude-opus-5-5AI 제안 실패ai_triage.failed
    대상JE-003307
위험도 요약을 보이려면 payload에 risk_level이 있어야 한다 (C-12).
대상 하나?subject=JE-007702
  1. #76박검토반려review.rejected
    대상JE-007702
  2. #65이작성결론 제출conclusion.submitted
    대상JE-007702추가 확인 필요
  3. #24triage:claude-opus-5-5AI 제안 완료ai_triage.completed
    대상JE-007702
일괄 요청 #10은 subject가 test_run이라 전표 하나로 거르면 나오지 않는다. 누가 요청했는지 보려면 실행으로 거른다.
조건에 맞는 것이 없음?actor=system&subject=JE-000123
조건에 맞는 이벤트가 없습니다.
필터 없이 비는 일은 없다. 감사 대상을 만들 때 #1 engagement.created가 남는다.
새 이벤트 여러 건audit_appended 3번
#80까지 보는 중
누르면 맨 위부터 다시 읽고 목록 첫 줄로 포커스를 옮긴다. 필터가 걸려 있으면 메시지에 행위자와 대상이 없어 맞는 건수를 셀 수 없다. 그때는 숫자 없이 "새 이벤트가 있습니다"로 보인다 (C-13).
더 불러오는 중phx-click-loading
payload 펼침시스템 이벤트
  1. #7ledger.import_workerGL 적재 완료ledger_import.succeeded
    payload
    entry_count
    8341
    line_count
    19804
    total_debit
    "386214905000"
    total_credit
    "386214905000"
    warning_count
    0
  2. #5ledger.import_workerGL 적재 실패ledger_import.failed
    payload
    reason
    "validation_failed"
    error_count
    4
payload는 저장된 JSON 그대로 보인다. 키는 문자열, Decimal은 문자열이다. 금액 쉼표를 넣지 않는 것은 원문을 바꾸지 않기 위해서다.
처음 기록까지 봄before_seq=2
  1. #1김작성감사 대상 생성engagement.created
    payload
    client_name
    "누리정밀 주식회사"
    fiscal_year
    2025
    period_end_date
    "2025-12-31"
    performance_materiality
    "225000000"

처음 기록까지 모두 보았습니다.

더 보기 단추는 사라진다.

S6.3 이벤트

S6 이벤트
트리거처리성공실패감사 로그
mountlist_events(scope, id, limit: 50)를 stream에 넣는다. 연결된 mount에서 Engagements.subscribe/2목록 표시비멤버는 404-
필터 (patch)handle_params에서 actor_type:, subject:를 붙여 다시 읽고 stream을 비운다목록 교체, 새 이벤트 수 0--
audit_appended%{seq, action}. 보이는 첫 번호보다 크면 수를 1 올린다"새 이벤트 N건" 표시--
새 이벤트 누름list_events(scope, id, limit: 50), stream 비우고 다시 넣기첫 줄로 포커스--
더 보기list_events(..., before_seq: 마지막 seq, limit: 50), stream 뒤에 붙이기목록이 길어진다--
대상 링크finding은 S5, test_run은 S4, ledger_import는 S2 적재 이력과 같은 링크로 navigate해당 화면대상이 이 감사 대상 밖이면 404-
payload 펼침details만 연다. 서버 왕복 없음---

부록 A. 화면 문구

사용자에게 보이는 문구를 한곳에 모았다. 예외 이름, HTTP 상태, SQL 같은 내부 구현은 보이지 않는다. gettext에는 en만 있어서 지금 구현은 공통, S1, S2에서 영어 기본 문구를 보인다 (C-02, C-03, C-06). S3 오류 코드별 문구는 S3.5에 있다.

화면 문구
화면상황종류문구근거
공통연결 끊김배너인터넷 연결을 찾을 수 없습니다. 다시 연결하는 중LiveSocket 끊김
공통서버 오류배너서버에 문제가 생겼습니다. 다시 연결하는 중LiveView 프로세스 종료
공통없는 주소, 비멤버페이지페이지를 찾을 수 없습니다. 없는 주소이거나 내가 멤버가 아닌 감사 대상입니다.404
S1회사명 빈 값필드회사명을 입력하세요.changeset client_name
S1회계연도 범위 밖필드2000에서 2100 사이로 입력하세요.CHECK fiscal_year_range
S1결산일 없음필드결산일을 선택하세요.changeset period_end_date
S1수행중요성 0 이하필드0보다 큰 금액을 입력하세요.CHECK performance_materiality_positive
S1만듦flash감사 대상을 만들었습니다.:ok
S2멤버 추가flash멤버를 추가했습니다.:ok
S2없는 이메일필드가입한 사용자 중 이 이메일이 없습니다.사용자 조회 실패
S2권한 없음flash멤버를 추가할 권한이 없습니다.:forbidden
S3파일이 큼필드20MB보다 큰 파일은 올릴 수 없습니다.allow_upload :too_large
S3권한 없음flashGL을 불러올 권한이 없습니다.:forbidden
S3검증 실패경고GL을 적재하지 못했습니다. 아래 오류 N건을 고친 뒤 다시 올리세요. 파일의 원래 값은 화면에 다시 쓰지 않습니다.validation_failed
S3시스템 실패경고파일을 처리하지 못했습니다. 같은 파일을 다시 올려 주세요. 계속 실패하면 관리자에게 알려 주세요.worker_failed, file_missing
S4일괄 요청flashAI 트리아지 N건을 요청했습니다.enqueued: n
S4요청할 것 없음flash (info)새로 요청할 항목이 없습니다.enqueued: 0
S4권한 없음flashAI 트리아지를 요청할 권한이 없습니다.:forbidden
S4룰 실패경고룰 N개를 실행하지 못했습니다. 그 룰은 점수에 넣지 않았습니다. 같은 GL로 다시 실행하면 처음부터 다시 계산합니다.completed_with_errors
S4필터 결과 없음빈 상태조건에 맞는 전표가 없습니다.-
S5이미 요청됨flash (info)이미 AI가 분석 중입니다.:already_queued
S5요청 권한 없음flashAI 제안을 요청할 권한이 없습니다.:forbidden
S5판단 없음필드판단을 선택하세요changeset outcome
S5코멘트 없음, 너무 김필드코멘트를 입력하세요 (1-2000자)changeset comment
S5제출flash결론을 제출했습니다. 검토자에게 검토 대기로 보입니다.:ok
S5승인된 항목에 제출flash승인된 항목은 수정할 수 없습니다.submit_conclusion :invalid_state
S5제출 권한 없음flash결론을 제출할 권한이 없습니다.:forbidden
S5다른 사람이 먼저 제출경고이름이 방금 결론을 제출했습니다. 제출하면 새 결론으로 남습니다.finding_updated
S5검토 중 결론이 바뀜경고이름이 결론을 다시 제출했습니다. 최신 결론을 확인한 뒤 결정하세요.finding_updated
S5반려 사유 없음필드반려 사유를 입력하세요CHECK comment_required_on_reject
S5이미 처리됨flash이 항목은 이미 처리되었습니다. 최신 상태로 다시 불러왔습니다.decide :invalid_state
S5결정 권한 없음flash승인, 반려할 권한이 없습니다.:forbidden
S5자기 검토flash본인이 제출한 결론은 승인, 반려할 수 없습니다.:self_review (C-10)
S6필터 결과 없음빈 상태조건에 맞는 이벤트가 없습니다.-
S6새 이벤트단추새 이벤트 N건. 필터 중이면 새 이벤트가 있습니다audit_appended
S6목록 끝안내처음 기록까지 모두 보았습니다.받은 수가 limit보다 적음

문장형 flash와 경고는 마침표로 끝내고, 필드 아래 짧은 오류는 마침표 없이 쓴다. 시안의 S5 컴포넌트 문구를 따랐다.

부록 B. 감사 이벤트와 화면

계약 문서 4.2의 이벤트가 어느 화면의 어떤 행동에서 생기는지, S6에서 어떤 이름으로 보이는지 정리했다. payload 열은 계약 그대로다.

감사 이벤트
action행위자subjectpayload (계약)생기는 곳S6 이름
engagement.created사람engagementclient_name, fiscal_year, period_end_date, performance_materialityS1 만들기감사 대상 생성
engagement.member_added사람engagementuser_id, roleS2 멤버 추가멤버 추가
ledger_import.created사람ledger_importsource_kind, original_filename, byte_size, sha256S3 업로드, 생성GL 적재 시작
ledger_import.succeeded시스템 ledger.import_workerledger_importentry_count, line_count, total_debit, total_credit, warning_countImportWorkerGL 적재 완료
ledger_import.failed시스템 ledger.import_workerledger_importreason, error_countImportWorkerGL 적재 실패
test_run.started사람test_run-없음S2 테스트 실행테스트 실행 시작
test_run_rule.failed시스템 testing.rule_workertest_run-없음RuleWorker룰 실행 실패
test_run.completed시스템 testing.rule_workertest_run-없음마지막 RuleWorker테스트 실행 완료
ai_triage.requested사람test_run (일괄), finding (건별)-없음S4 일괄, S5 건별AI 제안 요청
ai_triage.completedAI triage:<response_model>finding-없음TriageWorkerAI 제안 완료
ai_triage.failedAI 또는 시스템finding-없음TriageWorkerAI 제안 실패
conclusion.submitted사람findingconclusion_id, outcome (4.1 코드 예시. 4.2 표는 빈 칸)S5 결론 제출결론 제출
review.approved사람finding-없음S5 승인승인
review.rejected사람finding-없음S5 반려반려
review.denied사람finding-없음S5 자기 검토 시도 (C-10)검토 거부됨

S6가 한 줄 요약을 보이려면 빈 칸에 다음 키가 필요하다 (C-12, 제안).

payload 제안
action더할 키S6 요약이유
test_run.startedledger_import_id, rule_ids, triage_threshold룰 8개, 기준 점수 25실행 조건은 나중에 바뀔 수 있어 당시 값을 남긴다
test_run.completedstatus, finding_count적중 전표 265건-
test_run_rule.failedrule_id룰 이름원인 원문은 넣지 않는다
ai_triage.requested일괄이면 enqueued52건 요청-
ai_triage.completedai_triage_id, risk_level위험도 높음모델, 프롬프트 버전, 입력 해시는 ai_triages 행에 있다
ai_triage.failedreason (분류 값만)실패 이유 분류HTTP 본문, 키 같은 원문은 넣지 않는다
review.approvedconclusion_id, review_decision_id-어느 결론을 승인했는지 로그만으로 알 수 있게 (C-11)
review.rejectedconclusion_id, review_decision_id, comment반려 사유 첫 줄감사 로그는 append-only라 사유를 복사해도 어긋나지 않는다

부록 C. 미결 사항과 차이

설계서를 쓰면서 찾은, 지금 구현이나 계약과 이 설계서가 다른 곳이다. 계약을 바꾸는 항목은 메인 세션이 계약 문서에 반영한 뒤 구현한다.

미결 사항
ID영역지금제안
C-01상단 바Phoenix 기본 헤더(Website, GitHub, Get Started)와 영어 Settings, Log out3.1의 앱 바로 바꾼다. 레이아웃 파일 한 곳
C-02연결 끊김 문구영어 기본 flash부록 A 문구. gettext ko를 더하거나 레이아웃 문자열을 바로 바꾼다
C-03404"Not Found" 한 줄안내 페이지와 목록 링크. 비멤버도 같은 404로 감사 대상이 있는지 숨긴다
C-04시각 titlekst_time의 title이 ISO 8601title을 "YYYY-MM-DD HH:MM:SS UTC"로 맞춘다
C-05사람 이름users에 이름이 없고 이메일만 있다users.display_name을 더할지 정한다. 안 더하면 이메일을 그대로 쓴다
C-06검증 문구Ecto 영어 기본 문구changeset에서 한국어 메시지를 지정하거나 gettext ko
C-07S2 통계해결 (2026-10-19). count_findings/2가 review_queue, minor, approved를 돌려준다S2 실행 행과 통계, S4 탭이 함께 쓴다
C-08S4 더 보기list_findings/3에 페이지 옵션이 없다limit와 커서(점수, 금액, id) 옵션. 검토 큐 52건이면 MVP는 한 번에 읽어도 된다
C-09경미 적중 AIrequest_finding_triage/2가 기준 미만을 거부하지 않는다:below_threshold 오류를 더해 서버에서도 막는다. 화면에서 단추만 숨기면 직접 호출로 우회된다
C-10자기 검토 기록authorize(:review_conclusion)가 먼저라 작성자는 :forbidden이 된다. :self_review와 review.denied는 역할이 바뀐 사용자만 닿는다방어 코드로 남긴다. 시안 S6 #74는 바꿨다. 작성자의 시도까지 기록하려면 :forbidden에서도 review.denied를 남긴다
C-11낡은 결론 결정decide/4가 검토자가 본 conclusion_id를 받지 않는다. 화면을 다시 그리기 전에 누른 승인은 새 결론에 붙는다decide가 conclusion_id를 받고, 최신 결론이 아니면 :stale로 거부한다
C-12감사 payload4.2의 여러 payload가 빈 칸이다부록 B 제안 키
C-13S6 대상 필터subject 옵션 모양이 정해지지 않았다. 전표번호를 finding으로 바꾸는 함수가 없고, 한 전표가 여러 실행의 finding일 수 있다. audit_appended에 행위자와 대상이 없다subject: type, id 모양으로 확정. 전표번호는 이 감사 대상의 전표에 걸린 finding 전부로 바꾼다. 브로드캐스트에 actor_type, subject_type, subject_id를 더한다

디자인 문서, 시안과 다른 점

Changelog