LabHub
배우기 러닝패스 코스

되돌릴 수 없는 변경 · 변경 결과를 증거로 설명하기 · 실습

세 건 처리했다는데 우주 축제 보고서가 틀렸다

LabHub 에서 이어서 보기

목표

우주 축제 취소 작업의 승인·감사·현재 행을 관측하고, 고객 설명과 기계 판정이 같은 근거를 가진 보고서를 만듭니다. 읽기 전용 수집과 기존 파일을 보존하는 발행을 연결합니다.

왜 중요한가

같은 건수, 통과한 해시, 긴 보고 문장은 올바른 대상과 현재 상태의 증거가 아닙니다. Python dict·list·예외·파일, SQL 트랜잭션, 앞의 승인 버전·청크 재개 수업을 먼저 학습하세요. 예상 130분이므로 만료 전에 +시간으로 연장하세요. 최대 180분 안에 마쳐야 하며 세션이 끝나면 파일이 사라집니다. 필요한 코드는 별도로 보관하세요.

환경과 공통 계약

산출물은 /root/evidence/worker.py입니다. PostgreSQL 16·psycopg 3.2.3·Python 3은 이미지에 있습니다. 추가 설치·네트워크·권한 없이 postgres 사용자로 /root에 씁니다.

채점기는 로컬 labdb의 고유 임시 스키마에 아래 테이블과 가상 데이터를 만들고 자신이 만든 스키마만 정리합니다. 전달된 con의 search_path와 DSN을 사용하고 public이나 운영 DB는 수정하지 않습니다. 테이블·열 이름은 아래 고정 계약이며 SQL 데이터 값은 매개변수로 전달합니다. 고객·ID·관측 시각·스키마를 하드코딩하지 않습니다.

CREATE TABLE orders(id integer PRIMARY KEY,tenant text NOT NULL, qty integer NOT NULL CHECK(qty BETWEEN 1 AND 1000), state text NOT NULL CHECK(state IN ('pending','paid','cancelled')), revision integer NOT NULL CHECK(revision>=0));CREATE TABLE changes(change_id text PRIMARY KEY,tenant text NOT NULL,targets jsonb NOT NULL);CREATE TABLE audit(change_id text REFERENCES changes(change_id),id integer NOT NULL, previous_revision integer NOT NULL,new_revision integer NOT NULL,qty integer NOT NULL, PRIMARY KEY(change_id,id));

change_id·tenant는 정확한 str, ASCII 영문·숫자·밑줄·하이픈 1–64자입니다. id는 정확한 int 1–2147483647, qty는 정확한 int 1–1000, 승인 revision은 정확한 int 0–2147483646입니다. 현재·감사의 revision·previous_revision·new_revision은 정확한 int 0–2147483647입니다. bool은 정수로 허용하지 않습니다.

targets는 id·revision·qty 세 키만 가진 정확한 dict들의 정확한 list 1–64개입니다. audit는 id·previous_revision·new_revision·qty, current는 id·tenant·qty·state·revision 키의 정확한 dict 목록이며 각각 0–64개입니다. 각 목록 안의 ID 중복은 금지합니다. current의 state는 pending·paid·cancelled 중 하나이고 승인 밖 ID는 금지합니다. audit의 승인 밖 ID는 형식 오류가 아니라 분석할 이상입니다. 목록은 정규화할 때 ID순의 깊은 사본을 만들고 입력을 바꾸지 않습니다.

빌린 con은 autocommit=True·Read Committed이며 호출 시작 시 IDLE입니다. capture는 이를 닫지 않고 성공·실패 뒤 원 격리 수준·읽기 전용 설정·잠금 및 문장 제한을 복원하고 IDLE로 돌아갑니다. 다른 외부 트랜잭션으로 감싸지 않습니다. publish만 연결을 소유합니다. fault가 있을 때 명시한 문자열을 인자로 호출하며 원 오류를 숨기지 않습니다.

증거와 분류 형식

