본문 바로가기
C.W.K.
Lesson 07 of 07 · published

고친 판은 갈아 끼우지 않고 뒤를 이어

~14 min · immutability, revisions, delivery, evolution

Level 0불 꺼진 작업장
0 XP0/58 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

처음부터 참일 수 없던 문장

이번 판 전까지 트랙 3 의 채널 강의는 이렇게 말했어. 업로드 뒤에 주인이 고쳐달라고 하면, 새로 렌더해서 그 업로드를 갈아 끼운다고. 평범한 제작 약속처럼 읽히지. 그런데 플랫폼엔 그런 기능이 없어. 이미 있는 영상 주소 밑에서 영화만 슬쩍 바꿔 넣을 방법이 없거든. 새 영화는 새 영상이야. 주소도, 영수증도, 이력도 따로야. 그러니까 진짜 질문은 업로드를 어떻게 갈아 끼우냐가 아니었어. 더 나은 판이 올라간 뒤에, 옛 영상이랑 작업장이 그 영상에 대해 적어둔 기록 전부를 어떻게 하냐였지.

스페인 여행의 정정 두 건이 이틀 만에 답을 끌어냈어. 2026-10-21, 프리힐리아나 하루를 담은 편집본에서 목소리가 주인의 'one of us' 를 한글로 적은 걸 'one of earth' 로 읽었어. 고친 편집본은 새 비공개 영상으로 올라갔고, 첫 영상은 개정 1 로 비공개인 채 남았어. 2026-10-22, 네르하 하루 편집본은 주인이 자기 카메라로도 찍은 식당 두 곳에 폰 사진을 썼어. 주인은 대놓고 말했지.

최고의 맛집들이라고 하고 아이폰 사진 쓰는 게 미안하다 — 아빠

카메라 사진 편집본은 같은 승인 대본, 같은 목소리로 새 비공개 영상이 됐어. 두 세션 다 revisions/r01/ 폴더를 손으로 만들고, 옛 업로드 기록 이름을 out/upload-r01.json 으로 바꿨어. 어느 영상이 어느 영상을 대신했는지는 아무도 안 적었어. 두 번째 업로드를 막던 장치는 파일 이름 하나였어. 나중에 코드가 스스로 그렇게 적었지.

올라간 영상은 못 바꿔

리뷰 5(2026-10-22, 63bc75e)가 이 습관을 장치로 바꿨어. 출발점은 거절이야. 이 제작물이 올라갔냐고 묻는 검사는 이제 전부 함수 하나를 읽어. 지금 유효한 업로드 기록만 나열하는 함수야. 두 단계가 그 답을 보고 거절해. video 는 유효한 업로드가 있는 동안 '올라간 영화는 못 바꾼다 … 고친 편집본은 개정이다'라며 렌더를 안 해. upload 는 있는 기록 위에 다른 영화를 안 보내. 같은 영화로 다시 돌려도 두 번째 사본은 절대 안 나가고, 커버에 관문이 걸린 업로드라면 그 영상의 커버만 고치고 다시 읽어. 뭐가 올라갔는지 적은 기록이 실수로 덮일 일은 없어. 그 위에 쓰는 게 아무것도 없으니까.

개정은 증거를 옆으로 비켜놔

정정은 주인이 있어야 도는 명령이야. pipeline.py revise --slug … --note … --said … --source-ref …. 주인이 실제로 한 말과 그 말을 한 자리가 없으면 거절해. 개정은 주인의 요청이지, 계속 일하라는 말이 아니거든. 그다음 밀려나는 업로드의 영수증 묶음 전체를 상대 경로 그대로 다음 빈 revisions/rNN/ 으로 옮겨. 업로드 기록과 그 안내 페이지, 커버의 배달 영수증과 재시도 기록과 내보낸 파일, 영화의 검증·품질·QA·글리프 되읽기 기록, 시청 메모, 작성 목록, 그리고 기록이 바이트로 가리키는 올라간 영화 파일까지. 그래야 video 가 그 자리에 고친 편집본을 렌더할 수 있어. 절대 안 옮기는 건 제작물의 바닥이야. 편집, 표시 층, 얼려둔 접수와 재료, 오디오, 그림, 승인된 커버 그림, 감사 이력 전부.

