- 발행일
제출 단계 칩에 스튜어드가 찍혀 있었다 — 같은 칸을 두 사람이 반대로 읽었다
제출 단계 칩에 스튜어드가 찍혀 있었다
신청 상세 화면 위에는 진행 띠가 있습니다. 기록 → 제출 → 사전검증 → 검증 → 사전인증 → 인증 → 배포. 칸마다 단계 이름과 담당 역할 칩이 붙어요. 단계 수와 역할을 프로세스 정의에서 읽게 바꾼 건 8월 23일이었고, 이 글은 그 위에서 칩에 적히는 이름을 놓고 9월 9일 하루에 세 번 방향이 바뀐 이야기입니다.
운영 화면에서 이렇게 보였습니다.
| 단계 | 칩 | 이상한 점 |
|---|---|---|
| 1. 기록 | 신청자 | 맞음 |
| 2. 제출 | 데이터 스튜어드 | 제출하는 사람은 신청자입니다 |
| 3. 사전검증 | 변경통제위원회 | 사전검증을 하는 건 스튜어드예요 |
| 7. 배포 | 완료 | 역할 칸만 비어 있음 |
전부 한 칸씩 앞으로 밀려 보였어요.
왜 밀렸나
8월에 짠 규칙이 "그 단계에서 나가는 승인 전이를 실행할 그룹"이었습니다. 상태 기계를 걷는 코드 입장에서는 자연스러운 선택이에요. 각 상태에서 다음으로 넘기는 액션을 들고 있으니, 그 액션의 실행 그룹을 그대로 쓰면 됩니다.
그런데 화면에서 읽으면 뜻이 달라집니다. 칸에는 "제출"이라고 적혀 있고 그 아래 "데이터 스튜어드"가 붙어 있으면, 사람은 이 단계를 한 사람이 스튜어드라고 읽어요. 실제 값은 "제출 상태에서 다음으로 넘길 사람"인데요. 마지막 배포 칸이 비는 것도 같은 이유입니다. 배포에서 나가는 전이가 없으니 역할을 못 찾고, 템플릿 기본값인 '완료'로 떨어졌어요.
들어오는 전이로 바꿨다 (16:04)
칩을 그 단계로 들어오는 전이의 역할로 붙였습니다. 기록·제출은 신청자, 사전검증은 스튜어드, 검증은 위원회 순으로 교대해요. 첫 단계만 나가는 전이를 씁니다.
chain, seen, state = [], set(), start
entered_by = approve.get(start.pk)
while state is not None and state.pk not in seen:
seen.add(state.pk)
entry = approve.get(state.pk)
chain.append({"name": state.name, "role": cls._action_role(entered_by[1]) if entry and entered_by else None})
entered_by = entry
state = entry[0].next_state if entry else None
한 칸 뒤처진 값을 들고 다니는 게 전부입니다. 같은 규칙을 이용안내의 7단계 그림(review_steps)에도 적용해 담당 칩을 한 칸씩 옮겼고, 캡처 8장을 다시 찍었어요.
배포 칸은 역할 없이 '완료'로 뒀습니다. 그리고 그 이유를 문서와 docstring에 적었어요 — "배포는 인증 승인이 만드는 결과이지 누가 처리하는 자리가 아니다. 역할을 붙이면 아직 할 일이 남은 것처럼 읽힌다."
43분 뒤, 배포 칸을 뒤집었다 (16:47)
재협의에서 배포 칸을 데이터 스튜어드로 바꿨습니다. 코드로는 위 조건에서 entry and를 뺀 게 전부였고, 캡처 8장을 한 번 더 찍었어요. 배포는 저절로 도달하는 종착점이 아니라 스튜어드가 인증 단계에서 승인 액션을 실행해야 넘어가는 단계이고, 공개라는 행위의 주체가 있다는 쪽이었어요. 위원회는 검증·인증에서 적합 여부만 판정합니다.
여기서 제가 문서에 적은 문장이 이 글의 핵심입니다.
종전 심사 목록 안내의 '완료'는 마지막 단계에 나가는 전이가 없어 역할을 못 찾은 결과지 결정이 아니었다.
43분 전에 저는 그 '완료'를 설계 판단인 것처럼 적어 뒀습니다. 실제로는 빈 칸이 나온 걸 보고 그럴듯한 이유를 붙인 거였어요. 코드가 만든 결과를 의도로 서술하면, 나중에 그 자리를 고치려는 사람이 "결정된 사항"으로 읽고 비켜 갑니다.
첫 변경 두 시간 뒤, 팀원이 되돌렸다 (18:07)
같은 날 저녁 팀원이 이 변경을 철회했습니다. 기준은 원래대로 **"해당 상태에서 다음 승인을 처리할 역할"**이고, 배포는 다시 '완료'예요. 제가 옮겨 놓은 이용안내 문구와 캡처 8장도 원본으로 복원됐습니다.
두 읽기가 이런 관계입니다.
| 읽기 | 2. 제출 칸 | 7. 배포 칸 | 답하는 질문 |
|---|---|---|---|
| 들어오는 전이 (제 변경) | 신청자 | 데이터 스튜어드 | 이 단계를 한 사람은 누구인가 |
| 나가는 전이 (원복·현행) | 데이터 스튜어드 | 완료 | 이 상태에서 넘길 사람은 누구인가 |
어느 쪽도 틀리지 않습니다. 칩 한 줄이 두 질문에 동시에 답할 수 없었을 뿐이에요. 제가 "밀려 보인다"고 판단한 근거는 화면을 읽는 방식이었고, 원복 커밋은 기준을 '해당 상태에서 다음 승인을 처리할 역할'로 적고 그 절을 표시 정책의 정본으로 못박았습니다.
되돌린 커밋이 실제로 문제를 닫은 방식이 더 중요합니다. 칩을 고른 게 아니라 칸에 세 번째 줄을 붙이는 방식이었어요. 번호·상태 / 역할 / 행동 순서로, 기록 칸에는 '제출', 제출 칸에는 '1차 검토', 인증 칸에는 '배포 처리'가 적힙니다. 문구는 공용 함수 한 곳에서 만들고요.
def step_task(current, approved_states):
current = current.removesuffix("단계")
tasks = {
("기록", "제출"): "제출",
("제출", "사전검증"): "1차 검토",
# ("사전검증", "검증")·("검증", "사전인증")·("사전인증", "인증") 3쌍 생략
("검증", "배포"): "배포 처리",
("인증", "배포"): "배포 처리",
}
...
그리고 띠 위에 한 문장이 붙었습니다 — "역할은 이 상태에서 다음 단계로 넘길 사람을 뜻합니다." 표시 규칙을 잠그는 회귀 테스트 160줄도 같이 들어왔어요. 5단계·7단계, 표준데이터·OID, 현재·완료 표시, 안내 그림을 함께 확인합니다.
칩의 뜻을 코드로 정하는 대신 화면에 적고 테스트로 잠근 겁니다. 뜻이 화면에 없었기 때문에 두 사람이 같은 칸을 반대로 고칠 수 있었어요. 8월 23일 글 말미에 "절차 안내가 정의와 일치하는지를 회귀로 안 잠갔다"고 적어 뒀는데, 그 부채가 이렇게 청구됐습니다.
원복된 화면 위에서 이어 한 것
저녁에는 되돌아온 규칙 위에서 표시만 손봤습니다. 값은 안 건드리고요.
띠가 지나온 칸·현재 칸·예정 칸을 구분하게 했습니다. 지나온 칸은 회색 바탕에 번호 대신 ✓, 현재 칸은 파란 테두리에 옅은 파란 바탕, 예정 칸은 점선에 흐린 글씨예요. 종전에는 현재 칸 테두리 색과 작은 ✓뿐이라 훑어서 읽히지 않았습니다.
굵기를 border가 아니라 ring으로 준 이유가 있어요. ring은 칸 안쪽 폭을 안 먹어서 현재 칸만 내용이 좁아지는 일이 없습니다. 지나온 칸의 역할 칩은 색을 빼고, 이미 지난 화살표만 주 색을 줬어요 — 색이 끊기는 자리가 곧 "어디까지 왔나"입니다.
현재 칸이 없는 호출은 예전 중립 모양을 그대로 씁니다. 목록 화면의 절차 안내와 취소된 건이 여기 해당해요. 전부 '예정'으로 그리면 안내가 통째로 흐려 보입니다.
표시 테스트도 ✓ 표기에 맞춰 고쳤습니다.
# 지나온 칸은 번호 대신 ✓, 현재 칸은 번호를 단다
self.assertRegex(text, r"✓ 기록.*신청자\s+제출")
self.assertRegex(text, r"2 제출\s+데이터 스튜어드\s+1차 검토")
다음 날, 폭 문제를 폭이 아닌 걸로 풀었다
8월 23일 글에서 한계로 적어 둔 자리입니다. 칸 폭이 7단계에 맞춘 고정값이라, 간편 5단계 환경에서는 줄 오른쪽이 3분의 1쯤 빈 채로 남았어요.
칸을 늘려 채우는 방법(flex-1)을 먼저 해 보고 되돌렸습니다. 두 가지가 함께 어긋나요. 세로형 카드가 가로로 퍼져 뭉툭해지고, 화살표가 없는 마지막 칸만 그 자리까지 먹어 혼자 넓어집니다(7단계 실측 144px × 6 + 165px).
답은 justify-center 한 줄이었습니다. 칸 폭은 그대로 두고 줄을 가운데로 모아 남는 폭을 좌우로 나눠요. 신청·심사 목록 4곳, 관리 화면 4곳, 상세 진행 띠까지, 이 띠를 그리는 두 매크로(process_journey·workflow_steps_strip)에 같은 한 줄을 넣었습니다.
신청 안내 칸 폭은 8.75rem에서 11rem으로 넓혔는데, 이건 실측값이에요. 설명이 두 줄에 들어가는 최소 폭이자 6칸이 한 줄에 남는 최대 폭입니다(176px × 6 + 화살표 = 1,180px, 본문 1,200px). 좁히면 설명이 서너 줄로 접혀 칸이 길쭉해지고, 넓히면 6칸이 줄을 넘깁니다. 카드 높이는 160px에서 137px이 됐고 표준데이터 5칸은 설명이 전부 2줄로 들어갔어요.
검증
- 원복 커밋이 함께 넣은 표시 회귀 테스트가 5·7단계, 표준데이터·OID, 현재·완료 표시와 안내 그림을 확인합니다. 지나온 칸이 ✓로, 현재 칸이 번호로 나오는지도 여기서 잠겼어요.
- 다음 날 폭을 고치면서 dev의
DEFAULT_WORKFLOW_TYPE이 또1(간편 5단계)로 돌아가 있는 걸 발견해 운영과 같은0으로 되돌렸습니다. 기록상 이 값이 바뀐 게 세 번째고, 그중 모르는 사이1로 흘러 있던 건 두 번째예요. 개발 DB에는 5단계 2건과 7단계 1건이 섞여 있어서, 한 목록에 절차가 다른 건이 함께 서는 상태가 이미 와 있습니다. - 칸 폭은 화면에서 실측했습니다. 근거 숫자와 "칸을 늘려 채우지 말 것"을 문서에 남겨서, 다음에 같은 자리를 보는 사람이
flex-1을 다시 시도하지 않게 했어요.
남은 것 · 한계
- 제 변경은 두 시간 남짓 만에 되돌아갔습니다. 칩 규칙은 화면 표시 정책입니다. 운영에서 이상하게 보인다는 관찰까지는 맞았고 협의도 거쳤지만, 원래 표시 정책을 적어 둔 문서 절과 맞춰 보지 않고 규칙부터 바꾼 게 순서 착오였어요.
- 43분 만에 뒤집은 배포 칸이 더 뼈아픕니다. 첫 커밋에서 '완료'를 설계 판단처럼 적었는데, 사실은 값을 못 찾아 나온 빈 칸이었습니다. 코드가 만든 결과를 의도로 쓰지 않기 — 이게 이번에 제일 크게 남았어요.
- 띠 위의 설명 문장과 제 읽기는 여전히 다릅니다. 화면은 "역할은 이 상태에서 다음 단계로 넘길 사람"이라고 말하는데, 칸 이름과 칩을 나란히 보면 아직도 "이 단계를 한 사람"으로 읽힐 여지가 있어요. 행동 줄이 그걸 많이 덜어 주지만 칩 자체의 중의성은 남아 있습니다.
- 캡처가 왕복했습니다. 제가 16:04와 16:47에 8장씩 두 번 찍고, 원복으로 8장이 되돌아오고, 그날 밤 다른 변경 때문에 다시 찍혔어요. 바이너리라 리뷰에서 내용이 안 보이는 것도 그대로입니다. 캡처 쪽 이야기는 따로 정리했습니다.
- 칸 폭은 아직 고정값입니다. 줄을 가운데로 모아 빈 자리가 눈에 덜 띄게 했을 뿐, 단계가 8개인 프로세스가 생기면 여전히 줄을 넘깁니다.
관련 글: 7단계를 박아둔 템플릿이 5단계 환경에서 거짓말을 했다 · ISO 14817-2 기반 7단계 워크플로우 · 신청 항목 셋 중 한 건이면 제출 · 상세 화면을 목업에 맞추며 하루에 세 번 뒤집었다