evidence는 format·change_id·tenant·targets·audit·current·snapshot·observed_at 여덟 키만 가진 정확한 dict입니다. format은 bool이 아닌 int 1입니다. snapshot은 PostgreSQL pg_current_snapshot()의 텍스트이며 4096자 이하, 십진수:십진수:선택적인 쉼표 구분 십진수 목록 형태입니다. observed_at은 UTC ISO 문자열로 마지막이 Z이고 Python datetime.isoformat()을 UTC로 출력한 형태와 같습니다. 예: 2026-09-13T01:02:03.123456Z. DB의 transaction_timestamp()를 변환하며 공인 인증 시각을 뜻하지 않습니다.

capture의 targets는 plan으로 정규화합니다. audit에는 해당 change_id만, current에는 승인 ID에 해당하는 현재 행만 넣습니다. 현재 행이 다른 고객으로 바뀌었더라도 그 차이를 보려면 해당 ID를 관측해야 합니다. 원 감사가 손상됐다고 고치거나 삭제하지 않습니다. 다른 변경의 감사·승인 밖 주문을 섞지 않습니다.

analyze의 모든 ID list는 오름차순입니다.

고객 설명과 번들 계약

describe는 normalize·analyze의 결과만으로 다음 순서의 평문을 반환합니다. 각 줄 끝은 LF이며 마지막 줄 뒤에도 LF가 하나 있습니다. 대괄호 자리에는 실제 값을 넣습니다. ID들은 공백 없는 쉼표 구분이고 빈 목록은 없음입니다.

변경: [change_id] / 고객: [tenant]관측: [observed_at] / 스냅샷: [snapshot]판정: [hold=보류, incomplete=미완료, complete=관측 시점 완료]승인 ID: [approved]확정 ID: [committed]미확정 ID: [remaining]현재 일치 ID: [matching]후속 차이 ID: [drifted]누락 ID: [missing]감사 불일치 ID: [invalid_audit]범위: 일관된 과거 관측이며 현재 상태·출처 인증을 보장하지 않습니다.

번들은 payload·sha256 두 키입니다. payload는 format=1, scope=historical-snapshot-not-current-state-or-authenticity, 정규화한 evidence, 분석 report, 평문 summary의 다섯 키입니다. sha256은 payload의 canonical bytes에 대한 소문자 64자리 16진수입니다. 검증기는 evidence에서 payload를 다시 계산해 해시뿐 아니라 자료·분류·설명·범위를 전부 비교합니다. 형식 버전의 True는 1로 인정하지 않습니다. 다른 관측 시각으로 일관되게 새로 만든 번들이 통과할 수 있다는 사실이 이 방식의 진위 인증 한계입니다.

파일 출력 계약

출력 path는 정확한 str 절대 경로이며 사용자가 소유한 기존 디렉터리 안의 일반 파일 자리입니다. 없는 파일은 허용하되 심볼릭 링크·디렉터리·상대 경로는 ValueError입니다. 부모 디렉터리를 자동 생성하지 않습니다. 채점은 자신이 만든 임시 출력 폴더를 인자로 주므로 출력 경로도 하드코딩하지 않습니다. 학생이 직접 시험한다면 /root/evidence/report.json처럼 자기 폴더를 사용하세요.

목적지와 같은 폴더의 고유 임시 파일에 완성 바이트를 쓰고 flush·fsync·닫기 후 교체합니다. 일반 예외에서는 임시 파일을 정리하고 기존 또는 새 완성본을 보존합니다. 강제 종료·전원 장애나 다른 프로세스가 부모 경로를 교체하는 공격까지 다루는 파일시스템 보안 경계는 아닙니다. 믿을 수 있는 소유 폴더에서만 사용합니다.

단계

