From 7ab6f21a777858f94ef593cfbe55643ea2a53649 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 May 2026 09:46:44 +0000 Subject: [PATCH 1/7] chore: start codeql alert remediation plan Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --- backend/__pycache__/auth_router.cpython-312.pyc | Bin 0 -> 36075 bytes ...router_security.cpython-312-pytest-9.0.3.pyc | Bin 0 -> 16026 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 backend/__pycache__/auth_router.cpython-312.pyc create mode 100644 tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc diff --git a/backend/__pycache__/auth_router.cpython-312.pyc b/backend/__pycache__/auth_router.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f45d60f3a38c95034a4696bb3626a9ffade27589 GIT binary patch literal 36075 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 literal 0 HcmV?d00001 diff --git a/tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc b/tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f4e03b16861c0ddccfbc6076693f8ffb6612bb1 GIT binary patch literal 16026 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 Date: Fri, 8 May 2026 09:48:46 +0000 Subject: [PATCH 2/7] fix: remediate CodeQL security and quality findings Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --- .../__pycache__/auth_router.cpython-312.pyc | Bin 36075 -> 0 bytes backend/auth.py | 29 ++++++++---------- backend/llm/orchestrator.py | 26 +++++++++++----- backend/main.py | 8 +++-- backend/marketplace/router.py | 15 ++++++--- .../hooks/use-feature-orchestrator.ts | 4 +-- gpu-llm-server/custom-server/server.py | 2 +- ...uter_security.cpython-312-pytest-9.0.3.pyc | Bin 16026 -> 0 bytes 8 files changed, 48 insertions(+), 36 deletions(-) delete mode 100644 backend/__pycache__/auth_router.cpython-312.pyc delete mode 100644 tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc diff --git a/backend/__pycache__/auth_router.cpython-312.pyc b/backend/__pycache__/auth_router.cpython-312.pyc deleted file mode 100644 index f45d60f3a38c95034a4696bb3626a9ffade27589..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36075 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 diff --git a/backend/auth.py b/backend/auth.py index 3e4a6bf..89839ec 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -16,23 +16,18 @@ def _resolve_secret_key() -> tuple[str, bool]: if configured: return configured, False - fallback_path = Path( - os.getenv( - "SECRET_KEY_FILE", - str(Path(os.getenv("TEMP") or "/tmp") / "codeai_jwt_secret.key"), - ) - ) - try: - fallback_path.parent.mkdir(parents=True, exist_ok=True) - if fallback_path.exists(): - cached_secret = fallback_path.read_text(encoding="utf-8").strip() - if cached_secret: - return cached_secret, True - generated_secret = secrets.token_urlsafe(48) - fallback_path.write_text(generated_secret, encoding="utf-8") - return generated_secret, True - except Exception: - return secrets.token_urlsafe(48), True + configured_file = str(os.getenv("SECRET_KEY_FILE") or "").strip() + if configured_file: + fallback_path = Path(configured_file).expanduser() + try: + if fallback_path.exists() and fallback_path.is_file(): + cached_secret = fallback_path.read_text(encoding="utf-8").strip() + if cached_secret: + return cached_secret, True + except Exception: + pass + + return secrets.token_urlsafe(48), True SECRET_KEY, SECRET_KEY_IS_RUNTIME_FALLBACK = _resolve_secret_key() diff --git a/backend/llm/orchestrator.py b/backend/llm/orchestrator.py index 0bf7c48..9a90634 100644 --- a/backend/llm/orchestrator.py +++ b/backend/llm/orchestrator.py @@ -2694,9 +2694,8 @@ def _runtime_progress_root() -> Path: return progress_root -def _orchestration_progress_path(run_id: str) -> Path: - safe_run_id = re.sub(r"[^a-zA-Z0-9_.-]+", "-", str(run_id or "unknown")).strip("-") or "unknown" - return _runtime_progress_root() / f"{safe_run_id}.json" +def _orchestration_progress_path() -> Path: + return _runtime_progress_root() / "progress_store.json" def _build_progress_poll_url(run_id: str) -> str: @@ -2712,8 +2711,17 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s normalized["run_id"] = str(run_id or normalized.get("run_id") or "") normalized.setdefault("updated_at", datetime.utcnow().isoformat() + "Z") _ORCHESTRATION_PROGRESS_STORE[normalized["run_id"]] = normalized - progress_path = _orchestration_progress_path(normalized["run_id"]) - progress_path.write_text(json.dumps(normalized, ensure_ascii=False, indent=2), encoding="utf-8") + progress_path = _orchestration_progress_path() + persisted_payload: Dict[str, Any] = {} + try: + if progress_path.exists() and progress_path.is_file(): + existing_payload = json.loads(progress_path.read_text(encoding="utf-8")) + if isinstance(existing_payload, dict): + persisted_payload = dict(existing_payload) + except Exception: + persisted_payload = {} + persisted_payload[normalized["run_id"]] = normalized + progress_path.write_text(json.dumps(persisted_payload, ensure_ascii=False, indent=2), encoding="utf-8") return normalized @@ -2721,13 +2729,15 @@ def _load_orchestration_progress(run_id: str) -> Dict[str, Any]: cached = _ORCHESTRATION_PROGRESS_STORE.get(str(run_id or "")) if isinstance(cached, dict) and cached: return dict(cached) - progress_path = _orchestration_progress_path(run_id) + progress_path = _orchestration_progress_path() try: if progress_path.exists() and progress_path.is_file(): payload = json.loads(progress_path.read_text(encoding="utf-8")) if isinstance(payload, dict): - _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(payload) - return dict(payload) + stored = payload.get(str(run_id or "")) + if isinstance(stored, dict): + _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(stored) + return dict(stored) except Exception: return {} return {} diff --git a/backend/main.py b/backend/main.py index 65c89cc..092cc99 100644 --- a/backend/main.py +++ b/backend/main.py @@ -250,7 +250,7 @@ def _start_ad_order_worker_thread() -> None: try: from backend.marketplace.router import run_ad_order_worker - except Exception as exc: + except Exception: logger.warning(f"[WARN] ad order worker import failed: {exc}") return @@ -1031,6 +1031,8 @@ def _runtime_health_payload() -> Dict[str, Any]: from backend.marketplace.router import get_ad_queue_runtime_status queue_runtime = get_ad_queue_runtime_status() except Exception as exc: + logger.exception("Failed to load ad queue runtime status") + safe_queue_error = "queue_runtime_unavailable" queue_runtime = { "redis_queue": { "available": False, @@ -1038,7 +1040,7 @@ def _runtime_health_payload() -> Dict[str, Any]: "note": "Redis queue 진단을 로드하지 못했습니다.", "connection_id": "redis:video_render_queue", "queue_name": "video_render_queue", - "error": str(exc), + "error": safe_queue_error, }, "ad_worker": { "available": False, @@ -1047,7 +1049,7 @@ def _runtime_health_payload() -> Dict[str, Any]: "connection_id": "redis:video_render_queue", "queue_name": "video_render_queue", "worker_id": "ad-render-worker-001", - "error": str(exc), + "error": safe_queue_error, }, } redis_queue = queue_runtime.get("redis_queue", {}) diff --git a/backend/marketplace/router.py b/backend/marketplace/router.py index fc322c7..6d8e5b4 100644 --- a/backend/marketplace/router.py +++ b/backend/marketplace/router.py @@ -595,15 +595,20 @@ def _persist_progress(*, percent: int, step: str, state: str, message: str) -> N ), ) except Exception as exc: + logger.exception( + "Marketplace feature orchestrate stream failed run_id=%s", + request.run_id, + ) + public_error_message = "라이브뷰 실행 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요." local_metadata["popup_state"] = "failed" local_metadata["last_event"] = "failed" - local_metadata["error"] = str(exc) + local_metadata["error"] = public_error_message local_metadata["updated_at"] = _utc_now_iso() - _persist_progress(percent=100, step="failed", state="failed", message=str(exc)) + _persist_progress(percent=100, step="failed", state="failed", message=public_error_message) local_stage_run = _set_feature_metadata(local_stage_run, local_metadata) - local_stage_run = _apply_feature_popup_state(local_stage_run, "failed", str(exc)) + local_stage_run = _apply_feature_popup_state(local_stage_run, "failed", public_error_message) save_stage_run(local_stage_run) - yield _build_feature_sse_event("failed", {"run_id": request.run_id, "state": "failed", "message": str(exc)}) + yield _build_feature_sse_event("failed", {"run_id": request.run_id, "state": "failed", "message": public_error_message}) yield _build_feature_sse_event( "progress", _build_feature_progress_payload( @@ -611,7 +616,7 @@ def _persist_progress(*, percent: int, step: str, state: str, message: str) -> N percent=100, step="failed", state="failed", - message=str(exc), + message=public_error_message, ), ) diff --git a/frontend/frontend/hooks/use-feature-orchestrator.ts b/frontend/frontend/hooks/use-feature-orchestrator.ts index ad39af5..7cb8b72 100644 --- a/frontend/frontend/hooks/use-feature-orchestrator.ts +++ b/frontend/frontend/hooks/use-feature-orchestrator.ts @@ -358,7 +358,7 @@ function buildDefaultCatalogItem(featureId: string): FeatureCatalogItem { const meta = FEATURE_EXPERIENCE_META[featureId] || FEATURE_EXPERIENCE_META['ai-sheet']; return { feature_id: featureId, - title: meta.popupKicker.replace('AI ', 'AI '), + title: meta.popupKicker, summary: meta.launcherSummary, popup_mode: preset.contextTags[1] || meta.outputKind, status: 'enabled', @@ -958,4 +958,4 @@ export function useFeatureOrchestrator() { progressSnapshot, progressHistory, }; -} \ No newline at end of file +} diff --git a/gpu-llm-server/custom-server/server.py b/gpu-llm-server/custom-server/server.py index 606bccf..1cb3cf0 100644 --- a/gpu-llm-server/custom-server/server.py +++ b/gpu-llm-server/custom-server/server.py @@ -176,7 +176,7 @@ def load_model(): logger.error(f"Failed to load model: {e}") model = None tokenizer = None - model_load_error = str(e) + model_load_error = "model_load_failed" logger.warning("Server will stay up without a loaded model.") diff --git a/tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc b/tests/__pycache__/test_auth_router_security.cpython-312-pytest-9.0.3.pyc deleted file mode 100644 index 2f4e03b16861c0ddccfbc6076693f8ffb6612bb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16026 zcmeG@TW}OtcHJ{Q&z{lfG3Wuvk^o`G1|fmO!{+@o21z{Rm%Ogdpl-p4(Tse%1xTLp z%DYZ161HGt$KZsN*;J|`);J-XO^T0vIQ~dnm8xV04W3Casim^}<3EgCMHN-4$~pIT z_q0ZXgg2z(r1olb`rO;+-h1xzJnrp(4hH=kg!&u*8Cy}yalgWd83Y5F`57RWIGL0A zIM>h9r(@VT;NZDoq2I-`H|~BnedFo((5JWG3r|PfH|*>84g353l+GFV4vYPwktfg} z02(149IoiE;5i4kpOanha#%9jA*fBVt{eMAa6uwo&w|&r_iqGcgQ?c=ad})1=$JEH6}z{x?kECJ*6BORmj+Y z(HNQ^aVPxGRMS#Ye2SCk3j|}_HNNK>ue+pCRUy|Lx}YlYL0!O@iaDE`AMRRvGC8cQ zC8LRiLe`#1l2dC3lCrY%fcelARa4g@l%k{BNr@y!HHAp3GB8SF+F0AjnC_LNSR$rL z(s)gg%{DVJ02Qe|025qcQNyJ0BmWFUxB;^RFoJjp{%70(E^#UD7)Nj>UE?B7QUQoA zM9B$Ncb__gPZgWo+}unm@s)cp76+Ebt8A6CBrH<(0D!7PHIpL2UYb)e?Z3tF1}h$d zXK65)AtJoE##4S&_RBS!1{lfUU@r<{Dvd|N8W2YrxrbrTu ztu&mJN8^YGB$vRgjs~Awc#>7y*__+fBTPAwa%VqEF&FUbq0QT~tNiq9qXo^LekY(iC2N#roSN3u9pfU7o_#R5 z*gv`}D$9zj`v#K3!!b=$WD!TWp_>#z&P|Y&&5X#zm89h%`Yy6=xlEuQzbPf6kxxHe|gGms@Xpn;F+u z{{a>DAMIPV8PO4kBo^q1_eFTJ4C15#fPD?Q+0c(yn6_(IDZht-=Kfv~{PU z%`kHy%0jt$GlbM7K?Yf8N*$ila9^Tf2-jyxkR9eYrg;y09<%4jL+_S&bojU&-8Q~liYKG8 zUFNlIi6;l5adjIYjOZCG6leWAY+oL)F!^A6!YnS#l`>OLxhM*J6!$3XnW%rbVxEen zW7_EPmq2{$j+m^wl;LPBu6t3Qqr?EQQo1bGL0x1$f=&dpTdVu5@<<1~#!cLiBZXkq zq@z$-H|Z+WG@kRaCD8@%BYxefj;Xqbi7ZujjwDBPmo_${sJg$K%CSC(=N5_rEQ9dZIfg)c?A=E?>PYTfHo^eEoEF=fuGWg46ZX*BpR*ey+Y{;$Th)-xWf6 zp)M=bU2%Raek4K}Hyd-p{z7ST*NwMw!tOg_I4>^CicsXHX>oH#*gSUw#f<3_3o+;ejA~HI(Hv457R%37;78 z)111vFVCv&FLSiE(Q6;)0x7l)$NP0Zy%reE~X` z)gu2D*Eo!I+9Qi{AmxeiH0IS7nO$ou3FTo)NO{eY%;yh4gq`KnPWTt#-wiW_8$wxL z5@3!7%^3%xuIvnb?0G2sb{^Wh_gkzOYj*wrpNF7cR`lpiI}aV|eMRc&?UDArdicQ6 z13mktoqM_u^hkZZ2lw_I+XpteheTs)EODX_E%}%#MdjgGLJt6@j*cheBUmSU9-#0XTzYLT=g zs4o8WDlB7tM2+~<%`ughxGAFQqD`gI{gf|ixr6=h92sjzunxg`1YHO=jfbKtFs#us zI)DdV7#Ztm*9DD?Dv_Y>2Gc}IoTjpnph(bHJ|2_0f5*rev-*i%C4t@(mxwY#KrbJO z5=9TdYw7qXSl+P&*yT(!((BE_kyJVnPacoPshJC^h8i7IFj=qINm&Mm$X-H{M6Zzs zlVmumVQ0moi72{CbT=prz<9(*b^zDpAc8~4zlasHMt9gFo%YB^dt?KPuu3*F&RL|R zgBlm8MbNiYG$Y%Le+B>EVQ|(f>i-sZQ&CYp4z6I3+Ub+<`nae}Ivr97F`9N2>8EH2q2d$0RUp$m$f z(FU;i%C;xvl>I2V@jor31X-k*OAdg#=SsOxtwO~i@ zwbA~KkJHCT-2lcNHifISK`StvpgTzmv;M^2j5+6$A9q5o&=F9j)H56 zNXL_-3EAe=A!tyJAEMi-szr$=jljkYK4uw2$w2Zn_{dPcsS;M39F`y(+O6ZMeoibZp%U3PLqX@(xr2m}1ZgLfwIq zxdTDx!wQ>t-T=}W5;y4Xb{epdmRW_g6blJbkqrWMZkMTA5(KboAg)A-j)rwq(+G8J zF$rJyh`!dB9yYrFsL}h!a8Qma;GR@9@-p_`D+pdk@I3@?0EqaDB3O5TMeikWzL89* ziXJrk3u@F9yUcC3N7maT>+I6F%O2UNqE%^#Vt6oV{M>IoeaL%I-ahzO+o_PYNiY=G ztFpqXywH{v+Q1cHS?!m_tFLF*9m)yacYKu}G+o_!L-?#KS9M_8_u|B^yP?JT(7J4B zUBO$E_qJ!f?S<-wLT%)pi}Q8Na8959o|p59GoriFJ@LZ5N^VheKD;R#-gL7*v#US5 z`Soo0dlTISu|CtVIxDULC%}z*02y)3jry#(IWOSvwvoVM(=^8LTS(7PI|Py^U1YAD zdje$_m7YI^b@wEtr_84vvV+M%**(91)0DY3`RTcC8LH#JqU^ikeuT_(Y=QTWkg5*Z zFN@a$rW8&)HOsFFIm$RBWXCv6SW}+t;V$xTG@~rlaId6csEq8>EJNGO*~Ni@%a=2w zQ^HsVz5&hE|wZ@oA0@b>|ezM>kwaDCz)UoE9lT>nIIHN^p`?EM zB7c6lz~Y^%QR ziLH9OOKSdAZ&AXkuaoPa7zfsQ(t`VrMF||#+xo5C9%ao3x#5bF?h^rdvAiS|F!qVy zWA76|bAK?O=01`3jB(4k4mdYG19xR!<9PVH=sfQ_>wJwn!_RrcpTE!EM3(mGK}*F& zb?PfTRv@PU?9<&Fx;odd>mX<@euW+)aDI6}d4^jaDjxe&H901eIK0xmOe2N^`Xojd z%~Fs-4ONCmG&lyLUN6w=quRrYn5-n==x1!r2uUUf$r;E8`g#Jh*P8F(Hi=E|re()| z3_OxIf$SPT-h)~;sMgF42gv+xIC`?}Pq(2~I;v7j0vv6IhQ1NBaIbQ90IsYICa(#k z8>%2Z2sGpg6!eX=wD!$RYu_CE6c_@P-JM8k&9MaN{(F!saJeB8}b$cRKs-ls5X#Do*{VDqPLbR_b;P0HFm)0enZRG zU0`dSP{0d#c;~TWuk;?>BOTqlyZ7b2M_-e=cfKkCa$w(U($2oVz1@fVj_GdZ1k~N= zG*wiBCr5e<9rm#@jWP$6;1bbA+W^?ii&xhRqj^R;Dp$i_T;BfEH+F15Y`f$H(KsLYFo^KW z;_AGBzuQIvi&-!W`8KiGbP>(b(rCV0G#{e?e5%IEY9UZ`A<~(TbY~;o8KFBPwqyVi zyYmA6Zks7AYQZc`XVGaJZV}uPx~G;{?@bg;R4CmdvJjwt5W+G1nvUO?%Vk z$wWsi4d6XSi`4Msnl+j6GB%;3tW5}ae#+W}0@#G~h@aYos2whB6XFqnsN{(M$=QUK z&bOd$W>G$dO=wxs3+Qmg1NQkfQ_Fj8*@PZy6BZBHAJ-j3mhMqE_X$RFana{wMzCX_R4 zOeweq9G=&lEIZ8^fVY<9*PY3@e9b}KuX^ZYR6Pj-(aaiq-&EnqA7SBCcwa*7BLsg8 zpuA#3{uJM%RzrS*;Li|H&0`5;+pIx49gAA7f+mNSLm8h~@209D1jfJI=LgQLcID%cEZv-nqJO{%c^j>}QW@|3IV>+<&o1n+%f8c}??uEHP z_qdmkb75>ME}S(H|@uWzMom#S=+ube;y zXM(a@_7we`Ljjl&CBgK!6@8WT6wodwMKjB9mS6T?@s#%~$fDs*weD}4HBldH8A%9j z^2mYfK~v^|f76g{X>6@auDD`cI0NQvKUl!PKBNTjL-Xp0i0+Bnzr$Tf11^ zyqe`G=b=rz;bNaJUsN%gTM%NOipixPc8 zHm=K!hovnpKxdr{%k_rm#w$0-i>)(eQG496n9sxOhdy@>mGOhac(~Yjz(vc)8I2`$ zG?tc(M&lEk@s_r<;AmKsFdB^u^zwIZG-NRyD8H}4XarI|@Rzz$t_9b|Q*hapmzyw0 zacwNn7Xee-lbfH|s9C|ev~Mt+9i z6o5YRKBB04+LT&PsZ#raMf53RmNHBJ5~F_w08jp@{-p2~8_^s1hE+knBJlS)kp= znm2*bcM+h6(0;SPxEshW{cRw>gEZ8ENOkrfKvWF^nB@~>F2VKX^!Cva`~Xyya9Aa{ zx+2l57;t}-d9lggLs=KFQ2bVIe&zj+xcGAP#-g0KabgeLH@F!7gGRVzVE6^=3(M9O zzJ*dB@|Gy|pnq2`$%i&%LmOsYSg3C;G&~C$JlW1OlGm^BAOGnKwgW;LV&s;hVo)lR&2@$tFCqd$cRl> zyRsr&#=zfgBZ0*%2$wY`J6UYHh~_9UOTLB0S@aeH)CDnkVaqvaQPbs40FztbAvWa& z{M}}87O`L!QrEHAbP>%VEla(HWm)7F0>lMzaYop8_1XNgec5IE0G-^Pfv31HFW~RC zk-%cp7-OgfS#C-Uuc07{=7fDvj74N2kWTMHMe1~~c^{XcIjc8F%z59&?jXUvrZ%FS zfsc0K@{^SW*P&S&TZY>pBiVppGXlD11;pq&{s~~ZXD|wWeYn<)7CHMIlG@h0F{&WI zYd6#zPR&@l97?`Cg72)@_e0yr=Rl0+irNR@o|EVKU*d;((_G~*IR7uWz%RK~|IW4i ziVFgK;Pzvcius15S)Jr02T%~kw!_gDG&qx}8)N Date: Fri, 8 May 2026 09:56:15 +0000 Subject: [PATCH 3/7] fix: finalize CodeQL remediation hardening updates Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --- backend/auth.py | 6 ++++- backend/llm/orchestrator.py | 49 ++++++++++++++++++++++--------------- backend/main.py | 2 +- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/backend/auth.py b/backend/auth.py index 89839ec..511f234 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -10,6 +10,8 @@ from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer +logger = logging.getLogger(__name__) + def _resolve_secret_key() -> tuple[str, bool]: configured = str(os.getenv("SECRET_KEY") or "").strip() @@ -27,6 +29,9 @@ def _resolve_secret_key() -> tuple[str, bool]: except Exception: pass + logger.error( + "SECRET_KEY/SECRET_KEY_FILE is not configured; generating ephemeral runtime secret that invalidates tokens on restart." + ) return secrets.token_urlsafe(48), True @@ -37,7 +42,6 @@ def _resolve_secret_key() -> tuple[str, bool]: ) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") -logger = logging.getLogger(__name__) def get_password_hash(password: str) -> str: diff --git a/backend/llm/orchestrator.py b/backend/llm/orchestrator.py index 9a90634..55d5a0f 100644 --- a/backend/llm/orchestrator.py +++ b/backend/llm/orchestrator.py @@ -2684,6 +2684,7 @@ class OrchestrationAcceptedResponse(BaseModel): _ORCHESTRATION_PROGRESS_STORE: Dict[str, Dict[str, Any]] = {} +_ORCHESTRATION_PROGRESS_FILE_LOCK = threading.Lock() def _runtime_progress_root() -> Path: @@ -2694,7 +2695,7 @@ def _runtime_progress_root() -> Path: return progress_root -def _orchestration_progress_path() -> Path: +def _orchestration_progress_store_path() -> Path: return _runtime_progress_root() / "progress_store.json" @@ -2711,17 +2712,23 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s normalized["run_id"] = str(run_id or normalized.get("run_id") or "") normalized.setdefault("updated_at", datetime.utcnow().isoformat() + "Z") _ORCHESTRATION_PROGRESS_STORE[normalized["run_id"]] = normalized - progress_path = _orchestration_progress_path() - persisted_payload: Dict[str, Any] = {} - try: - if progress_path.exists() and progress_path.is_file(): - existing_payload = json.loads(progress_path.read_text(encoding="utf-8")) - if isinstance(existing_payload, dict): - persisted_payload = dict(existing_payload) - except Exception: - persisted_payload = {} - persisted_payload[normalized["run_id"]] = normalized - progress_path.write_text(json.dumps(persisted_payload, ensure_ascii=False, indent=2), encoding="utf-8") + progress_path = _orchestration_progress_store_path() + with _ORCHESTRATION_PROGRESS_FILE_LOCK: + persisted_payload: Dict[str, Any] = {} + try: + if progress_path.exists() and progress_path.is_file(): + existing_payload = json.loads(progress_path.read_text(encoding="utf-8")) + if isinstance(existing_payload, dict): + persisted_payload = dict(existing_payload) + except Exception: + logger.warning( + "Failed to read orchestration progress store from %s before write", + str(progress_path), + exc_info=True, + ) + persisted_payload = {} + persisted_payload[normalized["run_id"]] = normalized + progress_path.write_text(json.dumps(persisted_payload, ensure_ascii=False, indent=2), encoding="utf-8") return normalized @@ -2729,16 +2736,18 @@ def _load_orchestration_progress(run_id: str) -> Dict[str, Any]: cached = _ORCHESTRATION_PROGRESS_STORE.get(str(run_id or "")) if isinstance(cached, dict) and cached: return dict(cached) - progress_path = _orchestration_progress_path() + progress_path = _orchestration_progress_store_path() try: - if progress_path.exists() and progress_path.is_file(): - payload = json.loads(progress_path.read_text(encoding="utf-8")) - if isinstance(payload, dict): - stored = payload.get(str(run_id or "")) - if isinstance(stored, dict): - _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(stored) - return dict(stored) + with _ORCHESTRATION_PROGRESS_FILE_LOCK: + if progress_path.exists() and progress_path.is_file(): + payload = json.loads(progress_path.read_text(encoding="utf-8")) + if isinstance(payload, dict): + stored = payload.get(str(run_id or "")) + if isinstance(stored, dict): + _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(stored) + return dict(stored) except Exception: + logger.error("Failed to load orchestration progress for run_id=%s", str(run_id or ""), exc_info=True) return {} return {} diff --git a/backend/main.py b/backend/main.py index 092cc99..f520ea4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -250,7 +250,7 @@ def _start_ad_order_worker_thread() -> None: try: from backend.marketplace.router import run_ad_order_worker - except Exception: + except Exception as exc: logger.warning(f"[WARN] ad order worker import failed: {exc}") return From b2cd5137d922182d88500aa129de2021e7d5c342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=B2=A0=ED=99=8D?= <111139476+parkcheolhong@users.noreply.github.com> Date: Mon, 11 May 2026 18:35:02 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4,=20=EB=B3=91=ED=95=A9?= =?UTF-8?q?=ED=95=B4=EC=A3=BC=EC=84=B8=EC=9A=94=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Harden Pillow dependency floor to patched range for active image parsing CVEs (#7) * chore: raise Pillow minimum version to 12.2 Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/9ec743ae-a698-4cc0-aa87-8825771cb8d6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * chore: remove accidental pycache artifacts Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/9ec743ae-a698-4cc0-aa87-8825771cb8d6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * Harden orchestrator/auth error surfaces and remove CodeQL-flagged unsafe patterns (#8) * chore: start codeql alert remediation plan Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * fix: remediate CodeQL security and quality findings Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * fix: finalize CodeQL remediation hardening updates Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/e096e163-c0eb-430e-95b8-006690b13d72 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * Sanitize health diagnostic errors to avoid exception detail exposure (#9) * fix: redact health diagnostic exception details Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * test: make health sanitization checks portable Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * chore: remove compiled test artifacts Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * refactor: normalize diagnostic error codes Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * test: share diagnostic error code fixture Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * refactor: simplify safe diagnostic code map Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/5d18c2d0-8dda-4817-837b-37752598afa6 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * Potential fix for code scanning alert no. 4: Information exposure through an exception (#10) Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix(ci): set explicit python-version in codeql workflow (#11) Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/4ea2a28e-7f09-4b9d-a3df-785939fa43ac Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * fix: webauthn stub in tests, timezone-aware datetimes, Pydantic v2 ConfigDict, add .gitignore (#12) * fix(tests): stub webauthn in auth_router test fixture to fix import failures Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/63299979-62f4-489f-a1d2-307336759de9 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * fix: stub webauthn in tests, replace datetime.utcnow, fix Pydantic Config, add .gitignore Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/63299979-62f4-489f-a1d2-307336759de9 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * Add consolidated design-change report and expanded PR body draft (#13) * docs: add overall design change and PR report Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/82b0addf-4e64-42b9-ac75-63d99a14f84d Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * docs: make PR report paths portable Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/82b0addf-4e64-42b9-ac75-63d99a14f84d Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> * docs: clarify bilingual PR report structure Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/82b0addf-4e64-42b9-ac75-63d99a14f84d Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 6 + .gitignore | 58 ++++++ backend/auth.py | 4 +- backend/auth_router.py | 25 ++- backend/main.py | 67 +++++-- backend/marketplace/router.py | 5 +- ...rall-design-change-pr-report-2026-05-08.md | 182 ++++++++++++++++++ pyproject.toml | 2 +- tests/test_auth_router_security.py | 45 ++++- tests/test_health_diagnostics_sanitization.py | 105 ++++++++++ 10 files changed, 458 insertions(+), 41 deletions(-) create mode 100644 .gitignore create mode 100644 docs/overall-design-change-pr-report-2026-05-08.md create mode 100644 tests/test_health_diagnostics_sanitization.py diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 239520b..4357ddc 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,6 +32,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 + - name: Setup Python + if: matrix.language == 'python' + uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88555e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.Python +*.egg +*.egg-info/ +dist/ +build/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.whl +.installed.cfg + +# Virtual environments +.venv/ +venv/ +env/ +ENV/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# IDEs +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Environment variables +.env +.env.* +!.env.example + +# Node +node_modules/ +npm-debug.log* + +# Distribution +*.tar.gz +*.zip diff --git a/backend/auth.py b/backend/auth.py index 511f234..deabddf 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -2,7 +2,7 @@ import os import bcrypt import secrets -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Optional @@ -67,7 +67,7 @@ def create_access_token( ) -> str: to_encode = data.copy() if not no_expiry: - expire = datetime.utcnow() + ( + expire = datetime.now(timezone.utc) + ( expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) to_encode.update({"exp": expire}) diff --git a/backend/auth_router.py b/backend/auth_router.py index 0fb4767..1ae751d 100644 --- a/backend/auth_router.py +++ b/backend/auth_router.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from secrets import compare_digest, randbelow, token_urlsafe import base64 import os @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.security import OAuth2PasswordRequestForm from typing import Optional, cast -from pydantic import BaseModel, EmailStr +from pydantic import BaseModel, ConfigDict, EmailStr from sqlalchemy.orm import Session from webauthn import ( generate_authentication_options, @@ -82,8 +82,7 @@ class UserResponse(BaseModel): business_registration_number: Optional[str] = None representative_name: Optional[str] = None - class Config: - from_attributes = True + model_config = ConfigDict(from_attributes=True) class Token(BaseModel): @@ -254,7 +253,7 @@ class PasswordRecoveryResetResponse(BaseModel): def _issue_recovery_token(prefix: str) -> tuple[str, datetime]: - expires_at = datetime.utcnow() + timedelta(minutes=10) + expires_at = datetime.now(timezone.utc) + timedelta(minutes=10) return f"{prefix}_{token_urlsafe(24)}", expires_at @@ -273,7 +272,7 @@ def _normalize_password_recovery_scope(scope: str) -> str: def _purge_expired_password_recovery_sessions() -> None: - now = datetime.utcnow() + now = datetime.now(timezone.utc) expired_tokens = [ session_token for session_token, session_state in _password_recovery_store.items() @@ -380,7 +379,7 @@ def start_passkey_registration( "user_id": int(user.id), "challenge": _to_base64url(options.challenge), "device_label": str(payload.device_label or "이 기기 패스키").strip() or "이 기기 패스키", - "expires_at": datetime.utcnow() + timedelta(minutes=5), + "expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), "user_handle": user_handle, "rp_id": rp_id, "expected_origin": expected_origin, @@ -403,7 +402,7 @@ def finish_passkey_registration( raise HTTPException(status_code=404, detail="패스키 등록 세션을 찾을 수 없습니다") expires_at = state.get("expires_at") - if not isinstance(expires_at, datetime) or expires_at <= datetime.utcnow(): + if not isinstance(expires_at, datetime) or expires_at <= datetime.now(timezone.utc): _passkey_registration_store.pop(payload.registration_token, None) raise HTTPException(status_code=410, detail="패스키 등록 세션이 만료되었습니다") @@ -430,7 +429,7 @@ def finish_passkey_registration( user.passkey_public_key = _to_base64url(verification.credential_public_key) user.passkey_device_label = str(state.get("device_label") or "이 기기 패스키") user.passkey_sign_count = int(verification.sign_count) - user.passkey_registered_at = datetime.utcnow() + user.passkey_registered_at = datetime.now(timezone.utc) db.add(user) db.commit() _passkey_registration_store.pop(payload.registration_token, None) @@ -465,7 +464,7 @@ def start_passkey_login( ) _passkey_login_store[str(user.email)] = { "challenge": _to_base64url(options.challenge), - "expires_at": datetime.utcnow() + timedelta(minutes=5), + "expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), "credential_id": str(user.passkey_credential_id), "rp_id": rp_id, "expected_origin": expected_origin, @@ -492,7 +491,7 @@ def finish_passkey_login( raise HTTPException(status_code=404, detail="패스키 로그인 세션을 찾을 수 없습니다") expires_at = state.get("expires_at") - if not isinstance(expires_at, datetime) or expires_at <= datetime.utcnow(): + if not isinstance(expires_at, datetime) or expires_at <= datetime.now(timezone.utc): _passkey_login_store.pop(str(user.email), None) raise HTTPException(status_code=410, detail="패스키 로그인 세션이 만료되었습니다") @@ -581,7 +580,7 @@ def verify_password_recovery_identity(payload: PasswordRecoveryVerifyIdentityReq raise HTTPException(status_code=404, detail="복구 세션을 찾을 수 없습니다") expires_at = session_state.get("expires_at") - if not isinstance(expires_at, datetime) or expires_at <= datetime.utcnow(): + if not isinstance(expires_at, datetime) or expires_at <= datetime.now(timezone.utc): _password_recovery_store.pop(payload.recovery_session_token, None) raise HTTPException(status_code=410, detail="복구 세션이 만료되었습니다") @@ -638,7 +637,7 @@ def reset_password_via_recovery( raise HTTPException(status_code=403, detail="본인확인 검증이 완료되지 않았습니다") reset_expires_at = session_state.get("reset_expires_at") - if not isinstance(reset_expires_at, datetime) or reset_expires_at <= datetime.utcnow(): + if not isinstance(reset_expires_at, datetime) or reset_expires_at <= datetime.now(timezone.utc): _password_recovery_store.pop(session_token, None) raise HTTPException(status_code=410, detail="재설정 토큰이 만료되었습니다") diff --git a/backend/main.py b/backend/main.py index f520ea4..516b298 100644 --- a/backend/main.py +++ b/backend/main.py @@ -723,6 +723,24 @@ def _relative_percent(numerator: float, denominator: float) -> Optional[float]: return round((numerator / denominator) * 100, 1) +_SAFE_DIAGNOSTIC_ERROR_CODES = { + "cpu_load_unavailable", + "gpu_runtime_unavailable", + "memory_snapshot_unavailable", + "queue_runtime_unavailable", +} + + +def _sanitize_diagnostic_error(raw_error: Any, fallback: str) -> Optional[str]: + if raw_error is None: + return None + if isinstance(raw_error, str): + normalized = raw_error.strip().lower() + if normalized in _SAFE_DIAGNOSTIC_ERROR_CODES: + return normalized + return fallback + + def _linux_memory_snapshot() -> Optional[Dict[str, Any]]: meminfo_path = "/proc/meminfo" if not os.path.exists(meminfo_path): @@ -739,7 +757,12 @@ def _linux_memory_snapshot() -> Optional[Dict[str, Any]]: if number.isdigit(): values[key.strip()] = int(number) except Exception as exc: - return {"error": str(exc)} + return { + "error": _sanitize_diagnostic_error( + exc, + "memory_snapshot_unavailable", + ) + } total_kb = values.get("MemTotal", 0) available_kb = values.get("MemAvailable", values.get("MemFree", 0)) @@ -761,7 +784,12 @@ def _windows_memory_snapshot() -> Optional[Dict[str, Any]]: if not kernel32.GlobalMemoryStatusEx(ctypes.byref(status)): return None except Exception as exc: - return {"error": str(exc)} + return { + "error": _sanitize_diagnostic_error( + exc, + "memory_snapshot_unavailable", + ) + } total_bytes = int(status.ullTotalPhys) available_bytes = int(status.ullAvailPhys) @@ -778,12 +806,18 @@ def _memory_snapshot() -> Dict[str, Any]: snapshot = _linux_memory_snapshot() if snapshot is None and os.name == "nt": snapshot = _windows_memory_snapshot() - if not snapshot: - return { + if not snapshot or snapshot.get("error"): + payload: Dict[str, Any] = { "available": False, "state": "warning", "note": "메모리 사용량을 수집하지 못했습니다.", } + if snapshot: + payload["error"] = _sanitize_diagnostic_error( + snapshot.get("error"), + "memory_snapshot_unavailable", + ) + return payload usage_percent = snapshot.get("usage_percent") critical_percent = max( @@ -868,7 +902,7 @@ def _cpu_snapshot() -> Dict[str, Any]: usage_percent: Optional[float] = None note = "CPU 부하가 정상 범위입니다." state = "ok" - error_message = "" + error_code: Optional[str] = None warning_percent = min( SAFE_COMPUTE_USAGE_LIMIT_PERCENT, int(os.getenv("RUNTIME_CPU_WARNING_PERCENT", str(SAFE_MEMORY_OCCUPANCY_LIMIT_PERCENT)) or SAFE_MEMORY_OCCUPANCY_LIMIT_PERCENT), @@ -883,7 +917,7 @@ def _cpu_snapshot() -> Dict[str, Any]: getloadavg = cast(Any, getattr(os, "getloadavg")) load_1m = round(float(getloadavg()[0]), 2) except Exception as exc: - error_message = str(exc) + error_code = _sanitize_diagnostic_error(exc, "cpu_load_unavailable") if load_1m is not None and cpu_count > 0: load_ratio_percent = _relative_percent(load_1m, cpu_count) @@ -911,29 +945,28 @@ def _cpu_snapshot() -> Dict[str, Any]: "load_ratio_percent": load_ratio_percent, "usage_percent": usage_percent, } - if error_message: - payload["error"] = error_message + if error_code: + payload["error"] = error_code return payload def _gpu_snapshot() -> Dict[str, Any]: gpu_runtime = get_gpu_runtime_info() + gpu_runtime_data = gpu_runtime if isinstance(gpu_runtime, dict) else {} + gpu_error = _sanitize_diagnostic_error( + gpu_runtime_data.get("error"), + "gpu_runtime_unavailable", + ) devices = ( - gpu_runtime.get("devices", []) - if isinstance(gpu_runtime, dict) - else [] + gpu_runtime_data.get("devices", []) ) - if not gpu_runtime.get("available"): + if not gpu_runtime_data.get("available"): return { "available": False, "state": "warning", "note": "GPU 런타임이 감지되지 않았습니다. CPU fallback 또는 드라이버 상태를 확인하세요.", "devices": [], - "error": ( - gpu_runtime.get("error") - if isinstance(gpu_runtime, dict) - else None - ), + "error": gpu_error or "gpu_runtime_unavailable", } peak_usage = 0.0 diff --git a/backend/marketplace/router.py b/backend/marketplace/router.py index 6d8e5b4..55cda54 100644 --- a/backend/marketplace/router.py +++ b/backend/marketplace/router.py @@ -1059,9 +1059,10 @@ def get_ad_queue_runtime_status() -> Dict[str, Dict[str, Any]]: if redis_client is not None: try: queue_depth = int(redis_client.llen(VIDEO_RENDER_QUEUE_NAME)) - except RedisError as exc: + except RedisError: + logger.exception("Failed to read Redis queue depth for health diagnostics") redis_available = False - redis_error = str(exc) + redis_error = "redis_queue_unavailable" with _ad_worker_lock: started_at = _ad_worker_runtime.get("started_at") diff --git a/docs/overall-design-change-pr-report-2026-05-08.md b/docs/overall-design-change-pr-report-2026-05-08.md new file mode 100644 index 0000000..232e4c3 --- /dev/null +++ b/docs/overall-design-change-pr-report-2026-05-08.md @@ -0,0 +1,182 @@ +# 전체 설계변경 요약 및 PR 본문 초안 + +> Note: This document is bilingual by design. The repository-wide design summary is written in Korean for the primary working context, and the PR body draft is written in English so it can be pasted directly into GitHub pull request fields. + +## 문서 목적 +- 현재 저장소 문서 기준으로 전체 설계변경 내용을 한 번에 검토할 수 있도록 정리한다. +- 바로 복사해 사용할 수 있는 실제 PR 본문 초안을 남긴다. +- 설계변경 요약과 PR 설명의 근거 문서를 함께 묶어 추적 가능하게 유지한다. + +## 근거 문서 +- `README.md` +- `docs/orchestrator-multigenerator-upgrade-status.md` +- `docs/final_readiness_checklist.md` +- `docs/admin-dashboard-ui-ux-browser-blueprint.md` +- `docs/admin-dashboard-section-linkage-checklist.md` +- `gpu-llm-server/reports/pr-body-2026-04-27.md` + +--- + +## 1. 전체 설계변경 요약 + +### 1-1. 플랫폼 운영 구조 재정렬 +- 공개 메인 앱과 관리자 앱의 운영 기준을 분리했다. +- 공개 메인 앱은 `frontend`, 관리자 앱은 `frontend/frontend` 기준으로 정렬했다. +- 운영 진입 경로를 `marketplace`, `admin`, `/api/llm/ws` 중심으로 고정했다. +- 운영 판정은 단순 구현 여부가 아니라 운영 경로 실검증 기준으로 묶었다. + +### 1-2. 오케스트레이터 및 멀티 생성기 계약 단일화 +- Python 산출물 서비스 구조를 `app/services/__init__.py`, `app/services/runtime_service.py` 패키지 기준으로 통일했다. +- 레거시 `app/services.py` 단일 파일 기준과 신규 패키지 기준이 동시에 유지되지 않도록 계약을 정렬했다. +- 템플릿, 검증기, 체크리스트, capability 진단 규칙이 같은 서비스 패키지 기준을 보도록 재정리했다. + +### 1-3. 생성 직후 hard gate 검증 체계 강화 +- 생성 직후 결과물 폴더에서 의존성 설치, 단독 기동, 핵심 API 호출, 테스트, ZIP 재현 검증까지 한 흐름으로 묶었다. +- readiness checklist, semantic gate, completion gate, packaging audit, output audit의 연결 구조를 강화했다. +- 산출물 문서와 운영 증거가 분리되지 않도록 `final_readiness_checklist.md` 중심의 판정 체계를 유지했다. + +### 1-4. capability evidence 및 self-run 추적 강화 +- 관리자 capability 진단에 summary/detail 분리와 evidence bundle 해석을 반영했다. +- `completion_gate_ok`, `self_run_status`, `failure_tags`, `target_file_ids`, `evidence_digest` 등 추적 요약을 노출하도록 정리했다. +- self-run terminal state, `applied_to_source`, runtime artifact, operational evidence를 같은 흐름에서 확인할 수 있게 했다. + +### 1-5. 운영 경로 실검증 기준 고정 +- 운영 경로 검증 대상을 `admin`, `marketplace`, websocket, system settings, workspace self-run record까지 포함해 정리했다. +- 로컬 성능 및 검증 기준은 `localhost` 대신 `127.0.0.1:8000` 또는 운영 도메인 기준으로 고정했다. +- 완료 판정은 운영 경로 실검증과 readiness evidence가 함께 닫혀야만 가능하도록 유지했다. + +### 1-6. 관리자 대시보드 UI/UX 구조 재설계 +- 관리자 대시보드를 중앙 오케스트레이터 허브 중심 구조로 재배치했다. +- 상단 바, 히어로 액션, 중앙 런처 허브, 양측 레일, 오버레이 창형 섹션 구조를 연결했다. +- 인라인 접기 카드 위주 화면에서 운영자가 실제 제어에 집중할 수 있는 실행형 패널 구조로 전환했다. + +### 1-7. 신규 생성 프로그램의 운영형 기본 규칙 확대 +- 새로 생성되는 프로그램에도 운영형 설정, 보안 파일, 상태 클라이언트, 최소 코드량, self-configurable 검증 규칙을 공통 적용하는 방향으로 확장했다. +- 단순 스캐폴드가 아니라 운영 준비도와 검증 문서까지 포함하는 생성기 구조를 목표 상태로 정리했다. + +--- + +## 2. 실제 PR 제목 제안 + +### 추천 제목 +`오케스트레이터·멀티 생성기·운영 검증 체계 전면 정렬 및 관리자 UI 구조 재설계` + +### 대안 제목 +- `생성기 계약 단일화와 hard gate 검증 체계 정렬, 관리자 허브 UI 재설계` +- `운영형 오케스트레이터 증거 체계 정렬 및 admin/marketplace 검증 구조 고도화` + +--- + +## 3. 실제 PR 본문 초안 + +## Summary + +This PR consolidates the repository-wide design changes into a single operational baseline. It aligns the public/admin runtime structure, unifies the generator contract around the `app/services/` package layout, strengthens post-generation hard-gate validation, and reorganizes the admin dashboard into an operator-centric orchestration hub. It also ties readiness evidence, self-run traces, and operational verification into a single reviewable flow. + +## Why + +- The generator contract, validation rules, and documentation needed to follow the same service-package standard. +- Completion status needed to be grounded in real operational evidence instead of partial implementation signals. +- Post-generation validation needed to verify dependency install, standalone boot, core API health, tests, and ZIP reproduction as one closed gate. +- The admin dashboard needed to shift from scattered inline sections to a workflow-centered control hub that exposes actionable evidence. + +## Scope Of Changes + +### 1. Runtime / Platform Structure +- Reaffirmed split operation between the public main app and the admin app. +- Kept operational routing focused on `marketplace`, `admin`, and `/api/llm/ws`. +- Synchronized runtime interpretation with the documented production entry points. + +### 2. Generator Contract Unification +- Standardized Python service outputs on: + - `app/services/__init__.py` + - `app/services/runtime_service.py` +- Removed contract ambiguity between legacy single-file service references and package-based service structure. +- Kept templates, validators, checklists, and capability diagnostics aligned to the same package contract. + +### 3. Hard-Gate Validation Baseline +- Strengthened the closed validation path executed immediately after generation: + - dependency installation + - standalone boot + - core API smoke + - test execution + - ZIP reproduction verification +- Preserved semantic gate, completion gate, packaging audit, and readiness checklist linkage. +- Kept `final_readiness_checklist.md` as the central review artifact for closure. + +### 4. Capability Evidence / Self-Run Traceability +- Expanded capability summary/detail separation and evidence bundle interpretation. +- Surfaced evidence-oriented fields such as: + - `completion_gate_ok` + - `self_run_status` + - `failure_tags` + - `target_file_ids` + - `evidence_digest` +- Connected self-run terminal status and `applied_to_source` evidence to the admin-facing review flow. + +### 5. Operational Verification Standards +- Kept operational verification centered on real production paths, including admin, marketplace, websocket, system-settings, and workspace self-run record flows. +- Kept local verification baselines on `127.0.0.1:8000` or the production domain rather than `localhost`. +- Maintained the rule that completion status requires operational evidence, not just code presence. + +### 6. Admin Dashboard UX Redesign +- Reorganized the admin screen around a central orchestration hub. +- Connected top actions, hero actions, launcher tiles, inline surfaces, and modal/overlay sections into a more operator-focused UI. +- Shifted away from dense inline foldable cards toward a clearer action-and-control workflow. + +### 7. Operational-Grade Output Defaults +- Extended generator expectations so newly produced applications follow operational-grade defaults rather than bare scaffolds. +- Preserved expectations for security/runtime/status components and stronger output quality gates. + +## Validation Basis + +- Operational readiness and completion status are documented in `docs/final_readiness_checklist.md`. +- Orchestrator and multi-generator alignment details are documented in `docs/orchestrator-multigenerator-upgrade-status.md`. +- Admin dashboard UX restructuring basis is documented in: + - `docs/admin-dashboard-ui-ux-browser-blueprint.md` + - `docs/admin-dashboard-section-linkage-checklist.md` +- Documentation-only baseline validation for this update: + - `npm --prefix frontend/frontend run test` + +## Risks + +- Tightening contract alignment can expose stale references in secondary documents or auxiliary diagnostic paths. +- Evidence-first completion criteria can downgrade previously tolerated partial states into explicit blockers. +- Admin dashboard workflow changes may alter operator navigation expectations until the new hub pattern is fully internalized. + +## Rollback Strategy + +- Roll back documentation and PR narrative independently if wording or scope grouping needs refinement. +- If a runtime/design interpretation needs to be reverted, restore the corresponding baseline in the status/readiness documents first so the repository does not present mismatched closure criteria. +- Preserve the service-package contract and evidence-based completion rule unless a repository-wide alternative standard is intentionally adopted. + +## Reviewer Focus + +- Verify that generator, validator, checklist, and capability documentation all describe the same `app/services/` package contract. +- Check that hard-gate validation is represented as a closed operational path rather than a partial quality signal. +- Review whether the admin dashboard redesign description accurately matches the current launcher-hub and overlay-window structure. +- Confirm that completion claims stay anchored to documented operational evidence. + +## Notes For Release / Reporting + +- This PR body is intended as a consolidated reporting layer for the current repository baseline. +- It is suitable for follow-up release notes, readiness reviews, or status reports that need one narrative covering generator structure, operational verification, and admin UI direction. + +--- + +## 4. 짧은 PR 본문 버전 + +## Summary +- 공개/관리자 운영 구조를 재정렬하고 생성기 계약을 `app/services/` 패키지 기준으로 단일화했다. +- 생성 직후 hard gate 검증, readiness checklist, self-run evidence, operational verification 흐름을 같은 판정 체계로 묶었다. +- 관리자 화면을 중앙 오케스트레이터 허브 기반 구조로 정리해 운영 제어와 증거 확인 흐름을 강화했다. + +## Validation +- `docs/final_readiness_checklist.md` +- `docs/orchestrator-multigenerator-upgrade-status.md` +- `npm --prefix frontend/frontend run test` + +## Reviewer Focus +- 서비스 패키지 계약 정합성 +- hard gate 및 readiness evidence 표현 정확성 +- 관리자 허브 UI 설명과 실제 구조의 일치 여부 diff --git a/pyproject.toml b/pyproject.toml index 8ceecb9..1bd5e2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "httpx>=0.28,<1.0", "requests>=2.32,<3.0", "redis>=5.2,<6.0", - "Pillow>=10.4,<12.0", + "Pillow>=12.2,<13.0", "torch>=2.6,<3.0", "qdrant-client>=1.12,<2.0", "grpcio>=1.71,<2.0", diff --git a/tests/test_auth_router_security.py b/tests/test_auth_router_security.py index 5b861c5..245042d 100644 --- a/tests/test_auth_router_security.py +++ b/tests/test_auth_router_security.py @@ -1,7 +1,7 @@ import importlib import sys import types -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from types import SimpleNamespace import pytest @@ -50,6 +50,39 @@ class _StubUser: fake_models.User = _StubUser + # Stub webauthn if not installed so auth_router can be imported without it + if "webauthn" not in sys.modules: + _stub_webauthn = types.ModuleType("webauthn") + for _name in ( + "generate_authentication_options", + "generate_registration_options", + "options_to_json", + "verify_authentication_response", + "verify_registration_response", + ): + setattr(_stub_webauthn, _name, None) + + _stub_structs = types.ModuleType("webauthn.helpers.structs") + for _name in ( + "PublicKeyCredentialType", + "AuthenticatorAssertionResponse", + "AuthenticatorAttestationResponse", + "AuthenticatorSelectionCriteria", + "PublicKeyCredentialDescriptor", + "RegistrationCredential", + "AuthenticationCredential", + "UserVerificationRequirement", + "ResidentKeyRequirement", + ): + setattr(_stub_structs, _name, None) + + _stub_helpers = types.ModuleType("webauthn.helpers") + _stub_helpers.structs = _stub_structs + + monkeypatch.setitem(sys.modules, "webauthn", _stub_webauthn) + monkeypatch.setitem(sys.modules, "webauthn.helpers", _stub_helpers) + monkeypatch.setitem(sys.modules, "webauthn.helpers.structs", _stub_structs) + monkeypatch.setitem(sys.modules, "backend.database", fake_database) monkeypatch.setitem(sys.modules, "backend.models", fake_models) return importlib.import_module("backend.auth_router") @@ -96,7 +129,7 @@ def test_password_recovery_verify_identity_limits_failed_attempts(monkeypatch): "verified": False, "verification_code": "654321", "verification_attempts": 0, - "expires_at": datetime.utcnow() + timedelta(minutes=5), + "expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), } payload = auth_router.PasswordRecoveryVerifyIdentityRequest( recovery_session_token=recovery_session_token, @@ -123,8 +156,8 @@ def test_reset_password_requires_verified_identity(monkeypatch): "scope": "admin", "verified": False, "reset_token": "reset_token", - "reset_expires_at": datetime.utcnow() + timedelta(minutes=5), - "expires_at": datetime.utcnow() + timedelta(minutes=5), + "reset_expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), + "expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), } with pytest.raises(HTTPException) as exc_info: @@ -151,8 +184,8 @@ def test_reset_password_updates_hash_and_clears_session(monkeypatch): "verified": True, "identity_session_token": "identity-proof", "reset_token": "reset_token", - "reset_expires_at": datetime.utcnow() + timedelta(minutes=5), - "expires_at": datetime.utcnow() + timedelta(minutes=5), + "reset_expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), + "expires_at": datetime.now(timezone.utc) + timedelta(minutes=5), } response = auth_router.reset_password_via_recovery( diff --git a/tests/test_health_diagnostics_sanitization.py b/tests/test_health_diagnostics_sanitization.py new file mode 100644 index 0000000..2eaadc0 --- /dev/null +++ b/tests/test_health_diagnostics_sanitization.py @@ -0,0 +1,105 @@ +import ast +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, cast + + +MAIN_PATH = Path(__file__).resolve().parent.parent / "backend" / "main.py" +SAFE_DIAGNOSTIC_ERROR_CODES = { + "cpu_load_unavailable", + "gpu_runtime_unavailable", + "memory_snapshot_unavailable", + "queue_runtime_unavailable", +} + + +def _load_functions(*names: str, extra_globals: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + tree = ast.parse(MAIN_PATH.read_text(encoding="utf-8-sig"), filename=str(MAIN_PATH)) + selected = [ + node + for node in tree.body + if isinstance(node, ast.FunctionDef) and node.name in names + ] + namespace: Dict[str, Any] = { + "Any": Any, + "Dict": Dict, + "List": List, + "Optional": Optional, + "Path": Path, + "cast": cast, + "os": os, + } + if extra_globals: + namespace.update(extra_globals) + exec(compile(ast.Module(body=selected, type_ignores=[]), str(MAIN_PATH), "exec"), namespace) + return namespace + + +def test_sanitize_diagnostic_error_redacts_exception_text(): + namespace = _load_functions( + "_sanitize_diagnostic_error", + extra_globals={ + "_SAFE_DIAGNOSTIC_ERROR_CODES": SAFE_DIAGNOSTIC_ERROR_CODES, + }, + ) + + sanitize = namespace["_sanitize_diagnostic_error"] + + assert sanitize(PermissionError("cannot open /proc/meminfo"), "memory_snapshot_unavailable") == "memory_snapshot_unavailable" + assert sanitize("gpu_runtime_unavailable", "memory_snapshot_unavailable") == "gpu_runtime_unavailable" + assert sanitize(" GPU_Runtime_Unavailable ", "memory_snapshot_unavailable") == "gpu_runtime_unavailable" + assert sanitize(None, "memory_snapshot_unavailable") is None + + +def test_memory_snapshot_error_becomes_warning_payload(): + namespace = _load_functions( + "_sanitize_diagnostic_error", + "_memory_snapshot", + extra_globals={ + "_SAFE_DIAGNOSTIC_ERROR_CODES": SAFE_DIAGNOSTIC_ERROR_CODES, + "_linux_memory_snapshot": lambda: {"error": "permission denied: /proc/meminfo"}, + "_windows_memory_snapshot": lambda: None, + "SAFE_COMPUTE_USAGE_LIMIT_PERCENT": 90, + "SAFE_MEMORY_OCCUPANCY_LIMIT_PERCENT": 75, + }, + ) + + payload = namespace["_memory_snapshot"]() + + assert payload["available"] is False + assert payload["state"] == "warning" + assert payload["error"] == "memory_snapshot_unavailable" + assert "/proc/meminfo" not in payload["error"] + + +def test_cpu_and_gpu_snapshots_expose_only_safe_error_codes(monkeypatch): + namespace = _load_functions( + "_sanitize_diagnostic_error", + "_cpu_snapshot", + "_gpu_snapshot", + extra_globals={ + "_SAFE_DIAGNOSTIC_ERROR_CODES": SAFE_DIAGNOSTIC_ERROR_CODES, + "SAFE_COMPUTE_USAGE_LIMIT_PERCENT": 90, + "SAFE_MEMORY_OCCUPANCY_LIMIT_PERCENT": 75, + "_relative_percent": lambda numerator, denominator: round((numerator / denominator) * 100, 1) if denominator > 0 else None, + "_linux_cpu_usage_percent": lambda: None, + "get_gpu_runtime_info": lambda: { + "available": False, + "error": "driver init failed for /dev/nvidia0", + "devices": [], + }, + }, + ) + + def _raise_loadavg_error(): + raise OSError("cannot read /proc/loadavg") + + monkeypatch.setattr(os, "getloadavg", _raise_loadavg_error) + + cpu_payload = namespace["_cpu_snapshot"]() + gpu_payload = namespace["_gpu_snapshot"]() + + assert cpu_payload["error"] == "cpu_load_unavailable" + assert "/proc/loadavg" not in cpu_payload["error"] + assert gpu_payload["error"] == "gpu_runtime_unavailable" + assert "/dev/nvidia0" not in gpu_payload["error"] From db89f79fce7c1f471f4a60f773ad7ca6e875d677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=B2=A0=ED=99=8D?= <111139476+parkcheolhong@users.noreply.github.com> Date: Mon, 11 May 2026 18:43:17 +0900 Subject: [PATCH 5/7] Update .github/workflows/codeql.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변경 감사합니다 Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4357ddc..1a905d0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,7 +36,9 @@ jobs: if: matrix.language == 'python' uses: actions/setup-python@v6 with: - python-version: '3.13' + # Use the project's supported/runtime Python version (or lowest supported) + # so that CodeQL analysis reflects the actual environment. + python-version: '3.11' - name: Initialize CodeQL uses: github/codeql-action/init@v4 From 6959b4fea44d4e1b64221cc8ab94ccbd60014009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=B2=A0=ED=99=8D?= <111139476+parkcheolhong@users.noreply.github.com> Date: Mon, 11 May 2026 18:48:49 +0900 Subject: [PATCH 6/7] Potential fix for pull request finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 커밋합니다. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- backend/llm/orchestrator.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/llm/orchestrator.py b/backend/llm/orchestrator.py index 55d5a0f..0531565 100644 --- a/backend/llm/orchestrator.py +++ b/backend/llm/orchestrator.py @@ -2728,7 +2728,33 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s ) persisted_payload = {} persisted_payload[normalized["run_id"]] = normalized - progress_path.write_text(json.dumps(persisted_payload, ensure_ascii=False, indent=2), encoding="utf-8") + serialized_payload = json.dumps(persisted_payload, ensure_ascii=False, indent=2) + temp_path: Optional[Path] = None + try: + with tempfile.NamedTemporaryFile( + mode="w", + encoding="utf-8", + dir=str(progress_path.parent), + prefix=f"{progress_path.name}.", + suffix=".tmp", + delete=False, + ) as temp_file: + temp_file.write(serialized_payload) + temp_file.flush() + os.fsync(temp_file.fileno()) + temp_path = Path(temp_file.name) + os.replace(temp_path, progress_path) + except Exception: + if temp_path is not None: + try: + temp_path.unlink(missing_ok=True) + except Exception: + logger.warning( + "Failed to remove temporary orchestration progress file %s", + str(temp_path), + exc_info=True, + ) + raise return normalized From 8a513c74980150931e157aa5ca12e11275b806e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 09:49:33 +0000 Subject: [PATCH 7/7] fix: address PR review feedback for auth and progress persistence Agent-Logs-Url: https://github.com/parkcheolhong/codeAI/sessions/54ddac5c-9ffc-4dd9-8328-a90404f7b582 Co-authored-by: parkcheolhong <111139476+parkcheolhong@users.noreply.github.com> --- backend/auth.py | 29 ++- backend/llm/orchestrator.py | 40 ++-- ...rall-design-change-pr-report-2026-05-08.md | 182 ------------------ 3 files changed, 38 insertions(+), 213 deletions(-) delete mode 100644 docs/overall-design-change-pr-report-2026-05-08.md diff --git a/backend/auth.py b/backend/auth.py index deabddf..38d3535 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -22,16 +22,33 @@ def _resolve_secret_key() -> tuple[str, bool]: if configured_file: fallback_path = Path(configured_file).expanduser() try: - if fallback_path.exists() and fallback_path.is_file(): + if not fallback_path.exists(): + logger.error( + "SECRET_KEY_FILE is configured but does not exist: %s. Falling back to ephemeral runtime secret.", + str(fallback_path), + ) + elif not fallback_path.is_file(): + logger.error( + "SECRET_KEY_FILE is configured but is not a file: %s. Falling back to ephemeral runtime secret.", + str(fallback_path), + ) + else: cached_secret = fallback_path.read_text(encoding="utf-8").strip() if cached_secret: return cached_secret, True + logger.error( + "SECRET_KEY_FILE is configured but empty: %s. Falling back to ephemeral runtime secret.", + str(fallback_path), + ) except Exception: - pass - - logger.error( - "SECRET_KEY/SECRET_KEY_FILE is not configured; generating ephemeral runtime secret that invalidates tokens on restart." - ) + logger.exception( + "Failed to read SECRET_KEY_FILE at %s. Falling back to ephemeral runtime secret.", + str(fallback_path), + ) + else: + logger.error( + "SECRET_KEY/SECRET_KEY_FILE is not configured; generating ephemeral runtime secret that invalidates tokens on restart." + ) return secrets.token_urlsafe(48), True diff --git a/backend/llm/orchestrator.py b/backend/llm/orchestrator.py index 0531565..d49739b 100644 --- a/backend/llm/orchestrator.py +++ b/backend/llm/orchestrator.py @@ -2695,8 +2695,10 @@ def _runtime_progress_root() -> Path: return progress_root -def _orchestration_progress_store_path() -> Path: - return _runtime_progress_root() / "progress_store.json" +def _orchestration_progress_file_path(run_id: str) -> Path: + safe_run_id = str(run_id or "unknown") + file_name = f"{hashlib.sha256(safe_run_id.encode('utf-8')).hexdigest()}.json" + return _runtime_progress_root() / file_name def _build_progress_poll_url(run_id: str) -> str: @@ -2712,23 +2714,8 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s normalized["run_id"] = str(run_id or normalized.get("run_id") or "") normalized.setdefault("updated_at", datetime.utcnow().isoformat() + "Z") _ORCHESTRATION_PROGRESS_STORE[normalized["run_id"]] = normalized - progress_path = _orchestration_progress_store_path() + progress_path = _orchestration_progress_file_path(normalized["run_id"]) with _ORCHESTRATION_PROGRESS_FILE_LOCK: - persisted_payload: Dict[str, Any] = {} - try: - if progress_path.exists() and progress_path.is_file(): - existing_payload = json.loads(progress_path.read_text(encoding="utf-8")) - if isinstance(existing_payload, dict): - persisted_payload = dict(existing_payload) - except Exception: - logger.warning( - "Failed to read orchestration progress store from %s before write", - str(progress_path), - exc_info=True, - ) - persisted_payload = {} - persisted_payload[normalized["run_id"]] = normalized - serialized_payload = json.dumps(persisted_payload, ensure_ascii=False, indent=2) temp_path: Optional[Path] = None try: with tempfile.NamedTemporaryFile( @@ -2739,7 +2726,7 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s suffix=".tmp", delete=False, ) as temp_file: - temp_file.write(serialized_payload) + temp_file.write(json.dumps(normalized, ensure_ascii=False, indent=2)) temp_file.flush() os.fsync(temp_file.fileno()) temp_path = Path(temp_file.name) @@ -2754,7 +2741,12 @@ def _save_orchestration_progress(run_id: str, payload: Dict[str, Any]) -> Dict[s str(temp_path), exc_info=True, ) - raise + logger.warning( + "Failed to write orchestration progress file at %s", + str(progress_path), + exc_info=True, + ) + return dict(_ORCHESTRATION_PROGRESS_STORE.get(normalized["run_id"], {})) return normalized @@ -2762,16 +2754,14 @@ def _load_orchestration_progress(run_id: str) -> Dict[str, Any]: cached = _ORCHESTRATION_PROGRESS_STORE.get(str(run_id or "")) if isinstance(cached, dict) and cached: return dict(cached) - progress_path = _orchestration_progress_store_path() + progress_path = _orchestration_progress_file_path(run_id) try: with _ORCHESTRATION_PROGRESS_FILE_LOCK: if progress_path.exists() and progress_path.is_file(): payload = json.loads(progress_path.read_text(encoding="utf-8")) if isinstance(payload, dict): - stored = payload.get(str(run_id or "")) - if isinstance(stored, dict): - _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(stored) - return dict(stored) + _ORCHESTRATION_PROGRESS_STORE[str(run_id or "")] = dict(payload) + return dict(payload) except Exception: logger.error("Failed to load orchestration progress for run_id=%s", str(run_id or ""), exc_info=True) return {} diff --git a/docs/overall-design-change-pr-report-2026-05-08.md b/docs/overall-design-change-pr-report-2026-05-08.md deleted file mode 100644 index 232e4c3..0000000 --- a/docs/overall-design-change-pr-report-2026-05-08.md +++ /dev/null @@ -1,182 +0,0 @@ -# 전체 설계변경 요약 및 PR 본문 초안 - -> Note: This document is bilingual by design. The repository-wide design summary is written in Korean for the primary working context, and the PR body draft is written in English so it can be pasted directly into GitHub pull request fields. - -## 문서 목적 -- 현재 저장소 문서 기준으로 전체 설계변경 내용을 한 번에 검토할 수 있도록 정리한다. -- 바로 복사해 사용할 수 있는 실제 PR 본문 초안을 남긴다. -- 설계변경 요약과 PR 설명의 근거 문서를 함께 묶어 추적 가능하게 유지한다. - -## 근거 문서 -- `README.md` -- `docs/orchestrator-multigenerator-upgrade-status.md` -- `docs/final_readiness_checklist.md` -- `docs/admin-dashboard-ui-ux-browser-blueprint.md` -- `docs/admin-dashboard-section-linkage-checklist.md` -- `gpu-llm-server/reports/pr-body-2026-04-27.md` - ---- - -## 1. 전체 설계변경 요약 - -### 1-1. 플랫폼 운영 구조 재정렬 -- 공개 메인 앱과 관리자 앱의 운영 기준을 분리했다. -- 공개 메인 앱은 `frontend`, 관리자 앱은 `frontend/frontend` 기준으로 정렬했다. -- 운영 진입 경로를 `marketplace`, `admin`, `/api/llm/ws` 중심으로 고정했다. -- 운영 판정은 단순 구현 여부가 아니라 운영 경로 실검증 기준으로 묶었다. - -### 1-2. 오케스트레이터 및 멀티 생성기 계약 단일화 -- Python 산출물 서비스 구조를 `app/services/__init__.py`, `app/services/runtime_service.py` 패키지 기준으로 통일했다. -- 레거시 `app/services.py` 단일 파일 기준과 신규 패키지 기준이 동시에 유지되지 않도록 계약을 정렬했다. -- 템플릿, 검증기, 체크리스트, capability 진단 규칙이 같은 서비스 패키지 기준을 보도록 재정리했다. - -### 1-3. 생성 직후 hard gate 검증 체계 강화 -- 생성 직후 결과물 폴더에서 의존성 설치, 단독 기동, 핵심 API 호출, 테스트, ZIP 재현 검증까지 한 흐름으로 묶었다. -- readiness checklist, semantic gate, completion gate, packaging audit, output audit의 연결 구조를 강화했다. -- 산출물 문서와 운영 증거가 분리되지 않도록 `final_readiness_checklist.md` 중심의 판정 체계를 유지했다. - -### 1-4. capability evidence 및 self-run 추적 강화 -- 관리자 capability 진단에 summary/detail 분리와 evidence bundle 해석을 반영했다. -- `completion_gate_ok`, `self_run_status`, `failure_tags`, `target_file_ids`, `evidence_digest` 등 추적 요약을 노출하도록 정리했다. -- self-run terminal state, `applied_to_source`, runtime artifact, operational evidence를 같은 흐름에서 확인할 수 있게 했다. - -### 1-5. 운영 경로 실검증 기준 고정 -- 운영 경로 검증 대상을 `admin`, `marketplace`, websocket, system settings, workspace self-run record까지 포함해 정리했다. -- 로컬 성능 및 검증 기준은 `localhost` 대신 `127.0.0.1:8000` 또는 운영 도메인 기준으로 고정했다. -- 완료 판정은 운영 경로 실검증과 readiness evidence가 함께 닫혀야만 가능하도록 유지했다. - -### 1-6. 관리자 대시보드 UI/UX 구조 재설계 -- 관리자 대시보드를 중앙 오케스트레이터 허브 중심 구조로 재배치했다. -- 상단 바, 히어로 액션, 중앙 런처 허브, 양측 레일, 오버레이 창형 섹션 구조를 연결했다. -- 인라인 접기 카드 위주 화면에서 운영자가 실제 제어에 집중할 수 있는 실행형 패널 구조로 전환했다. - -### 1-7. 신규 생성 프로그램의 운영형 기본 규칙 확대 -- 새로 생성되는 프로그램에도 운영형 설정, 보안 파일, 상태 클라이언트, 최소 코드량, self-configurable 검증 규칙을 공통 적용하는 방향으로 확장했다. -- 단순 스캐폴드가 아니라 운영 준비도와 검증 문서까지 포함하는 생성기 구조를 목표 상태로 정리했다. - ---- - -## 2. 실제 PR 제목 제안 - -### 추천 제목 -`오케스트레이터·멀티 생성기·운영 검증 체계 전면 정렬 및 관리자 UI 구조 재설계` - -### 대안 제목 -- `생성기 계약 단일화와 hard gate 검증 체계 정렬, 관리자 허브 UI 재설계` -- `운영형 오케스트레이터 증거 체계 정렬 및 admin/marketplace 검증 구조 고도화` - ---- - -## 3. 실제 PR 본문 초안 - -## Summary - -This PR consolidates the repository-wide design changes into a single operational baseline. It aligns the public/admin runtime structure, unifies the generator contract around the `app/services/` package layout, strengthens post-generation hard-gate validation, and reorganizes the admin dashboard into an operator-centric orchestration hub. It also ties readiness evidence, self-run traces, and operational verification into a single reviewable flow. - -## Why - -- The generator contract, validation rules, and documentation needed to follow the same service-package standard. -- Completion status needed to be grounded in real operational evidence instead of partial implementation signals. -- Post-generation validation needed to verify dependency install, standalone boot, core API health, tests, and ZIP reproduction as one closed gate. -- The admin dashboard needed to shift from scattered inline sections to a workflow-centered control hub that exposes actionable evidence. - -## Scope Of Changes - -### 1. Runtime / Platform Structure -- Reaffirmed split operation between the public main app and the admin app. -- Kept operational routing focused on `marketplace`, `admin`, and `/api/llm/ws`. -- Synchronized runtime interpretation with the documented production entry points. - -### 2. Generator Contract Unification -- Standardized Python service outputs on: - - `app/services/__init__.py` - - `app/services/runtime_service.py` -- Removed contract ambiguity between legacy single-file service references and package-based service structure. -- Kept templates, validators, checklists, and capability diagnostics aligned to the same package contract. - -### 3. Hard-Gate Validation Baseline -- Strengthened the closed validation path executed immediately after generation: - - dependency installation - - standalone boot - - core API smoke - - test execution - - ZIP reproduction verification -- Preserved semantic gate, completion gate, packaging audit, and readiness checklist linkage. -- Kept `final_readiness_checklist.md` as the central review artifact for closure. - -### 4. Capability Evidence / Self-Run Traceability -- Expanded capability summary/detail separation and evidence bundle interpretation. -- Surfaced evidence-oriented fields such as: - - `completion_gate_ok` - - `self_run_status` - - `failure_tags` - - `target_file_ids` - - `evidence_digest` -- Connected self-run terminal status and `applied_to_source` evidence to the admin-facing review flow. - -### 5. Operational Verification Standards -- Kept operational verification centered on real production paths, including admin, marketplace, websocket, system-settings, and workspace self-run record flows. -- Kept local verification baselines on `127.0.0.1:8000` or the production domain rather than `localhost`. -- Maintained the rule that completion status requires operational evidence, not just code presence. - -### 6. Admin Dashboard UX Redesign -- Reorganized the admin screen around a central orchestration hub. -- Connected top actions, hero actions, launcher tiles, inline surfaces, and modal/overlay sections into a more operator-focused UI. -- Shifted away from dense inline foldable cards toward a clearer action-and-control workflow. - -### 7. Operational-Grade Output Defaults -- Extended generator expectations so newly produced applications follow operational-grade defaults rather than bare scaffolds. -- Preserved expectations for security/runtime/status components and stronger output quality gates. - -## Validation Basis - -- Operational readiness and completion status are documented in `docs/final_readiness_checklist.md`. -- Orchestrator and multi-generator alignment details are documented in `docs/orchestrator-multigenerator-upgrade-status.md`. -- Admin dashboard UX restructuring basis is documented in: - - `docs/admin-dashboard-ui-ux-browser-blueprint.md` - - `docs/admin-dashboard-section-linkage-checklist.md` -- Documentation-only baseline validation for this update: - - `npm --prefix frontend/frontend run test` - -## Risks - -- Tightening contract alignment can expose stale references in secondary documents or auxiliary diagnostic paths. -- Evidence-first completion criteria can downgrade previously tolerated partial states into explicit blockers. -- Admin dashboard workflow changes may alter operator navigation expectations until the new hub pattern is fully internalized. - -## Rollback Strategy - -- Roll back documentation and PR narrative independently if wording or scope grouping needs refinement. -- If a runtime/design interpretation needs to be reverted, restore the corresponding baseline in the status/readiness documents first so the repository does not present mismatched closure criteria. -- Preserve the service-package contract and evidence-based completion rule unless a repository-wide alternative standard is intentionally adopted. - -## Reviewer Focus - -- Verify that generator, validator, checklist, and capability documentation all describe the same `app/services/` package contract. -- Check that hard-gate validation is represented as a closed operational path rather than a partial quality signal. -- Review whether the admin dashboard redesign description accurately matches the current launcher-hub and overlay-window structure. -- Confirm that completion claims stay anchored to documented operational evidence. - -## Notes For Release / Reporting - -- This PR body is intended as a consolidated reporting layer for the current repository baseline. -- It is suitable for follow-up release notes, readiness reviews, or status reports that need one narrative covering generator structure, operational verification, and admin UI direction. - ---- - -## 4. 짧은 PR 본문 버전 - -## Summary -- 공개/관리자 운영 구조를 재정렬하고 생성기 계약을 `app/services/` 패키지 기준으로 단일화했다. -- 생성 직후 hard gate 검증, readiness checklist, self-run evidence, operational verification 흐름을 같은 판정 체계로 묶었다. -- 관리자 화면을 중앙 오케스트레이터 허브 기반 구조로 정리해 운영 제어와 증거 확인 흐름을 강화했다. - -## Validation -- `docs/final_readiness_checklist.md` -- `docs/orchestrator-multigenerator-upgrade-status.md` -- `npm --prefix frontend/frontend run test` - -## Reviewer Focus -- 서비스 패키지 계약 정합성 -- hard gate 및 readiness evidence 표현 정확성 -- 관리자 허브 UI 설명과 실제 구조의 일치 여부