{
"schema": "release-metadata/v3",
"version": "v2.5.0",
"status": "released",
"releaseScopes": [
"db-migration",
"contracts-package",
"coupler-api",
"coupler-admin-web",
"mobile-nextpush",
"docs"
],
"extraRepoRefs": [],
"versionMapping": {
"docs": {
"tag": "v2.5.0",
"commit": null
},
"coupler-api": {
"tag": "v2.5.0",
"commit": "c0e70bbe3b8767e3cebdc82072a389ce8b4cf2c3"
},
"coupler-admin-web": {
"tag": "v2.5.0",
"commit": "fcde62ef660e9e06c8721898d38e81bda4af6ecf"
},
"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 v28 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v25 target 2.4.2",
"commit": "6050c86d01e61d45d866c00ef332eda040909909"
}
},
"scopeResults": {
"db-migration": {
"status": "released",
"summary": "개발계 apply·status에서 미적용 migration 0건을 확인한 source c0e70bbe를 그대로 운영계에 적용하고 운영 ledger 88건·미적용 migration 0건을 확인했다.",
"evidence": {}
},
"contracts-package": {
"status": "released",
"summary": "contracts 0.1.32 stable 발행과 Admin·Mobile exact pin, API release 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 반영 뒤 API source c0e70bbe를 production PM2에 배포하고 v2.5.0 annotated tag로 고정했다.",
"evidence": {
"deployment": "2026-08-14 KST /home/projects/coupler-api를 e4e344f3에서 c0e70bbe로 fast-forward하고 frozen install 뒤 production PM2 startOrReload·save를 완료했으며 remote HEAD와 v2.5.0 peeled target이 c0e70bbe3b8767e3cebdc82072a389ce8b4cf2c3으로 일치한다.",
"smoke": "reload 직후 5ms 시점 최초 내부 curl은 readiness race로 connection refused였고 재배포 없이 PM2 online·production, startup log server started on port 3002, 내부 root HTTP 200과 https://api.ritzy.fourhundred.co.kr/ HTTP 200을 확인했다.",
"publicContract": {
"apiRefs": {
"previous": "e4e344f3287c3c81a260ed98d00d1e8ce5a69960",
"current": "c0e70bbe3b8767e3cebdc82072a389ce8b4cf2c3"
},
"contractRefs": {
"previous": "@coupler-developer/coupler-api-contracts@0.1.31",
"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": "Mobile Store v2.4.2 source 37c0da07 exact pins contracts 0.1.30 and contains REST, group-chat WebSocket, bootstrap and version consumers."
},
{
"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": "릴리스 뒤에도 fallback인 Mobile Store v2.4.2 source 37c0da07은 contracts 0.1.30과 REST, group-chat WebSocket, bootstrap, version 소비 경로를 그대로 유지한다."
},
{
"state": "present",
"id": "previous-nextpush",
"surface": "mobile-nextpush",
"generation": "previous",
"artifact": {
"kind": "nextpush-deployment",
"mappingRef": "Android bluedotstudio.official-gmail.com/coupler/Production v27 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v24 target 2.4.2",
"ios": {
"app": "bluedotstudio.official-gmail.com/coupler-ios",
"deployment": "Production",
"label": "v24",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
},
"android": {
"app": "bluedotstudio.official-gmail.com/coupler",
"deployment": "Production",
"label": "v27",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
}
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.30",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "배포 전 Production history의 Android v27·iOS v24는 Store 2.4.2 대상 기존 Mobile 경로로 REST, group-chat WebSocket, bootstrap, version을 소비한다."
},
{
"state": "present",
"id": "current-nextpush",
"surface": "mobile-nextpush",
"generation": "current",
"artifact": {
"kind": "nextpush-deployment",
"mappingRef": "Android bluedotstudio.official-gmail.com/coupler/Production v28 target 2.4.2; iOS bluedotstudio.official-gmail.com/coupler-ios/Production v25 target 2.4.2",
"ios": {
"app": "bluedotstudio.official-gmail.com/coupler-ios",
"deployment": "Production",
"label": "v25",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
},
"android": {
"app": "bluedotstudio.official-gmail.com/coupler",
"deployment": "Production",
"label": "v28",
"cohort": "100%",
"targetBinary": "2.4.2 (103)"
}
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket", "bootstrap", "version"],
"interfaceInventoryEvidence": "Mobile source 6050c86d와 contracts 0.1.32로 생성한 Android v28·iOS v25 bundle은 REST, group-chat WebSocket, bootstrap, version 소비 경로를 포함한다."
},
{
"state": "present",
"id": "previous-admin",
"surface": "admin",
"generation": "previous",
"artifact": {
"kind": "admin-build",
"artifactRef": "bf64bce4a22a62ed6e314c25bef623aa06caebf1"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.31",
"interfaces": ["rest", "websocket"],
"interfaceInventoryEvidence": "배포 전 Admin source map hash로 고정한 bf64bce4는 contracts 0.1.31의 REST 소비와 concierge WebSocket lifecycle을 포함한다."
},
{
"state": "present",
"id": "current-admin",
"surface": "admin",
"generation": "current",
"artifact": {
"kind": "admin-build",
"artifactRef": "fcde62ef660e9e06c8721898d38e81bda4af6ecf"
},
"contractRef": "@coupler-developer/coupler-api-contracts@0.1.32",
"interfaces": ["rest", "websocket"],
"interfaceInventoryEvidence": "Admin source fcde62ef와 production artifact는 contracts 0.1.32 exact pin의 REST 소비 및 기존 concierge WebSocket lifecycle을 포함한다."
}
],
"cases": [
{"id":"previous-store-rest-current-api","consumerId":"previous-store","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe verify의 generated REST contract와 additive group-meeting route 검증이 Store 2.4.2 기존 REST 요청을 유지했다."},
{"id":"previous-store-websocket-current-api","consumerId":"previous-store","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe chat 회귀 검증에서 방 알림 OFF도 전체 member WebSocket publish를 유지했다."},
{"id":"previous-store-bootstrap-current-api","consumerId":"previous-store","interface":"bootstrap","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe verify가 기존 Mobile bootstrap 응답 계약을 변경 없이 통과했다."},
{"id":"previous-store-version-current-api","consumerId":"previous-store","interface":"version","apiGeneration":"current","exposure":"rollback","expected":"success","evidence":"API c0e70bbe verify가 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 v2.4.2 source 37c0da07의 contracts 0.1.30 REST 요청은 API c0e70bbe의 additive route 변경 뒤에도 유지됐다."},
{"id":"current-store-websocket-current-api","consumerId":"current-store","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"API c0e70bbe chat 회귀 검증이 Store v2.4.2 fallback의 group-chat WebSocket envelope를 유지했다."},
{"id":"current-store-bootstrap-current-api","consumerId":"current-store","interface":"bootstrap","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"API c0e70bbe verify가 Store v2.4.2 contracts 0.1.30 bootstrap 응답 계약을 유지했다."},
{"id":"current-store-version-current-api","consumerId":"current-store","interface":"version","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"API c0e70bbe verify가 Store v2.4.2 contracts 0.1.30 version 응답 계약을 유지했다."},
{"id":"previous-nextpush-rest-current-api","consumerId":"previous-nextpush","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"Android v27·iOS v24가 사용하던 REST 경로는 c0e70bbe의 additive 필드·command 추가 뒤에도 제거 또는 변경되지 않았다."},
{"id":"previous-nextpush-websocket-current-api","consumerId":"previous-nextpush","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe chat 회귀 검증이 이전 NextPush 소비자의 WebSocket message envelope를 유지했다."},
{"id":"previous-nextpush-bootstrap-current-api","consumerId":"previous-nextpush","interface":"bootstrap","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe verify가 Android v27·iOS v24의 기존 bootstrap 계약을 유지했다."},
{"id":"previous-nextpush-version-current-api","consumerId":"previous-nextpush","interface":"version","apiGeneration":"current","exposure":"rollback","expected":"success","evidence":"API c0e70bbe verify가 이전 NextPush rollback 뒤 사용할 version 경로를 유지했다."},
{"id":"current-nextpush-rest-current-api","consumerId":"current-nextpush","interface":"rest","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Mobile 6050c86d request body와 API c0e70bbe command response가 contracts 0.1.32 DTO로 결박돼 전체 verify를 통과했다."},
{"id":"current-nextpush-websocket-current-api","consumerId":"current-nextpush","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Mobile 6050c86d race 회귀와 API c0e70bbe chat 회귀가 알림 토글 뒤에도 WebSocket 수신을 유지했다."},
{"id":"current-nextpush-bootstrap-current-api","consumerId":"current-nextpush","interface":"bootstrap","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Android v28·iOS v25 bundle 전 contracts 0.1.32 exact pin 검사가 bootstrap 소비 source와 일치했다."},
{"id":"current-nextpush-version-current-api","consumerId":"current-nextpush","interface":"version","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Android v28·iOS v25 bundle 전 contracts 0.1.32 exact pin 검사가 version 소비 source와 일치했다."},
{"id":"previous-admin-rest-current-api","consumerId":"previous-admin","interface":"rest","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe는 기존 Admin list 기본 query를 보존하고 sort pair만 선택적으로 추가했으며 전체 API verify를 통과했다."},
{"id":"previous-admin-websocket-current-api","consumerId":"previous-admin","interface":"websocket","apiGeneration":"current","exposure":"activation","expected":"success","evidence":"API c0e70bbe는 기존 Admin concierge WebSocket 경로를 변경하지 않았고 전체 회귀 검증을 통과했다."},
{"id":"current-admin-rest-current-api","consumerId":"current-admin","interface":"rest","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Admin fcde62ef와 API c0e70bbe의 sort enum·pair parser·generated query 계약이 contracts 0.1.32 기준으로 전체 verify를 통과했다."},
{"id":"current-admin-websocket-current-api","consumerId":"current-admin","interface":"websocket","apiGeneration":"current","exposure":"post-activation","expected":"success","evidence":"Admin fcde62ef 전체 verify가 기존 concierge WebSocket lifecycle 회귀를 포함해 통과했다."}
]
},
"runtimeRecovery": {
"strategy": "forward-fix",
"stateSafety": {
"source": "application-evidence",
"persistedState": "신규 chat_message_notification_enabled는 NOT NULL DEFAULT 1의 additive column이며 운영 migration ledger 88건·미적용 migration 0건 뒤 기존 row와 새 runtime이 같은 상태를 읽는다.",
"queuedState": "메시지 저장·전체 member WebSocket publish·unread invalidation ownership은 유지되고 방 설정은 type 82 push 수신자 선필터에만 적용된다.",
"externalEffects": "방별 설정은 이후 type 82 FCM과 t_alarm 발송만 제어하며 durable outbox나 다른 77~81·83~85 알림 효과를 변경하지 않아 문제 시 c0e70bbe 기반 forward fix를 사용한다."
},
"previousReleaseCaseIds": []
}
}
},
"coupler-admin-web": {
"status": "released",
"summary": "Admin source fcde62ef의 production build를 원자 교체하고 HTTP·Arc 그룹미팅 정렬 smoke 뒤 v2.5.0 annotated tag로 고정했다.",
"evidence": {
"deployment": "source fcde62ef의 production build main.708d3827.js를 포함한 coupler-admin-web-fcde62ef660e9e06c8721898d38e81bda4af6ecf.tar.gz SHA-256 08e3741cbfbc13f9297514a9ec562ec368f9d206a17b4ba4fbdf1c239b1969b8과 index.html SHA-256 43f7a6c056a85760ec68abe0c6d4b3f9d519dce1d5ed8c19d58c9afa4371f87c를 검증하고 /var/www/coupler-admin-web index를 원자 교체했으며 v2.5.0 peeled target을 fcde62ef660e9e06c8721898d38e81bda4af6ecf로 고정했다.",
"smoke": "내부 http://127.0.0.1:8000/과 외부 https://cms.ritzy.fourhundred.co.kr/가 HTTP 200을 반환했고, 출시 책임자가 2026-08-14 KST Arc 로그인 후 그룹미팅 목록 일시 헤더의 오름차순·내림차순 전환이 정상임을 확인했다.",
"rollback": "배포 직전 source bf64bce4a22a62ed6e314c25bef623aa06caebf1·index SHA-256 acd7f9cfba099686aaf20d672811b9b16c50c3e325cd2010a36644ba40cf4c87을 /var/www/coupler-admin-web-backup-20260814T073253Z에 metadata와 함께 봉인했다."
}
},
"mobile-nextpush": {
"status": "released",
"summary": "Store 2.4.2 (103)을 대상으로 Mobile source 6050c86d의 Android v28·iOS v25 Production NextPush를 배포하고 운영 history를 확인했다.",
"evidence": {
"app": "Android bluedotstudio.official-gmail.com/coupler/Production; iOS bluedotstudio.official-gmail.com/coupler-ios/Production",
"productionLabel": "Android v28; iOS v25",
"androidPackageHash": "9311edd1ad702431653537594378041590f8317987179ed48f00e08a1d896789",
"iosPackageHash": "da3e2803f2abd845bb4375cc4312b3bcff3cbef58ac93a20fc6aa1b54bde2713",
"targetBinary": "Android/iOS 2.4.2 (103)",
"uploadedAt": "Android 2026-08-14 17:01:24 KST; iOS 2026-08-14 17:02:39 KST",
"history": "배포 뒤 Production history에서 Android v28·iOS v25, target 2.4.2, mandatory true, disabled false, rollout 100%와 각 package SHA-256을 확인했다.",
"rollbackTarget": "Android v27 package 99535d8182ad4c799255b0261c85f616df70892f8f1b2d40478e8394ac76fb66; iOS v24 package 10678b5de30746675aaccc4ecad3bbd0c00345666728ebd0a8f6abf9da1a4725",
"rollout": "Android 100%; iOS 100%",
"mandatory": "Android true; iOS true",
"disabled": "Android false; iOS false"
}
},
"docs": {
"status": "released",
"summary": "운영 결과와 NextPush label·bundle hash·rollback target을 최종 기록으로 고정하고 병합 뒤 docs v2.5.0을 발행한다.",
"evidence": {}
}
},
"apiContractCutover": null
}