1. 승인 목록의 의미를 고정한다 — Exception 하위 Conflict와 plan(change_id,tenant,targets)를 구현합니다. 공통 입력 계약을 검증하고 change_id·tenant·targets만 가진 새 dict를 반환합니다. targets는 ID 오름차순의 깊은 사본이며 입력을 바꾸지 않습니다. 잘못된 입력은 ValueError입니다.
2. 서로 다른 시점이 섞이지 않게 수집한다 — capture(con,change_id,fault=None)는 ID를 검증한 뒤 자기 트랜잭션에서 REPEATABLE READ READ ONLY와 잠금 500ms·문장 2000ms 한도를 적용합니다. changes의 해당 승인, 해당 change_id의 감사, 승인 ID에 해당하는 현재 orders를 순서대로 수집합니다. 각 조회 뒤 after-plan·after-audit·after-current 훅을 호출합니다. 같은 관측의 pg_current_snapshot() 표기와 transaction_timestamp()의 UTC 문자열을 넣어 아래 evidence 형식을 반환합니다. 없는 승인이나 저장된 승인 형식 손상은 Conflict입니다.
3. 증거의 형식과 중복을 검사한다 — normalize(evidence)는 아래 정확한 키·유형·범위·관측 표기를 검증하고 targets·audit·current를 ID순으로 정렬한 깊은 사본을 반환합니다. 중복 ID, 승인 밖 current, 알 수 없는 상태는 ValueError입니다. 승인 밖 audit는 삭제하지 않고 이후 분석할 수 있도록 보존합니다.
4. 과거 확정과 현재 차이를 나눈다 — analyze(evidence)는 normalize 후 아래 분류 계약에 따라 approved·committed·remaining·matching·drifted·missing·invalid_audit의 오름차순 ID list와 decision을 반환합니다. 입력은 바꾸지 않습니다. 감사 건수만 세거나 현재 값을 자동 복원하지 않습니다.
5. 고객 설명과 기계 결과를 같은 근거로 만든다 — canonical(value)는 UTF-8·ensure_ascii=False·sort_keys=True·separators=(쉼표,콜론)·allow_nan=False의 JSON bytes를 반환합니다. describe(evidence)는 아래 평문 형식으로 분석 결과를 설명합니다. bundle(evidence)는 정규화한 evidence, analyze의 report, describe의 summary를 payload에 넣고 payload 정규 바이트의 소문자 SHA-256을 sha256에 담습니다.
6. 해시를 다시 계산한 거짓 결론도 거절한다 — verify_bundle(value)는 정확한 번들 구조·format·scope·해시 표기를 검증하고 payload의 해시를 확인합니다. evidence에서 bundle을 다시 만들어 기존 번들과 정규 바이트 전체를 대조하고, 일치하면 분석 report의 새 dict를 반환합니다. 바이트 또는 결론·설명 불일치는 Conflict, 잘못된 형식은 ValueError입니다. decode(raw)는 정확한 bytes 1–1048576개를 UTF-8 JSON으로 해석해 verify_bundle의 결과를 반환하며 중복 키·비유한 상수·잘린 JSON·잘못된 UTF-8을 ValueError로 거절합니다.
7. 파일 교체 실패가 이전 결과를 지우지 않게 한다 — export(path,evidence,fault=None)는 출력 경로를 검증하고 정규 번들을 1MiB 이하의 bytes로 먼저 완성합니다. 목적지와 같은 디렉터리의 별도 임시 파일에 쓰고 flush·fsync 후 닫고 after-write를 호출합니다. os.replace로 목적지를 교체한 뒤 after-replace를 호출하고 번들의 sha256을 반환합니다. 교체 전 오류는 기존 파일을 보존하고 교체 후 오류는 새 완성본을 보존합니다. 정상·일반 예외 모두 자신의 임시 파일만 정리하고 원 오류를 전달합니다.
8. DB 관측부터 보고 발행까지 연결한다 — publish(dsn,change_id,path,fault=None)는 ID·출력 경로를 연결 생성 전에 검증합니다. psycopg.connect(dsn,autocommit=True,connect_timeout=2)로 소유 연결을 열어 capture하고 성공·실패 모두 닫습니다. 수집 성공 뒤 DB 연결을 닫은 상태에서 export를 호출해 sha256을 반환합니다. 수집·발행 훅과 오류를 그대로 전달하고 자동 재시도나 업무 DB 수정을 하지 않습니다.

참고

단계 8개

  1. 승인 목록의 의미를 고정한다
  2. 서로 다른 시점이 섞이지 않게 수집한다
  3. 증거의 형식과 중복을 검사한다
  4. 과거 확정과 현재 차이를 나눈다
  5. 고객 설명과 기계 결과를 같은 근거로 만든다
  6. 해시를 다시 계산한 거짓 결론도 거절한다
  7. 파일 교체 실패가 이전 결과를 지우지 않게 한다
  8. DB 관측부터 보고 발행까지 연결한다