LabHub
배우기 러닝패스 코스

되돌릴 수 없는 변경 · 고객 변경을 끝까지 인계하기 · 실습

축제는 취소됐는데 완료 버튼이 빨갛다

LabHub 에서 이어서 보기

목표

우주 축제의 변경 요청을 받아 승인·실행·관측·보고·별도 보상으로 연결하는 조정기를 작성합니다. SQL 연산이 맞아도 조정기가 틀리면 생기는 사고를 실제 DB와 CLI로 확인합니다.

왜 중요한가

앞의 승인 버전·보상·증거 보고 수업을 마친 뒤 진행하세요. Python 함수·dict·list·예외·JSON·CLI 사용이 필요합니다. 이번에는 이미 배운 DB 코드를 전부 다시 쓰지 않고 제공 연산을 조합합니다. 예상 140분이므로 만료 전에 +시간으로 연장하세요. 최대 180분이며 세션이 끝나면 파일이 사라집니다. 필요한 코드는 별도로 보관하세요.

환경과 산출물

산출물은 /root/change-capstone/coordinator.py입니다. PostgreSQL 16·psycopg 3.2.3·Python 3은 이미지에 있고 추가 설치·네트워크·권한은 필요 없습니다. 학생은 postgres 사용자로 /root에 씁니다.

제공 어댑터는 /opt/lab/fixtures/change_capstone/operations.py의 Operations(dsn)입니다. 같은 폴더의 revision_ops.py·compensation_ops.py·evidence_ops.py는 앞 세 단원의 누적 정답에서 생성한 라이브러리입니다. 읽어 볼 수 있으며 이번 조정기 답은 포함하지 않습니다. 채점기는 로컬 labdb의 UUID 임시 스키마에 가상 주문·승인·감사·보상 테이블을 만들고 자신이 만든 스키마만 정리합니다. 전달한 ops·DSN·출력 경로를 사용하고 public이나 운영 DB를 수정하지 않습니다.

요청의 정확한 형식

모든 요청은 정확한 dict이며 action별로 아래 키만 허용합니다.

action은 해당 영문 str 하나입니다. change_id·tenant·undo_id는 정확한 str, ASCII 영문·숫자·밑줄·하이픈 1–64자입니다. apply·undo의 approved는 실제 True만 허용하며 1·문자열·False는 거절합니다. 이 플래그는 인증·서명이 아닌 가상 업무의 입력 계약입니다.

ids는 정확한 int 1–2147483647을 담은 정확한 list 1–16개이며 중복 없이 오름차순으로 정규화합니다. targets는 id·revision·qty 세 키만 가진 정확한 dict의 list 1–16개입니다. id 범위는 위와 같고 revision은 정확한 int 0–2147483646, qty는 정확한 int 1–1000입니다. bool을 정수로 받지 않습니다. ID 중복을 금지하고 ID순 깊은 사본을 만듭니다. 모든 함수는 원 요청을 변경하지 않습니다.

reason은 정확한 str 1–200자이며 앞뒤 공백, 제어문자 U+0000–001F와 U+007F를 금지합니다. report_path는 정확한 str 절대 경로이며 기존 소유 디렉터리 안의 일반 파일 자리입니다. 없는 파일은 허용하되 심볼릭 링크·디렉터리·상대 경로는 거절하고 부모를 자동 생성하지 않습니다. 신뢰하는 소유 폴더를 전제로 하며 부모 경로 교체 공격 전체를 막는 계약은 아닙니다.

제공 연산 계약

각 호출은 자기 DB 연결을 열고 정리합니다. 조정기가 외부 트랜잭션으로 감싸거나 연결을 따로 닫지 않습니다. 값·ID·경로를 하드코딩하거나 SQL을 다시 구현하지 않습니다.

채점에서는 각 메서드가 Exception을 내거나 계약 밖 반환을 하는 대역도 제공합니다. 검증된 요청을 넘긴 뒤의 연산 오류만 각 단계의 상태로 분류합니다. KeyboardInterrupt·SystemExit 같은 BaseException 전체를 잡으라는 뜻은 아닙니다. ops.Conflict와 학생의 Conflict는 별도 클래스입니다.

조정 결과와 CLI 계약

apply의 dispatch 결과는 change_id·execution·observed·report·sha256 다섯 키입니다. execution은 applied·replayed·conflict·uncertain 중 하나이며 관측 결과로 지우거나 바꾸지 않습니다. observed는 complete·incomplete·hold·unknown·mismatch·not_checked입니다. report는 saved·failed·not_attempted이며 저장 실패/미시도에서는 sha256=None입니다.

preview dispatch는 approved=False의 apply 제안만 반환합니다. report dispatch는 change_id와 report 함수 결과의 세 키, undo dispatch는 compensate 결과의 세 키를 반환합니다. dispatch에서 요청을 먼저 검증하고 동작별 경계를 지키세요. applied라도 관측이 unknown이면 보고를 시도하지 않고, replayed라도 관측이 hold면 그 결과를 그대로 보고할 수 있습니다.