꼼꼼함은 순서에 있어. 기록 superseded.json 은 complete: false 로 첫 이동보다 먼저 써. 업로드 기록은 맨 마지막에 옮기고. 그래서 묶음이 다 비켜설 때까지 제작물은 계속 올라간 상태로 읽혀. 중간에 멈춘 실행은 다음 호출이 같은 폴더에서 마저 끝내. 파일은 같은 볼륨 안에서 이름 바꾸기로만 옮겨. 복사하고 지우는 일은 없어. 볼륨이 다르면 반쯤 옮겨진 묶음을 만드느니 거절해. 이미 있는 대상은 절대 안 덮어쓰고. 그 업로드들 중 하나에 커버 재시도가 아직 돌고 있으면, 끝날 때까지 개정은 못 해.

고친 편집본이 올라가면 업로드 단계가 그걸 개정 기록에 뒤를 잇는 영상으로 찍어. 언어마다 하나씩. 이미 찍힌 후속은 절대 안 덮어쓰고, 밀려난 영상을 자기 후속으로 찍으려 하면 거절해. 배달 현황은 밀려난 줄마다 어느 개정에서 밀려났는지, 무엇이 뒤를 이었는지 붙여. 10월 21일과 22일에 손으로 만든 두 폴더도 여전히 읽혀. 후속은 같은 언어의 다음 업로드에서 짐작하고.

옛 영상은 남고, 삭제는 관측해

밀려난 영상은 플랫폼에 그대로 남아. 처음엔 고른 게 아니었어. 주인이 네르하 카메라 편집본을 공개하고, 밀려난 영상은 직접 지우고, 왜 자기가 해야 하는지 말했어.

피파는 삭제 권한 없어서 어차피 못 지우더라고. 삭제는 아빠가 해야해 — 아빠

피파의 업로드 계정은 올리고, 공개 범위를 정하고, 커버를 걸 수 있어. 지우는 건 못 해. 절차의 baseline 1.9(2026-10-22, c4ab71b)는 그걸 권한 얘기만이 아니라 기록 얘기로 만들었어. 개정이 업로드를 밀어내면, 피파는 옛 영상을 그대로 두고, 어느 영상이 주인이 지울 몫인지 알려주고, 주인한테 걸린 대기 항목으로 보고해. revise 가 딱 그렇게 출력해. 삭제는 증거로만 기록해. 주인이 직접 한 말, 아니면 beacon delivery --reconcile 이 영상이 사라진 걸 관측한 것. 지워달라고 했다는 이유로는 안 적고, 피파가 지웠다는 문장은 아예 안 써. 네르하 기록이 바로 그 모양이야. 대조가 옛 영상을 삭제됨으로 관측했고, 피파가 내렸다는 기록은 어디에도 없어.

이력이 살아남아야 하는 이유

이 트랙 다섯 번째 강의는 사람이 결과를 승인하는 순간부터 다시 생성하는 건 재현이 아니고, 승인된 영화는 봉인해야 한다고 했어. 정정은 같은 주장의 한 걸음 뒤, 배달 단계야. 밀려난 업로드는 배달된 사실이었어. 누가 봤고, 커버를 다시 읽어 확인했고, 링크가 어디로 건너갔을 수도 있어. 그 기록을 덮어쓰면 작업장의 이력은 고친 편집본이 처음이자 유일한 판이었다고 말하게 돼. 절차 문서에도 그 금지가 그대로 적혀 있어. 보관된 영화와 예전 승인을 나중에 생긴 규칙을 따른 것처럼 보이게 고쳐 쓰지 않는다고. 그래서 개정 폴더는 옛 업로드의 증거를 통째로 지키고, 후속 표시는 무엇이 뒤를 이었는지 말하고, 배달 현황은 오늘 플랫폼이 뭘 보여주는지 말해. 사실 셋, 영수증 셋(트랙 7 다섯 번째 강의).

