사람이 늘어날 때
6. 그 밖에 사람이 늘어날 때 발생하는 것
6.1 스키마 변경이 다른 사람의 빌드를 중단시키는 것
기획이 컬럼 이름을 변경하면 생성 코드가 변경되고, 그 이름을 참조하던 코드가 컴파일되지
않습니다. SchemaBaseline은 이미 배포된 리더가 읽지 못하는 변경을 검출하지만 컴파일
중단은 검출 대상이 아닙니다.
구글 시트에는 PR이 없습니다. 변경이 저장소를 경유하지 않으므로 검사가 개입할 지점도 없습니다. 처방 은 주기적 변환입니다.
tabbit --recipe recipe.dev.jsonc --validate-only
--validate-only는 검증까지만 수행하고 산출물을 생성하지 않으므로 스케줄 잡에
적합합니다. 실패 보고에는 셀 위치가 포함됩니다. 주기는 시트를 편집하는 사람이 결과를
확인할 수 있는 간격이어야 하며, 하루 1회는 그 조건을 충족하지 않습니다.
2가지가 이 잡의 전제입니다.
- 인증. 이 잡은 구글 시트를 읽으므로 §6.5의 제약을 그대로 받습니다. 서비스 계정이 생기기 전에는 개인 토큰 캐시를 러너에 배치하는 임시 구성뿐이고, 토큰이 만료되면 그때부터 실행이 실패합니다. §8에서 서비스 계정을 첫 순서로 둔 이유입니다.
- 통보 경로. 시트를 고치는 사람은 CI 화면을 보지 않습니다. 실패가 메신저 채널에 닿지 않으면 이 잡은 실행되지 않는 것과 같습니다.
6.2 도구 버전의 드리프트
산출물은 시트만의 함수가 아니라 도구 버전의 함수이기도 합니다. 각자 다른 버전으로 변환하면 같은 시트에서 다 른 바이트가 나오고, 이것이 「내 환경에서는 정상」의 실제 원인입니다.
- 배포된 실행 파일 1개를 고정하고 버전을 저장소에 기재합니다. 자립 단일 파일이므로 .NET 설치가 없는 머신에서도 동작합니다.
summary타깃의Path를 데이터 출력 폴더로 지정합니다.run블록에toolVersion과environment가 함께 들어 있으므로, 배포된 데이터에서 「어느 환경의, 어느 버전 도구가 만든 것인가」가 그 파일 하나로 되짚어집니다. 히스토리에도 스냅샷마다 같은 것이 저장됩니다.
매니페스트에 버전을 넣는 안은 채택하지 않았습니다. 처음 계획에서는 4순위 항목이었는데, 매니페스트는 모든 런타임의 업데이터가 파싱하는 파일입니다. 아무도 읽지 않는 필드를 거기 넣는 것은 게이트 없는 무게이고, 같은 것을
summary가 이미 냅니다. C의 수제 파서가 모르는 키를 문법대로 건너뛰도록 쓰여 있어 위험하지는 않지만, 위험하지 않은 것과 필요한 것은 다릅니다. 매니페스트는 파일의 동일성을,summary는 그 실행의 내력을 담당합니다.
6.3 검증 규칙의 소유
validation/rules/의 .cs 파일은 텍스트이므로 병합됩니다. 이 영역은 문제가 되지
않습니다. 다만 다음 2가지를 규약으로 고정합니다.
TreatWarningsAsErrors는 CI에서만 켭니다. 애셋 누락 경고가 로컬 작업을 중단시키지 않으면서 빌드로는 통과하지 않게 하는 구성이며, 이것이 그 설정의 용도입니다.- 규칙 파일은
<테이블>Rules.cs명명과[RulePriority(n)]를 따릅니다. 실행 순서는--list-validators로 확인합니다 — 우선순위가 파일마다 분산되어 있으므로 전체 순서가 한곳에 모이는 자리는 그 출력뿐입니다.
6.4 비밀의 관리
googlesheets-client-secret.json은 무시 목록에 있으며, 과거에 실제로 커밋된 이력이 있습니다. 무시 규칙의 주석에 「삭제만으로는 부족하며 Google Cloud 콘솔에서 자격 증명을 폐기해야 한다」고 기재되어 있습니다.- recipe는 커밋되는 파일이므로 비밀번호·키를 직접 기재하지 않습니다. 연결 문자열은
${NAME}을, 암호화·MAC 키는 변수명 또는 파일 경로를 받습니다. - 로그의 연결 문자열은 마스킹됩니다(
ConnectionString.Redact).
6.5 구글 인증 — 서비스 계정, 됨
개정 전에는 GoogleWebAuthorizationBroker를 통한 대화형 OAuth 하나뿐이었습니다. 토큰을
~/.credentials/sheets.googleapis.com-tabbit에 캐시하므로 개발자의 기계에서는 맞지만,
빌드 서버에서는 2가지 제약이 발생했습니다 — 최초 실행에 브라우저 동의가 필요하고, CI의
시트 접근 권한이 특정 개인의 계정에 종속됩니다.
서비스 계정으로 접속할 수 있습니다.
| 설정 | 누구로 접속하나 |
|---|---|
ClientSecretFilename | 변환을 돌리는 사람. 개발자의 기계 |
ServiceAccountKeyFile · ServiceAccountKeyVariable | 그 잡 자신. 빌드 서버 |
- 둘을 함께 적으면 거부합니다. 서로 다른 계정이므로 하나를 말없이 고르면 그 잡이 자기가 아닌 사람으로 문서를 읽게 되고, 산출물의 어디에도 그 사실이 남지 않습니다.
- 키를 타입으로 요구하므로 클라이언트 비밀을 그 자리에 적으면 거기서 거부합니다. 구글이 내려주는 두 JSON은 바꿔 넣기 쉬운데, 그대로 API에 보내면 권한 오류로 돌아와 문서 공유 문제처럼 읽힙니다.
- 키는 환경 변수로 받을 수 있으므로 러너에 파일로 남지 않습니다.
이것이 §6.1의 주기 검증과 §4.4의 승격 잡의 전제였고, 둘 다 이제 성립합니다.
7. 도구 변경 없이 오늘 적용하는 것
| 항목 | 내용 |
|---|---|
| 1 | 데이터·매니페스트·summary를 .gitignore에 등재합니다. 생성 코드는 CI가 커밋하고, 사람은 산출물을 커밋하지 않습니다 |
| 2 | 데이터까지 커밋해야 한다면 CI만 push로 구성합니다. 규약으로 부족하면 데이터 전용 저장소 + 브랜치 보호입니다 |
| 3 | schema-baseline.json의 갱신 주체를 CI로 고정하고, 사람은 AcceptSchemaChanges로 의도를 선언합니다. 로컬 recipe는 SchemaBaseline을 비웁니다 |
| 4 | 암호화·MAC 키는 라이브 recipe에만 두고, 그 키와 라이브 DB 비밀번호는 CI 시크릿에만 둡니다. dev는 평문입니다 |
| 5 | recipe 1개를 ${TABBIT_ENV}로 매개변수화하고 --env로 환경을 지정합니다. 라이브는 커밋된 워크북 스냅샷을 읽습니다 |
| 6 | 승격을 「워크북을 내려받아 커밋하고 태그를 부여하는 것」으로 정의하고, CI 수동 트리거 잡 1개로 묶습니다. 시트가 정본인 라인에서 스냅샷은 편집하지 않습니다 |
| 7 | history 타깃을 CI 잡 1개에만 두고, ProjectKey 1개와 --branch 구분을 사용합니다. 조회는 --serve 1대 |
| 8 | --validate-only를 주기적으로 실행해 시트 변경을 조기에 검출합니다. 실패는 메신저로 통보합니다 |
| 9 | 도구 실행 파일 1개로 고정하고 버전을 저장소에 기재합니다 |
| 10 | 구글 시트의 시트·범위 보호로 소유를 강제하고(헤더 3줄 포함), 보호가 덮지 못하는 id는 대역으로 배분한 뒤 OnDuplicateIndex를 error로 유지해 변환이 검출하게 합니다 (§3.2) |
| 11 | CI 잡을 브랜치 단위로 직렬화해 Sweep 경합을 방지합니다. 로컬 확인 실행은 저장소 밖 경로를 가리키는 별도 recipe로 둡니다 |
| 12 | 구글 시트는 trunk 전용으로 둡니다 — 작업 중 격리는 주석 표기·ExcludeSheets·id 대역, 릴리스 라인은 스냅샷 브랜치, 옛 브랜치 핫픽스는 스냅샷을 고치고 시트에 전방 반영합니다 (§3.3) |
1~12는 다른 사람이 함께 쓰거나 환경이 추가된 뒤의 목록입니다 — §0. 6·8의 CI 잡은 §6.5의 서비스 계정으로 구성합니다.
8. 도구 변경 — 결과
| 순서 | 무엇 | 상태 |
|---|---|---|
| 1 | 구글 서비스 계정 인증 | 됨. 키를 파일 또는 환경 변수로 받고, 계정을 둘 적으면 거부합니다 (§6.5) |
| 2 | recipe 전역 ${NAME} 치환 | 됨. 파싱된 문서에 적용하므로 따옴표·역슬래시가 든 값이 그대로 들어갑니다. 없는 변수는 전부 모아 자리와 함께 보고합니다 (§4.5) |
| 3 | 환경 라벨 --env | 됨. 라벨과 ${TABBIT_ENV}를 한 낱말이 정하므로 둘이 어긋날 수 없습니다 |
| 4 | 매니페스트의 toolVersion | 하지 않습니다. summary가 같은 것을 내고, 매니페스트는 모든 런타임이 파싱합니다 (§6.2) |
| 5 | recipe 상속(Extends) | 하지 않습니다. 1~3으로 recipe가 1개가 되어 상속할 대상이 남지 않습니다 |
혼자 쓰는 구성은 이 4가지 중 어느 것도 켜지 않습니다 — §0.
9. 미결
- 주기적 변환의 주기. 시트 편집자가 결과를 확인할 수 있는 간격이어야 하며, 실제 편집 빈도를 측정한 뒤에 결정합니다. 미측정입니다.
- 승격의 승인 주체. 워크북 스냅샷 커밋에 리뷰를 요구할지, 태그 부여 권한만 제한할지가 정해지지 않았습니다.