decode(raw)는 정확한 bytes 1–65536개를 UTF-8 JSON으로 해석해 validate한 요청을 반환합니다. 중복 키·비유한 상수·잘린 JSON·잘못된 UTF-8·과대 입력은 ValueError입니다. main(argv,ops)는 요청 파일 경로 하나만 받고 최대 65537바이트를 읽어 decode→dispatch합니다. 성공하면 결과 JSON 한 줄을 stdout에 출력하고 0을 반환합니다. 인자·읽기·해석·처리 Exception이면 {"error":"request_failed"} 한 줄과 2를 반환하며 DSN·원 예외·traceback을 출력하지 않습니다.

스크립트로 실행할 때 sys.path에 /opt/lab/fixtures/change_capstone을 추가하고 operations.Operations를 읽습니다. 환경변수 LABHUB_CAPSTONE_DSN의 로컬 전용 DSN으로 Operations를 만들고 main(sys.argv[1:],ops)의 반환값으로 종료하세요. 이 환경변수는 정상 CLI 검증에서 반드시 제공됩니다. 모듈로 import될 때 CLI를 실행하면 안 됩니다. CLI 코드 0은 요청 처리 성공이지 업무 완료를 뜻하지 않습니다.

관측과 보고서 수집은 별도 호출입니다. observed는 앞 관측, report는 뒤 파일 저장 상태이며 두 호출의 시점이 같다는 보장은 없습니다. saved만으로 파일 안의 업무 결론이 complete라고 가정하지 않습니다. 보고서의 실제 관측 시각과 분류를 따로 확인하세요.

단계

1. 명시한 승인만 입력으로 받는다 — Conflict(Exception)와 validate(value)를 구현합니다. 아래 동작별 정확한 키·타입·범위를 검사하고 목록을 정렬한 깊은 사본을 반환합니다. apply·undo는 approved가 실제 True일 때만 허용하고, 오류는 ValueError입니다.
2. 미리보기를 자동 승인하지 않는다 — preview(value,ops)는 preview 요청을 검증하고 ops.preview(tenant,정렬한 ids)를 한 번 호출합니다. 받은 targets를 검증·정렬하고 ID 목록이 요청과 정확히 같은지 대조합니다. 다르면 Conflict입니다. 같으면 action=apply, 원 change_id·tenant·report_path, 관측 targets, approved=False의 제안 dict를 반환합니다.
3. 같은 ID의 다른 승인을 구분한다 — compare(value,observation)는 apply 요청을 검증합니다. observation이 None이면 unknown을 반환합니다. 그 외에는 아래 관측 형식에서 change_id·tenant·정규화 targets가 요청과 같은지 대조하고 다르면 Conflict입니다. 같으면 report.decision의 complete·incomplete·hold를 그대로 반환합니다. 다른 최상위 키나 알 수 없는 판정은 Conflict입니다.
4. 실행 응답과 결과 불명을 나눈다 — execute(value,ops)는 apply 요청 검증 후 ops.apply(change_id,tenant,targets)를 정확히 한 번 호출합니다. True는 applied, False는 replayed, ops.Conflict는 conflict, 그 외 Exception이나 bool 아닌 반환은 uncertain입니다. 호출 밖 검증 오류는 그대로 전달합니다.
5. 확정 기록을 관측해 불확실성을 드러낸다 — settle(value,ops,outcome)는 apply 요청과 네 실행 결과 값을 검증합니다. conflict면 observe 없이 observed=not_checked입니다. 그 외는 ops.observe(change_id)를 한 번 호출하고 compare로 observed를 정합니다. 조회 Exception은 unknown, 비교의 Conflict·ValueError·KeyError·TypeError는 mismatch입니다. change_id·execution=원 outcome·observed의 dict를 반환합니다.
6. 업무를 건드리지 않고 보고를 재생성한다 — report(value,ops)는 apply 또는 report 요청만 허용합니다. ops.publish(change_id,report_path)를 한 번 호출해 소문자 64자리 SHA-256 str이면 report=saved·sha256=그 값, 호출 Exception이나 잘못된 반환이면 report=failed·sha256=None의 dict를 반환합니다.
7. 별도 승인한 보상만 실행한다 — compensate(value,ops)는 undo 요청을 검증한 뒤 ops.undo(undo_id,change_id,reason)를 한 번 호출합니다. True·False·ops.Conflict·기타 Exception/반환을 각각 applied·replayed·conflict·uncertain으로 분류합니다. change_id·undo_id·compensation의 dict를 반환하고 후속 호출은 하지 않습니다.
8. 실제 JSON 요청과 CLI를 끝까지 연결한다 — dispatch(value,ops)·decode(raw)·main(argv,ops)와 CLI 진입점을 아래 계약으로 구현합니다. apply는 execute→settle 뒤 complete·incomplete·hold 관측에만 report를 호출합니다. 나머지는 report=not_attempted·sha256=None입니다. 다른 세 동작은 해당 함수만 호출합니다. 실제 DB·파일·CLI로 전체 흐름을 검사합니다.

참고

단계 8개

  1. 명시한 승인만 입력으로 받는다
  2. 미리보기를 자동 승인하지 않는다
  3. 같은 ID의 다른 승인을 구분한다
  4. 실행 응답과 결과 불명을 나눈다
  5. 확정 기록을 관측해 불확실성을 드러낸다
  6. 업무를 건드리지 않고 보고를 재생성한다
  7. 별도 승인한 보상만 실행한다
  8. 실제 JSON 요청과 CLI를 끝까지 연결한다