배달한 건 고치는 게 아니라 뒤를 잇게 해. 배달한 걸 도구 안에서 못 바꾸게 하고, 누가 무슨 요청을 했는지 적은 기록 밑으로 그 증거를 비켜두고, 대신하는 걸 후속으로 찍고, 옛것이 어떻게 됐는지는 관측한 것만 적어. 정정이 고쳐 쓴 이력은 감사할 수 없어. 정정이 이어 붙인 이력은 할 수 있고.

Code

개정이 옮기는 것과 남기는 것 (beacon/revisions.py 와 업로드 단계의 스케치, 2026-10-27 기준)·text
pipeline.py revise --slug <slug> --note "<what it corrects>" \
    --said "<the owner's words>" --source-ref "<where he said them>"

refuses:  no --said or --source-ref       (a revision is his request)
          no current upload record         (before any upload: just re-render)
          a cover retry still running      (let it finish)

revisions/r02/superseded.json   written FIRST, complete: false
revisions/r02/out/...           moved, same relative paths, os.rename only
  validate.json  quality.json  producer-qa.json  glyph-readback.json
  WATCH.md  authored-talk.json  (and a dub's -<lang> twins)
  thumbnail/[<lang>/]delivery.json  retry.json  derived exports
  UPLOAD[-<lang>].md
  <the uploaded movie whose bytes the record names>
  upload[-<lang>].json          moved LAST: the guard holds until here

stays:    edit.json  presentation.json  mobile/  material/  audio/
          picture/  the accepted cover pictures  every audit history

upload (corrected cut)  -> a new private video
                        -> stamp_successor: superseded_by.<lang|master>
beacon delivery         -> row.superseded = {revision, by}
the old video           -> stays; deleting it is the owner's step,
                           recorded only when observed
beacon/revisions.py 원문 그대로, 2026-10-27 기준·python
def stamp_successor(ep: Path, language: str | None, result: dict) -> dict | None:
    """After a new upload, name it as the successor on the latest revision
    that superseded this language's video. Returns the updated record, or
    None when no revision is waiting for one (an ordinary first upload, or a
    successor already named, which is never overwritten)."""
    ep = Path(ep)
    for _, folder in reversed(_revision_dirs(ep)):
        record = _record(folder)
        if record is None or not record.get("complete"):
            continue
        listed = [video for video in record.get("videos") or [] if video.get("language") == language]
        if not listed:
            continue
        successors = record.setdefault("superseded_by", {})
        if _key(language) in successors:
            return None
        if result.get("id") in {video.get("video_id") for video in listed}:
            raise ValueError(f"{result.get('id')} is the video {record['revision']} superseded, not its successor")
        successors[_key(language)] = {"video_id": result["id"], "watch_url": result.get("watch_url"),
                                      "at": result.get("at") or storage.now()}
        storage.write_json(folder / RECORD, record)
        return record
    return None

External links

Exercise

네가 배달하는 것 중에 사람들이 이미 받아 썼을 만한 걸 하나 골라. 배포한 패키지 버전, 보낸 청구서, 올린 글, 운영에 올린 모형 같은 거. 지금 네 절차가 그걸 고쳐야 할 때 뭘 하는지 적어봐. 그다음 배달한 게 도구 안에서 못 바뀌는 버전을 설계해. 정정 명령이 돌기 전에 꼭 받아야 하는 게 뭔지, 뭘 비켜두고 뭘 제자리에 두는지, 대신하는 게 뭘 대신하는지 어떻게 이름 붙이는지, 옛것의 운명 중에 네가 직접 할 수 없고 관측만 할 수 있는 건 뭔지.
Hint
패키지 저장소가 깔끔한 선례야. 올린 버전은 덮어쓸 수 없으니까 고친 건 새 버전이고, yank 는 옛 버전에 표시만 해. 팀들이 잊는 건 요청의 기록이야. 누가 어떤 말로 요청했는지 말 못 하는 정정은 나중에 꼭 다툼이 나고, 그 다툼엔 읽을 게 하나도 없어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.