ops: 8 of 29 workflow-level cancel-in-progress values are contracted by nothing (mutation census) #1988
Description
전수 변이 census — 워크플로 수준 cancel-in-progress 29개 중 21개만 계약됨
읽기가 아니라 변이로 쟀습니다. 각 워크플로의 워크플로 수준 플래그를 반대 값으로 뒤집고(주석 처리 + 반대값 삽입) 매번 전체 스위트를 돌린 뒤 복원하고 git status 로 clean 을 확인했습니다. 편집 중인 트리와 분리된 전용 detached 워크트리에서 실행했습니다.
기준 트리는 #1980 병합분입니다. false 인 워크플로에는 true 를 넣는 역방향도 포함했습니다 — 그래야 그 false 가 의도인지 방치인지 갈립니다.
결과: 21/29 계약됨
미계약 8건
값이 true 인데 아무도 안 지킴
audit-central-ruleset.yml sbom-generation.yml
scheduled-security-scan.yml secret-scan.yml
값이 false 인데 아무도 안 지킴
organization-commercial-readiness-loop.yml pr-auto-rebase.yml
pr-review-autofix.yml sbom-inventory-scheduler.yml
계약된 21건은 잡는 테스트까지 기록했습니다. 예: strix.yml → test_strix_serializes_provider_evidence_per_repository_and_pr, python-security.yml/sast-semgrep.yml → test_pr_keyed_scan_workflows_pin_cancellation_as_a_value(#1980), scorecard-analysis.yml(false) → test_scorecard_analysis_never_discards_an_in_flight_scan, hourly-review-repair.yml(false) → test_dispatch_job_uses_a_per_repository_dynamic_concurrency_group.
false 쪽 미보호가 이 census 의 새 소득입니다
true 미보호는 "취소가 조용히 꺼진다"이고, false 미보호는 "취소가 조용히 켜진다" 입니다. 후자는 실행 중인 작업이 죽는 쪽이라 방향이 다릅니다. 역방향 변이를 안 돌렸으면 이 넷은 안 보였습니다.
제 앞선 진술 정정 — pr-review-autofix.yml
#1979 본문에 제가 이렇게 썼습니다.
pr-review-autofix.yml은cancel-in-progress: false가 의도입니다(수리 중인 실행을 자기가 취소하면 안 되니까요)
그 근거가 저장소 어디에도 없습니다. 워크플로에 주석이 없고, docs/ 와 루트 마크다운에도 그 결정을 적은 곳이 없습니다.
.github/workflows/pr-review-autofix.yml:17 cancel-in-progress: false ← 주석 없음
grep -rn "autofix" docs/ *.md | grep -i cancel → 0건
워크플로의 목적에서 제가 추론한 것이지 기록이 아닙니다. 그럴듯한 추론이라고 보지만 사실처럼 적었고, 다른 세션들이 그걸 인용했습니다. 정정합니다.
그래서 이 넷에 대해 지금 테스트를 추가하면 안 됩니다 — 값이 옳다는 근거가 없는 상태에서 계약을 쓰면 아무도 안 내린 결정을 고정하는 것입니다. scheduled-security-scan.yml 은 특히 값 자체가 미결입니다(github.ref 키잉 + 취소 켜짐으로 2026-09-01~09-05 취소 228건 / 완주 0건).
필요한 것은 테스트가 아니라 결정입니다
미계약 8건 각각에 대해 의도된 값이 무엇인지 먼저 기록돼야 하고, 그 다음에 계약이 그 기록을 고정합니다. 순서를 뒤집으면 테스트가 결정을 대신 내립니다.
- 값이 확정된 것부터: 워크플로에 한 줄 주석으로 이유를 남기고 계약 추가
scheduled-security-scan.yml: 값 자체가 미결이므로 그 판단이 먼저
방법론 메모
이 census 를 돌리게 된 계기는 읽기 기반 커버리지 판정이 세 번 틀렸기 때문입니다. 근접도 스캔이 sast-semgrep 을 계약됨으로 오보했고, 제 앞선 스위프도 같은 워크플로를 CAUGHT 으로 오보했습니다. "무엇이 이 값을 지키는가"가 질문이면 계측기는 그 값을 바꾸는 것입니다.
주의: 변이 테스트는 중복을 못 봅니다. 같은 계약이 두 번 쓰여 있어도 어느 쪽이든 변이를 잡으니 CAUGHT 이 나옵니다. 위 표의 "잡는 테스트" 열이 그래서 유용합니다 — 이름이 하나면 단일 계약입니다.
🤖 Generated with Claude Code
Activity
seonghobae commentedon Sep 6, 2026
세 번째 출처(git 이력)까지 확인했습니다 — 의도 기록은 여전히 0건이고, 이력은 여기서 쓸 수 없습니다
본문에서 미계약 8건에 대해 "값이 의도인지 근거가 없다"고 썼는데, 그때 확인한 것은 워크플로 주석과
docs/뿐이었습니다. git 이력을 안 봤습니다. 이력은 기록이니 확인했어야 했고, 지금 했습니다.false넷을 도입한 커밋을 찾았습니다넷이 같은 커밋 하나이고, 그 제목은 이렇습니다.
concurrency 와 아무 관계가 없습니다. 본문에도
concurrency/cancel언급이 0건입니다.그 커밋의 정체
저장소의 최초 일괄 임포트입니다. 삽입만 있고 파일 314개가 한 번에 들어왔는데, 커밋 제목은 그중 아주 좁은 한 조각만 설명합니다.
결론과, 다음 사람을 위한 함정
결론은 본문 그대로입니다 — 이 넷의
false에 대한 의도 기록이 세 출처(주석·문서·이력) 어디에도 없습니다. 이제 근거가 더 튼튼해졌을 뿐 판단은 안 바뀝니다. 계약을 쓰면 여전히 정책 발명입니다.함정 하나를 남깁니다. 이 저장소에서 임포트 시점부터 존재한 무엇이든
git log -S로 추적하면 루트 커밋0c6b9a64가 나오고, 그 제목은 strix fallback 이야기입니다. "이 값을 도입한 커밋의 메시지" 를 의도의 근거로 쓰면, concurrency 설정에 대해 strix 이야기를 읽게 됩니다. 이력을 의도의 출처로 쓰기 전에 그 커밋이 루트/일괄 임포트인지 먼저 확인해야 합니다 —git rev-list --count <sha>가 1 이면 그렇습니다.그래서 필요한 것은 그대로입니다
의도를 지금 기록하는 것입니다. 이력에서 복구할 수 있는 것이 없으니, 값이 확정된 것부터 워크플로에 한 줄 주석을 남기고 그 다음에 계약이 그 주석을 고정하는 순서여야 합니다.
🤖 Generated with Claude Code