"repo 엔 버전 번호가 열두 개 있어. 그중 정확히 하나가 product 고, 정확히 하나만 그걸 바꿀 수 있어."
정확히 어느 버전?
repo 가 '버전 1.2.0'이라 치자. 어느 거? npm package 버전? native bundle build 번호? database schema 버전? API/protocol 버전? model 버전? deployment 번호? Git tag? 성숙한 프로젝트는 이걸 여럿 갖고, 정당하게 독립적으로 움직여. Firelink 는 그중 하나를 product version 이라 이름 붙여 — 사람이 보는 릴리스 — 그리고 집 하나를 줘. 작은 파일 cwk-product.json, product id 와 정규화된 SemVer 문자열(앞 v 없음; prerelease·build 부분은 진짜 의미 있을 때 허용).
값은 관례로 의미가 있어. 0.0.0 은 scaffold 껍데기, 0.y.z 는 불안정한 product 계약, 1.0.0 은 첫 안정 프로덕션 계약을 선언해. 표시 surface 는 독자를 위해 v 를 다시 붙여; 저장된 값은 깔끔하게 남아.
소유자 하나, editor 하나, 필드 하나
이건 사실-하나-소유자-하나를 구체화한 거고, 한 발 더 가. 소유자 하나만이 아니라 editor 하나. Firelink 는 어떤 legacy heuristic 소스보다 먼저 manifest 를 읽고, heuristic 은 유효한 manifest 없는 멤버용 read-only fallback 으로 남아. Web repository 카드가 그걸 쓸 수 있는 유일한 것이고, version 필드 만 바꿀 수 있어 — clean worktree 를 요구하고, 커밋 전에 coop 히스토리를 기록하고, 파일의 다른 모든 바이트를 보존하고, downgrade override 가 명시적으로 켜지지 않으면 낮은 SemVer precedence 를 거부하는, previewed·audited flow 를 통해. Native Launcher 는 여기서 완전히 read-only 야.
왜 그 의식이 핵심인가
version bump 는 사소해 보이는데, 정확히 그래서 안 지키면 위험해. product 버전을 올리는 건 주장이야 — '이제 이건 1.0.0, 안정 계약이다.' 아무 surface 나 그 번호를 끄적일 수 있으면, 그 주장은 아무 의미가 없어. bump 를 한 editor 로부터의 single-field·previewed·history-recorded·non-force-push operation 으로 만들어서, Firelink 는 버전을 정직하게 유지해. 일부러 바뀌었고, 정확히 뭐가 바뀌었는지 볼 수 있고, 그 변경이 진짜 흔적을 가진 진짜 커밋이야.