2.5.2 릴리스 실행 기록¶
{
"schema": "release-metadata/v3",
"version": "v2.5.2",
"status": "released",
"releaseScopes": [
"db-migration",
"contracts-package",
"coupler-api",
"coupler-admin-web",
"mobile-nextpush",
"docs"
],
"extraRepoRefs": [],
"versionMapping": {
"docs": {
"tag": "v2.5.2",
"commit": null
},
"coupler-api": {
"tag": "v2.5.2",
"commit": "a4a6341648c929a8dc5c97a3ca24816eecb117b5"
},
"coupler-admin-web": {
"tag": "v2.5.2",
"commit": "08e3e3f747b0776c7ee2b866a315a78d031ae081"
},
"coupler-mobile-app": {
"store": {
"android": {
"versionBuild": "2.4.2 (103)",
"releaseTag": "v2.4.2",
"commit": "37c0da07d22704c3a28723ec6d08758bff0f1aae",
"sourceStatus": "verified",
"limitation": null
},
"ios": {
"versionBuild": "2.4.2 (103)",
"releaseTag": "v2.4.2",
"commit": "37c0da07d22704c3a28723ec6d08758bff0f1aae",
"sourceStatus": "verified",
"limitation": null
}
},
"nextPush": "Android bluedotstudio.official-gmail.com/coupler/Production v31 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v28 target 2.4.2",
"commit": "04a3a6f1569bd316af89274d9f72121a39908c4d"
}
},
"scopeResults": {
"db-migration": {
"status": "released",
"summary": "개발계에서 검증한 source bafbae1f의 라운지 best→pinned 및 알림 outbox migration 2건을 2026-08-20 20:41:01 KST 운영계에 적용하고 ledger 90건·적용 대상 잔여 0을 확인했다.",
"evidence": {}
},
"contracts-package": {
"status": "released",
"summary": "API a4a63416의 packages/contracts tree가 기존 stable 0.1.32 source tree와 동일함을 확인하고 재발행 없이 재사용했다.",
"evidence": {
"publishedPackage": "@coupler-developer/coupler-api-contracts@0.1.32",
"workflow": "https://github.com/coupler-developer/coupler-api/actions/runs/31771588474",
"sourceRef": "c0e70bbe3b8767e3cebdc82072a389ce8b4cf2c3",
"sourceTree": {
"path": "packages/contracts",
"publishedSourceTree": "e76c3329a2f39087d09f587e6379ef32c2ed885f",
"releaseSourceTree": "e76c3329a2f39087d09f587e6379ef32c2ed885f"
}
}
},
"coupler-api": {
"status": "released",
"summary": "DB보다 먼저 반영된 0628b39에서 schema 불일치가 발생했으며 migration 적용 뒤 a4a63416을 production PM2에 재배포해 HTTP·schema·worker 안정성, 사용자 운영 CMS 쓰기와 type 40 운영 smoke를 확인했다.",
"evidence": {
"deployment": "2026-08-20 19:24:42 KST DB 의존 코드가 포함된 0628b39를 migration보다 먼저 반영해 schema 불일치가 발생했다. 20:41:01 KST migration 2건을 적용하고 20:41:35 KST 운영 API를 a4a6341648c929a8dc5c97a3ca24816eecb117b5로 fast-forward한 뒤 frozen install·production PM2 startOrReload·save로 복구했으며 annotated v2.5.2 peeled target이 같은 commit이다.",
"smoke": "PM2 online|production, startup log port 3002, 내부·외부 root HTTP 200, migration ledger 90건·적용 대상 잔여 0, pinned column·outbox table 존재와 35초 worker 주기 동안 error log 무증가를 확인했다. 출시 책임자가 운영 CMS 고정글 저장·재조회 반영을 확인했고, 2026-08-20 22:06~22:07 KST 전용 TEST 회원의 /admin/lounge/best N→Y가 outbox SENT·최초 시도 1회·type 40 alarm 1건·provider 성공을 통과한 뒤 exact 테스트 산출물 3건을 제거해 fixture·outbox 잔여 0을 확인했다.",
"publicContract": {
"apiRefs": {
"previous": "0628b39a7a39670ac0dd5be384b163b311ba8164",
"current": "a4a6341648c929a8dc5c97a3ca24816eecb117b5"
},
"contractRefs": {
"previous": "@coupler-developer/coupler-api-contracts@0.1.32",
"current": "@coupler-developer/coupler-api-contracts@0.1.32"
},
"consumers": [
{
"state": "present",
"id": "previous-store",
"surface": "mobile-store",
"generation": "previous",
"artifact": {
"kind": "store-builds",
"mappingRef": "Android 2.4.2 (103); iOS 2.4.2 (103)",
"iosVersionBuild": "2.4.2 (103)",
"androidVersionBuild": "2.4.2 (103)"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.30",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "Store 2.4.2 source 37c0da07의 기존 REST, group-chat WebSocket, bootstrap, version 소비 경로를 fallback으로 유지한다."
},
{
"state": "present",
"id": "current-store",
"surface": "mobile-store",
"generation": "current",
"artifact": {
"kind": "store-builds",
"mappingRef": "Android 2.4.2 (103); iOS 2.4.2 (103)",
"iosVersionBuild": "2.4.2 (103)",
"androidVersionBuild": "2.4.2 (103)"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.30",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "NextPush 뒤에도 Store 2.4.2 source 37c0da07은 contracts 0.1.30의 기존 네 인터페이스를 fallback으로 유지한다."
},
{
"state": "present",
"id": "previous-nextpush",
"surface": "mobile-nextpush",
"generation": "previous",
"artifact": {
"kind": "nextpush-deployment",
"mappingRef": "Android bluedotstudio.official-gmail.com/coupler/Production v30 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v27 target 2.4.2",
"ios": {
"app": "bluedotstudio.official-gmail.com/coupler-ios",
"deployment": "Production",
"label": "v27",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
},
"android": {
"app": "bluedotstudio.official-gmail.com/coupler",
"deployment": "Production",
"label": "v30",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
}
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "배포 전 Production history의 Android v30·iOS v27은 contracts 0.1.32 기반 기존 Mobile 네 인터페이스를 소비한다."
},
{
"state": "present",
"id": "current-nextpush",
"surface": "mobile-nextpush",
"generation": "current",
"artifact": {
"kind": "nextpush-deployment",
"mappingRef": "Android bluedotstudio.official-gmail.com/coupler/Production v31 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v28 target 2.4.2",
"ios": {
"app": "bluedotstudio.official-gmail.com/coupler-ios",
"deployment": "Production",
"label": "v28",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
},
"android": {
"app": "bluedotstudio.official-gmail.com/coupler",
"deployment": "Production",
"label": "v31",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
}
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "Mobile 04a3a6f1과 contracts 0.1.32로 생성한 Android v31·iOS v28 bundle이 기존 네 인터페이스를 유지한다."
},
{
"state": "present",
"id": "previous-admin",
"surface": "admin",
"generation": "previous",
"artifact": {
"kind": "admin-build",
"artifactRef": "fe73a0e98ef79c33a66aad3bd62eb473f69d4459"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket"],
"interfaceInventoryEvidence": "배포 전 Admin source fe73a0e9는 contracts 0.1.32 REST와 기존 concierge WebSocket lifecycle을 소비한다."
},
{
"state": "present",
"id": "current-admin",
"surface": "admin",
"generation": "current",
"artifact": {
"kind": "admin-build",
"artifactRef": "08e3e3f747b0776c7ee2b866a315a78d031ae081"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket"],
"interfaceInventoryEvidence": "Admin 08e3e3f7 production artifact는 contracts 0.1.32 REST와 기존 concierge WebSocket lifecycle을 유지한다."
}
],
"cases": [
{"id":"previous-store-rest-current-api","consumerId":"previous-store","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API a4a63416은 기존 Mobile wire best와 REST 응답 shape를 유지하며 운영 반영 후 CMS 저장·재조회를 확인했다."},
{"id":"previous-store-websocket-current-api","consumerId":"previous-store","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API a4a63416은 WebSocket route와 envelope를 변경하지 않았고 전체 API 회귀 검증을 통과했다."},
{"id":"previous-store-bootstrap-current-api","consumerId":"previous-store","interface":"bootstrap","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API a4a63416은 bootstrap 계약을 변경하지 않았고 generated contract 검증을 통과했다."},
{"id":"previous-store-version-current-api","consumerId":"previous-store","interface":"version","apiGeneration":"current","exposure":"rollback","expected":"success","evidence":"API a4a63416은 Store 2.4.2 version 확인 경로를 변경하지 않았다."},
{"id":"current-store-rest-current-api","consumerId":"current-store","interface":"rest","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Store 2.4.2의 legacy best 요청은 API에서 canonical pinned로 변환되고 운영 schema에 저장된다."},
{"id":"current-store-websocket-current-api","consumerId":"current-store","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Store fallback WebSocket 인터페이스는 이번 릴리스에서 변경되지 않았다."},
{"id":"current-store-bootstrap-current-api","consumerId":"current-store","interface":"bootstrap","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Store fallback bootstrap 인터페이스는 이번 릴리스에서 변경되지 않았다."},
{"id":"current-store-version-current-api","consumerId":"current-store","interface":"version","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Store fallback version 인터페이스는 이번 릴리스에서 변경되지 않았다."},
{"id":"previous-nextpush-rest-current-api","consumerId":"previous-nextpush","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"Android v30·iOS v27이 사용한 REST wire best와 free_send 요청은 API a4a63416에서 유지된다."},
{"id":"previous-nextpush-websocket-current-api","consumerId":"previous-nextpush","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"이전 NextPush의 WebSocket 인터페이스는 변경되지 않았다."},
{"id":"previous-nextpush-bootstrap-current-api","consumerId":"previous-nextpush","interface":"bootstrap","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"이전 NextPush의 bootstrap 인터페이스는 변경되지 않았다."},
{"id":"previous-nextpush-version-current-api","consumerId":"previous-nextpush","interface":"version","apiGeneration":"current","exposure":"rollback","expected":"success","evidence":"이전 NextPush rollback 뒤 version 인터페이스는 유지된다."},
{"id":"current-nextpush-rest-current-api","consumerId":"current-nextpush","interface":"rest","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Mobile 04a3a6f1은 pinned를 legacy best wire로 읽고 free_send·0key 수정과 함께 API a4a63416 계약 검증을 통과했다."},
{"id":"current-nextpush-websocket-current-api","consumerId":"current-nextpush","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Android v31·iOS v28 bundle의 WebSocket 소비 경로는 변경되지 않았다."},
{"id":"current-nextpush-bootstrap-current-api","consumerId":"current-nextpush","interface":"bootstrap","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Android v31·iOS v28 bundle은 contracts 0.1.32 bootstrap pin 검사를 통과했다."},
{"id":"current-nextpush-version-current-api","consumerId":"current-nextpush","interface":"version","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Android v31·iOS v28 bundle은 contracts 0.1.32 version pin 검사를 통과했다."},
{"id":"previous-admin-rest-current-api","consumerId":"previous-admin","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API a4a63416은 기존 Admin save best 요청을 canonical pinned로 저장하도록 호환을 복구했다."},
{"id":"previous-admin-websocket-current-api","consumerId":"previous-admin","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"기존 Admin concierge WebSocket 인터페이스는 변경되지 않았다."},
{"id":"current-admin-rest-current-api","consumerId":"current-admin","interface":"rest","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Admin 08e3e3f7은 내부 pinned draft를 저장 시 wire best로 직렬화하고 운영 CMS 저장·재조회 확인을 통과했다."},
{"id":"current-admin-websocket-current-api","consumerId":"current-admin","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Admin 08e3e3f7 전체 검증이 기존 concierge WebSocket lifecycle을 유지했다."}
]
},
"runtimeRecovery": {
"strategy": "forward-fix",
"stateSafety": {
"source": "application-evidence",
"persistedState": "t_lounge.best를 enum 값 보존 rename으로 pinned에 전환하고 outbox를 append-only로 추가했으며 운영 ledger 90건·적용 대상 잔여 0과 pinned column을 확인했다.",
"queuedState": "type 40 intent는 전용 N→Y command에서 같은 transaction으로 outbox에 저장되고 초기·실패·장기 처리 상태의 원자 선점 및 최대 3회 retry를 사용하며 배포 직후 미처리 row는 0건이었다.",
"externalEffects": "상세 /save는 고정 상태만 저장하고 push를 만들지 않으며 전용 /best N→Y만 stable event_id로 type 40을 발송한다. DB 전환 뒤 장애는 a4a63416 기반 forward fix로 복구한다."
},
"previousReleaseCaseIds": []
}
}
},
"coupler-admin-web": {
"status": "released",
"summary": "08e3e3f7 production artifact를 원자 교체하고 HTTP 및 사용자 운영 CMS 고정글 저장·재조회 반영을 확인했다.",
"evidence": {
"deployment": "source 08e3e3f747b0776c7ee2b866a315a78d031ae081의 artifact SHA-256 402b030d43b68715170d98e802edc98aa8913a0bced32b6853e85e1be17b1efe와 index SHA-256 45efcf4e9f47918bc40211108653b03c313688747a6979157b5346f1a6217a26을 검증하고 live index를 원자 교체했으며 annotated v2.5.2 peeled target이 같은 commit이다.",
"smoke": "내부 http://127.0.0.1:8000/과 외부 https://cms.ritzy.fourhundred.co.kr/ HTTP 200 및 새 live index SHA를 확인했고 출시 책임자가 2026-08-20 KST 운영 CMS 고정글 저장·재조회 반영을 직접 확인했다.",
"rollback": "배포 직전 source fe73a0e98ef79c33a66aad3bd62eb473f69d4459와 index SHA-256 aea5978c5aeda534e223be211b252a94f286cadf4b2e5f60e14e59e5d630791f를 /var/www/coupler-admin-web-backup-20260820T114300Z에 metadata와 함께 봉인했다."
}
},
"mobile-nextpush": {
"status": "released",
"summary": "Store 2.4.2 (103)을 대상으로 Mobile source 04a3a6f1의 Android v31·iOS v28 Production NextPush를 배포하고 history를 확인했다.",
"evidence": {
"app": "Android bluedotstudio.official-gmail.com/coupler/Production; iOS bluedotstudio.official-gmail.com/coupler-ios/Production",
"productionLabel": "Android v31; iOS v28",
"androidPackageHash": "7b16480c302274d083acf6031f4c3b0a343af1039c242c0e0fd6b52935ecffb6",
"iosPackageHash": "391da48d5c45c3ca10e821524dceb5c4fa96610afada4cac8a99ef4e6fbb8e71",
"targetBinary": "Android/iOS 2.4.2 (103)",
"uploadedAt": "Android 2026-08-20 20:47:39 KST; iOS 2026-08-20 20:48:42 KST",
"history": "배포 뒤 Production history에서 Android v31·iOS v28, target 2.4.2, mandatory true, disabled false, rollout 100%와 각 package SHA-256을 확인했다.",
"rollbackTarget": "Android v30 package 05cea01e04c094c20a295c7a595ce02d2a3410609f70959e0a178d56d4652107; iOS v27 package 8df2476c695d3616dad60d5848e801b08a9bb74329a4659edb904f1fb494921f",
"rollout": "Android 100%; iOS 100%",
"mandatory": "Android true; iOS true",
"disabled": "Android false; iOS false"
}
},
"docs": {
"status": "released",
"summary": "운영 반영 결과와 rollback 기준, docs v2.5.2 tag를 최종 기록 기준점으로 고정했다.",
"evidence": {}
}
},
"apiContractCutover": null
}
문서 역할¶
- 역할:
시나리오 - 문서 종류:
flow - 충돌 시 우선 문서:
policy/release-process.md, 태그 기준은policy/release-tag-policy.md - 기준 성격:
as-is
목적¶
v2.5.2릴리스의 실제 반영 결과와 검증 근거를 한 문서에 고정한다.
범위¶
- 대상:
docs,coupler-api,coupler-admin-web,coupler-mobile-app - 포함 범위: 라운지 고정글 DB 전환, API·Admin 운영 배포, Store 2.4.2 대상 Android·iOS NextPush, 릴리스 기록
- 제외 범위: Mobile Store 신규 제출, 새 contracts package 발행, 공개 wire
best제거 API cutover
상위 규범 문서¶
릴리스 상태¶
- 목표 버전:
v2.5.2 - 전체 상태:
released - 완료 범위: 운영 DB migration, contracts tree 검증, API·Admin 운영 반영·기능 smoke·서비스 태그, Android·iOS NextPush, docs 최종 기록 기준점
- 대기 범위: N/A
버전 매핑¶
docs: 기록 버전v2.5.2, 태그v2.5.2, 커밋은 최종 기록 병합 뒤 확정coupler-api: 태그v2.5.2, 커밋a4a6341648c929a8dc5c97a3ca24816eecb117b5coupler-admin-web: 태그v2.5.2, 커밋08e3e3f747b0776c7ee2b866a315a78d031ae081coupler-mobile-app: Android Store2.4.2 (103), Android 릴리스 태그v2.4.2, Android 커밋37c0da07d22704c3a28723ec6d08758bff0f1aae, Android sourceverified, iOS Store2.4.2 (103), iOS 릴리스 태그v2.4.2, iOS 커밋37c0da07d22704c3a28723ec6d08758bff0f1aae, iOS sourceverified, NextPushAndroid bluedotstudio.official-gmail.com/coupler/Production v31 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v28 target 2.4.2, NextPush 커밋04a3a6f1569bd316af89274d9f72121a39908c4d- Store 제출 마커 태그와 증빙 이관/삭제:
N/A- Mobile Store 미포함
작성 기준¶
- 신규 기록은 템플릿을 직접 복사하지 않고 docs 레포 루트에서
yarn release:record:init v2.5.2로 초기화해 lifecycle registry,content/AGENTS.md인덱스와mkdocs.ymlnav를 같은 변경 단위로 준비한다. - 이 파일은 입력 골격이다. 상태·scope·evidence의 규범과 closed value는
릴리스 프로세스의
릴리스 운영 모델과릴리스 기록 상태값을 따른다. - 위
release-metadata는 docs-onlyplanned시작형이다. 실제releaseScopes와 같은 key만scopeResults에 추가하고,scripts/release-schema.mjs가 정의하지 않은 key나 별도 완료 축을 만들지 않는다. - scope, exact commit, 검증 시나리오와 rollback 기준을 확정하고 현재 PR head에 적용된 필수 CI를 확인한 뒤
전체 상태와 nonterminal
scopeResults를pending으로 전환한다. 전환을 push한 현재 PR head의 필수 CI 성공을 다시 확인하기 전에는 preflight나 운영 실행을 시작하지 않는다. release-metadata가 기계 판정 SoT이고 본문은 사람이 읽는 mirror다. 두 표현의 범위·상태·버전 기준을 맞춘다.- terminal scope는 정책과 descriptor가 요구하는 concrete evidence로 닫는다. placeholder나
N/A로 완료 증빙을 대신하지 않는다. - API contract cutover가 있을 때만
content/templates/api-contract-cutover-gate-template.md를검증 근거아래에 삽입한다. - DB migration은 DB Migration 실행 런북에 따라 개발계와 운영계에 사용한 동일한 마이그레이션 소스 커밋과 최종 pending 0건을 사람이 읽는 결과에 기록한다. 별도 plan·execution artifact는 만들지 않는다.
main에 이미 병합된 릴리스 기록과 evidence는 수정·삭제·개명·대체하거나 현재 계약으로 재검증하지 않는다.- 개인 사용자명·로컬 home/tmp 경로·secret을 기록하지 않는다. 변동 값에는 확인 시각과 timezone을 남긴다.
- 최종 후보에서
yarn release:preflight --pending-ref <40자 commit SHA>와 적용 docs 품질 Gate를 실행한다.
릴리스 결과¶
- 현재 결과:
released- 모든 서비스 scope와 docs 최종 기록 기준점이 완료됐고 병합 뒤v2.5.2태그·Release·artifact postcheck를 수행한다. - 기록 복구: PR #187이
pending상태로04f0ba206229ae08ec0ac67bd621a5036fc77fd4에 병합됐지만 docs tag와 GitHub Release는 생성하지 않았다. 이 terminalization은 전체 상태와 docs scope·summary 및 허용된 상태 mirror bullet만 정렬했으며 다른 scope metadata/evidence, 버전 매핑, API cutover와 운영 사실은 변경하지 않았다.
메인 흐름¶
- 릴리스 기록 PR Gate와 공통 preflight를 통과한다.
- 19:24:42 KST 운영 API에 DB 의존 코드가 포함된
0628b39를 migration보다 먼저 반영했다. - 20:41:01 KST 운영 DB에 개발계와 같은 migration source
bafbae1f의 migration 2건을 적용했다. - 20:41:35 KST 운영 API
a4a63416을 재배포해 schema 불일치를 복구하고 Admin08e3e3f7의 내부·외부·기능 smoke를 수행했다. - Mobile
04a3a6f1을 Store 2.4.2 대상으로 Android·iOS Production NextPush에 배포했다. - 결과와 rollback label·hash를 기록하고 서비스·docs 태그를 마감한다.
운영 적용 순서 이탈과 복구¶
- 원인:
pinnedcolumn과 알림 outbox를 요구하는 API가 운영 migration보다 먼저 기동됐다. - 관측 영향: 복구 전 운영 error log에서
Unknown column 'L.pinned'64건과t_lounge_pinned_notification_outbox doesn't exist151건을 확인했다. 고유 영향 사용자 수는 확인할 수 없었고 데이터 손실은 관측되지 않았다. - 복구: 20:41:01 KST migration 2건을 적용하고 20:41:35 KST
a4a63416PM2를 기동했다. 이후 HTTP 200, migration 적용 대상 잔여 0,pinnedcolumn·outbox table 존재와 35초 worker 주기 동안 신규 schema error 0건을 확인했다. - 재발 방지: DB 의존 릴리스는 API traffic drain/전체 instance stop 뒤 migration을 먼저 적용하고 새 API를 기동하는 라운지 시스템의 maintenance cutover 순서를 따른다.
라운지 고정글 type 40 운영 샘플¶
- 범위: 2026-08-20 22:06~22:07 KST, 운영에서
TEST로 식별된 전용 회원의 FCM token을 dry-run으로 먼저 검증했다. 일반 사용자 글과 신규 글 topic을 건드리지 않도록MANAGER_ONLYsmoke fixture 1건만 준비했다. - 결과: 운영
/admin/lounge/best의 실제N → Y요청이 성공했고 outbox1은PROCESSING → SENT, 최초 시도 1회,last_error없음으로 종료됐다. 연결된 alarm47842는 type40, target648로 fixture와 일치했으며 provider 전송도 성공했다. - cleanup: 첫 smoke 스크립트가 전달 완료 뒤 cleanup 전 상태를 잘못 기대해 안전 fallback으로 fixture를
tombstone 처리했다. 이후 fixture
648, outbox1, alarm47842의 member·상태·type·target을 exact precondition으로 다시 확인하고 한 transaction에서 세 테스트 산출물만 제거했다. 22:07 KST 사후 조회에서 해당 fixture·outbox·alarm 잔여는 각각 0건이었다.
검증 근거¶
- Preflight 명령·결과: pending PR head
c78cef52a56bd1763a1fe45d4c7c212167e16fbc에서Result: PASS - Scope별 Gate·smoke·artifact: API
a4a63416PM2·HTTP·schema·worker 안정성, Admin artifact/index SHA와 HTTP, Mobile Android v31·iOS v28 history 확인 - Type 40 운영 smoke: 전용 TEST fixture의
/admin/lounge/bestN→Y, outboxSENT, attempt 1, alarm type 40· target 일치, provider 성공과 exact 테스트 산출물 cleanup·잔여 0 확인 - API cutover:
No- 이번 릴리스는 Mobile 공개 wirebest, FCM type 40 ID·payload와 Store 2.4.2 기존 요청·응답을 유지했으며, 이는 legacybest제거 완료 증빙이 아니다. 제거는 라운지bestwire 호환 제거 대기의 목표인 v2.5.2 다음coupler-api운영 릴리스에서 수행한다. - DB 마이그레이션 소스 커밋·개발계/운영계 결과: source
bafbae1f5ca2daa59700cd8adb7f5adc1586c188; 개발계와 운영계 두 migration applied·pending 0, 운영 ledger 90건 - Workflow·로그·수동 검증 링크: API·Admin 병합 PR과 운영 실행 로그, 출시 책임자의 운영 CMS 수동 확인, 최종 기록 docs PR #187
Mobile 개발계 QA 빌드 기록¶
개발계 QA 빌드가 있을 때만 기록한다. 운영 Store·NextPush·서비스 태그 증빙으로 사용하지 않는다.
- 기록일:
- API 대상:
- iOS TestFlight QA 빌드:
- Android QA APK:
- 운영 릴리스 전 확인:
롤백 기준¶
- DB: migration은 임의 역쿼리하지 않고 backup·ledger·postcondition을 기준으로 forward fix한다.
- API: DB 전환 후에는 pre-pinned API로 추측 rollback하지 않고
a4a63416기반 forward fix를 사용한다. - Admin: 배포 직전 live source와 index SHA를 봉인한 backup directory로만 원자 복구한다.
- Mobile NextPush: Android
v30, iOSv27exact release로 rollback한다. - docs: 운영 변경 전에는 같은 PR에서 기록을 보정하며 최종 병합·태그 뒤에는 수정하지 않는다.
후속 작업¶
- 남은 범위: 이 terminal 최종 기록 병합 커밋의 docs
v2.5.2태그·Release/Pages postcheck - 전환 부채: v2.5.2 다음
coupler-api운영 릴리스에서 contracts·Admin·Mobile을 canonicalpinned공개 계약으로 전환하고 지원 소비자 inventory와 결정론적 구best요청 차단을 검증한 뒤 legacy endpoint·필드·adapter· projection을 제거한다. 추적 기준은 라운지bestwire 호환 제거 대기다. - 완료 조건: 현재 운영 증빙 기록을 병합하고 docs 릴리스 postcheck를 완료한다.