Critic 리뷰 — 웹앱 실태 감사 전수 검증
최종 판정: REVISE
원본 보고서의 핵심 4대 고장 주장(/api/ops 500, /graph 404, KG embed 내부 graph.json 404, /api/chart/SPY 404)은 직접 curl과 코드 read로 재현됐다. 다만 “전수 감사”로 보기에는 누락된 등록 라우트가 있고, 일부 데이터 missing 주장은 현재 상태가 바뀌었으며, 회귀 테스트/플레이북 증거가 없다. 따라서 승인 불가, 보완 후 재검토가 맞다.
검토 대상: ~/knowledge-agent/100-inbox/260414_webapp_audit.md
체크리스트: /tmp/critic_checklist.md
검토 범위: 읽기 전용. 원본 보고서·웹앱 코드·shared/llm.py 수정 안 함.
1. 수행한 검증
- 원본 보고서 전체 read.
scripts/pipeline/webapp/blueprints/route annotation grep.- Flask
app.url_map덤프:/tmp/critic_routes.tsv생성, 94개 route entry 확인. - Flask test client 샘플 sweep:
/tmp/critic_test_client_routes.tsv생성, 86개 GET 샘플 확인. - 보고서가 주장한 깨진 엔드포인트를
curl http://localhost:8080/...로 직접 재현. - 원인 코드 read:
webapp/blueprints/market.py:195-205,2395-2402,2450-2453~/.openclaw/workspace/graphify-out/graph.html:19- 동일 패턴 grep:
_L()사용처 전체 확인./api/chart/,SPY,/graph,knowledge-graph/embed,market/api/summarize,watchlist/matches호출자 grep.- 데이터 missing 이중 확인:
~/.openclaw/workspace/memory현재 상태 재조회. knowledge-agent/500-signals/에 웹앱 감사 플레이북 존재 여부 확인.
2. 보고서의 “깨진 엔드포인트” 직접 재검증
| 주장 | 직접 재현 결과 | 판정 | 근거 |
|---|---|---|---|
/api/ops 500 |
500, 265 bytes |
✅ 재현 | curl --max-time 15 ... /api/ops; 서버 로그도 ValueError: too many values to unpack (expected 8) |
/graph 404 |
404, 207 bytes |
✅ 재현 | 등록 route 없음 |
KG embed 내부 ./graph.json 404 |
/vault/knowledge-graph/embed는 200, 내부 fetch 대상 /vault/knowledge-graph/graph.json은 404 |
✅ 재현 | graph.html에 fetch('./graph.json'); 현재 URL 기준으로 잘못 해석됨 |
/api/chart/SPY 404 |
404, 20 bytes, {"error":"no data"} |
✅ 재현 | market.py:2400가 memory/price-history/SPY.json을 찾음. 파일 없음 |
/api/briefing/default |
404, 0 bytes |
✅ 재현 | mode 제한. 단 의도된 contract일 가능성 있음 |
/api/briefing/latest |
404, 0 bytes |
✅ 재현 | 위와 동일 |
/api/fed-liquidity/chart 빈 데이터 |
200, 69 bytes, 모든 배열 빈 값 |
✅ 재현 | {"dates":[],...} |
/api/analysis 빈 JSON |
200, 3 bytes, {} |
✅ 재현 | daily-report/analysis-*.json 없음 |
/favicon.ico |
404, 207 bytes |
✅ 재현 | 정적 파일 없음 |
/robots.txt |
404, 207 bytes |
✅ 재현 | 정적 파일 없음 |
POST /api/alert-check |
404, 207 bytes |
✅ 재현 | route 없음 |
3. 원인 코드 확인
/api/ops 500
확인한 코드:
_L()는 10개 loader를 반환한다.api_ops()는 8개 변수로 unpack한다.
근거:
market.py:195-205return (load_analysis, load_market_indicators, load_hypotheses, load_filtered_ideas, load_agent_worker_status, load_system_digest_summary, load_analyst_agents, load_cowork_tasks, load_conflict_signals, load_etf_insight)market.py:2452_, _, _, _, lw, ld, _, lt = _L()
동일 패턴 grep 결과 _L() 직접 사용처는 다음뿐이며, 같은 unpack 폭 불일치는 api_ops() 1곳만 확인됐다.
market.py:211—_, lm, *_ = _L()라 가변 unpack, 안전.market.py:2213—fns = _L()라 안전.market.py:2441,2447— index 접근.market.py:2452— 실패 지점.
KG embed graph.json 404
~/.openclaw/workspace/graphify-out/graph.html는 다음 상대 경로를 사용한다.
<a href="./graph.json">graph.json</a>fetch('./graph.json')
이 파일이 /vault/knowledge-graph/embed에서 그대로 serve되면 브라우저는 /vault/knowledge-graph/graph.json을 요청한다. 해당 route가 없어 404다. 실제 존재하는 큰 원본은 /graphify/graph.json이며 로컬 파일 크기는 약 24MB(25201613 bytes)다.
/api/chart/SPY 404
market.py:2395-2402에서 memory/price-history/{TICKER}.json을 요구한다. 현재 memory/price-history/SPY.json은 없다. 템플릿 호출자도 확인됐다.
templates/analyst/log.html:633—/api/chart/fetchtemplates/market/index.html:1315—/api/chart/fetch
4. 보고서가 놓친 route / 감사 범위 누락
원본 보고서가 주요 화면은 잘 봤지만, “전수” 기준으로는 아래 등록 route가 누락되거나 너무 뭉뚱그려졌다.
| 누락/부족 route | 직접 결과 | 왜 문제인가 |
|---|---|---|
/market/api/summarize |
200, 50 bytes, ID 없음 |
등록된 API인데 보고서 테스트 목록에 없음. 빈 입력 contract/호출자 확인 필요 |
/api/watchlist/matches |
200, 3 bytes, {} |
watchlist API 계열 중 누락. 빈 데이터 상태 확인 필요 |
/api/vault/note |
valid path로 200, 129 bytes |
원본은 /vault/note 빈 query 400만 언급. API variant는 별도 contract |
/news/partial/<sector> |
200, 211 bytes, “아직 채점된 뉴스가 없습니다” |
sector news partial route 누락 |
/watchlist/news/<item_id> |
200, 70 bytes, “종목을 찾을 수 없습니다” |
watchlist partial route 누락 |
/market/bond-study/<date_str> |
404, 16 bytes for 2026-04-14 |
index만 200으로 보고, 날짜 detail/pdf route는 확인 누락 |
/market/bond-study/<date_str>/pdf |
404, 10 bytes for 2026-04-14/pdf |
위와 동일 |
/admin/api/telemetry/* |
비로그인 302; -L 사용 시 login HTML 200 |
원본의 /admin/* 302→200 정상은 큰 틀은 맞지만 API 호출자가 JSON을 기대할 때 -L 테스트가 200 HTML로 오인될 수 있음 |
/graphify/<path:filename> |
/graphify/graph.json은 약 24MB |
기능상 정상이나 공개/성능/응답 크기 리스크가 보고서에서 충분히 분리되지 않음 |
추가로 blueprint 파일 중 sector_data.py, sector_valuechain.py, showcase_curate.py는 route annotation이 없어 원본의 “미등록/보조” 판단은 맞다. 단 sector_compass.py.bak에는 route 흔적이 있으나 .py 등록 대상은 아니므로 운영 route에서는 제외하는 것이 맞다.
5. 데이터 missing 목록 이중 확인
현재 기준 memory/**/*.json은 65개다. 원본 보고서의 기준 시각은 2026-04-14 10:12 KST, 원본 count는 62개였다. 이후 자동화가 파일을 추가/갱신한 것으로 보인다.
원본 missing 목록은 대부분 현재도 맞다.
| 경로 | 현재 확인 |
|---|---|
daily-report/analysis-*.json |
0 |
fed-liquidity/latest.json |
0 |
analyst-synthesis/latest.json |
0 |
analyst-technical/latest.json |
0 |
price-history/SPY.json |
0 |
sector-news/*.json |
0 |
watchlist/watchlist.json |
0 |
bond-briefing/[0-9]*.json |
0 |
차이점:
memory/commodity-alerts/latest.json은 현재 존재한다.stat기준2026-04-14 12:41:09 +0900, 2144 bytes.- 따라서 원본의 “commodity-alerts latest 없음”은 보고서 작성 당시에는 맞았을 수 있으나 현재 상태와는 불일치한다. 이 항목은 스냅샷 시각과 파일 manifest를 함께 남겨야 재현성이 생긴다.
6. 체크리스트 A~E 판정
A. 근본 원인 검증
-
✅ 보고서가 주장하는 원인이 실제 코드와 일치하는가?
근거:/api/ops는market.py:2452unpack 불일치와 정확히 일치. KG embed는graph.html의fetch('./graph.json')와 정확히 일치./api/chart/SPY는memory/price-history/SPY.json누락과 코드 contract가 일치. -
⚠️ 같은 패턴이 다른 파일/함수에 있어서 놓친 건 없는가?
근거:_L()grep에서는 같은 unpack bug가 추가로 발견되지 않았다. 다만 route 전수 대조에서/market/api/summarize,/api/watchlist/matches,/api/vault/note,/news/partial/<sector>,/watchlist/news/<item_id>, bond detail routes 등 감사 누락이 발견됐다. “같은 패턴 버그”는 추가 없음, “같은 감사 범위 누락”은 있음. -
⚠️ 과거 유사 버그가 있었는지 git log / memory / error-ledger 검색
근거:git log에는 웹앱/브리핑/route 관련 변경 이력이 다수 있고,fix: glob("*.json") → glob("202*.json")같은 데이터 선택 버그 이력도 보인다.memory/error-ledger및500-signalsgrep에서는/api/ops, KG embed,price-history/SPY,alert-check직접 항목을 찾지 못했다. 과거 맥락 조사는 수행했지만 유사 버그 추적은 보고서에 충분히 반영되지 않았다.
B. 수정의 완전성
원본 산출물은 코드 수정이 아니라 audit-only 보고서다. 따라서 아래는 “수정안/감사 산출물의 완전성” 기준으로 판정한다.
-
✅ 수정/제안이 증상만 가리는가, 근본을 해결하는가?
근거:/api/ops는 unpack 구조 수정, KG embed는 실제/graphify/graph.json경로 정리,/api/chart/SPY는 price-history 생성/연결을 제안했다. 핵심 제안은 증상 masking보다는 근본 원인 방향이다. -
⚠️ 엣지 케이스: 빈 입력, None, 큰 입력, 동시성, 네트워크 단절
근거: 원본은/vault/note빈 query 400,/api/vault/searchquery 없음 등 일부 빈 입력만 언급했다. 그러나/market/api/summarize의 빈id, admin API의 비인증 JSON 기대, 24MB/graphify/graph.json, 네트워크/파일 missing fallback 같은 엣지 케이스가 체계적으로 정리되지 않았다. -
⚠️ 실패 모드: 새 실패 경로가 있는가?
근거: 코드 수정이 없어서 새 실패 경로는 만들지 않았다. 다만 제안된/graphredirect나 KG route 추가가 24MB JSON 공개/로드 경로를 바꿀 수 있는데, 실패 모드와 fallback 설계가 보고서에 없다. -
✅ 되돌릴 수 있는가? 롤백 전략?
근거: audit-only라 롤백 대상 변경이 없다. 코드 수정 없음 확인.
C. 부작용
-
⚠️ 다른 호출자 영향 확인
근거:/api/chart/호출자는 템플릿 grep으로 확인 가능했으나 원본 보고서에는 호출자 분석이 없다./graphalias 추가, KG embed 경로 수정, briefing mode 확장 여부는 다른 링크/템플릿 영향 분석이 필요하다. -
⚠️ 성능 영향: 메모리, CPU, I/O
근거: 원본은/ops6초,/verdict1.3MB,/news/integrated958KB,/graphify/graph.json24MB 일부를 언급했다. 하지만 전 route 성능 기준선이나 큰 JSON 공개에 따른 I/O 영향은 별도 표로 관리되지 않았다. -
⚠️ 보안: 새로운 공격 면
근거: 원본은 외부 봇 스캔과 admin redirect를 언급했으나,/admin/api/telemetry/*가 비로그인에서 302이고curl -L시 login HTML 200으로 보이는 점,/graphify/graph.json24MB 공개 응답의 노출/DoS 리스크는 충분히 다루지 않았다.
D. 검증
-
✅ 실제로 코드/엔드포인트가 실행됐는가?
근거: Critic이 직접curl로 핵심 고장 전부 재현했고, Flask test client sweep에서도/api/ops500을 확인했다. -
⚠️ before/after 메트릭 있는가?
근거: 원본은 상태/응답시간/크기 일부를 남겼지만 audit-only라 after가 없다. 현재 문제의 기준선으로는 쓸 수 있으나 수정 검증 메트릭은 아니다. -
❌ 회귀 테스트가 이 버그를 실제로 잡는가?
구체 결함: 원본 보고서에는 회귀 테스트 추가/실행 증거가 없다./api/ops500 같은 문제는 최소 route smoke test에 포함돼야 한다. 의도적 revert 실패 확인도 없다.
E. 문서·재현성
-
⚠️ 보고서만 읽고 다음 사람이 재현할 수 있는가?
근거: 원본은 명령 요약과 표가 있어 핵심 재현은 가능하다. 그러나 route 전체 목록, curl raw transcript, 데이터 missing manifest가 없어 “전수” 재현은 어렵다. 특히 memory 파일 수/commodity-alerts 존재 여부는 시간에 따라 달라졌다. -
⚠️ 접근 선택 근거와 대안
근거: 우선순위 제안은 합리적이지만/graphredirect vs 링크 제거, KG embed HTML rewrite vs route alias,/api/chart404 유지 vs 빈 200 fallback 등 대안 비교가 없다. -
❌
knowledge-agent/500-signals/플레이북 저장 여부
구체 결함:find ~/knowledge-agent/500-signals ... webapp|audit|playbook|ops|critic결과 웹앱 감사 플레이북 없음. 해당 디렉터리에는 다른 signal 문서만 확인됐다.
7. 결함 목록 — Codex A에게 넘길 수정 요구사항
- P0 —
/api/ops500 수정 필요 - 재현:
curl http://localhost:8080/api/ops→ 500. - 원인:
webapp/blueprints/market.py:2452,_L()10개 반환을 8개 변수로 unpack. -
요구: index 접근 또는 10개 unpack으로 수정. route smoke test에
/api/ops포함. -
P1 — KG embed 내부 graph JSON 경로 수정 필요
- 재현:
/vault/knowledge-graph/embed200, 내부/vault/knowledge-graph/graph.json404. - 원인:
graph.html의fetch('./graph.json'). -
요구: embed용 HTML을
/graphify/graph.json절대경로로 rewrite하거나/vault/knowledge-graph/graph.jsonalias를 제공. 24MB 응답 성능/캐시 정책도 함께 결정. -
P1 —
/api/chart/SPY데이터 contract 정리 필요 - 재현:
curl http://localhost:8080/api/chart/SPY→ 404{"error":"no data"}. - 원인:
memory/price-history/SPY.json없음. 템플릿에서/api/chart/호출 중. -
요구: price-history collector 실행/연결 또는 UI에서 missing 데이터 graceful fallback.
-
P1/P2 —
/graph404 처리 결정 필요 - 재현:
/graph404. -
요구: 실제 사용 링크가 있다면
/vault/knowledge-graph또는/graphify/로 redirect. 없다면 보고서에서 “별칭 기대가 잘못된 것”으로 낮춰 분류. -
P2 —
/api/briefing/default,/api/briefing/latest의 오류성 재분류 필요 - 재현: 둘 다 404.
-
보고서 설명처럼
morning/evening만 contract라면 실제 caller 존재 여부를 grep한 뒤 “버그”가 아니라 “잘못된 테스트 입력”으로 낮춰야 한다. -
P2 —
POST /api/alert-check호출자 추적 필요 - 재현: 404.
-
요구: 남아 있는 cron/browser/external caller를 찾아 제거하거나 route 구현.
-
P2 — 누락 route 감사 보완 필요
- 최소 추가 목록:
/market/api/summarize,/api/watchlist/matches,/api/vault/note,/news/partial/<sector>,/watchlist/news/<item_id>,/market/bond-study/<date_str>,/market/bond-study/<date_str>/pdf,/admin/api/telemetry/*. -
요구: 원본 보고서에 route map 기반 전수 표 또는 별첨 TSV 추가.
-
P2 — 데이터 missing manifest를 시각 고정으로 저장 필요
- 현재
commodity-alerts/latest.json은 존재한다. 원본 보고서 작성 이후 상태가 바뀐 것으로 보인다. -
요구: 감사 시각의 파일 목록/mtime/size를
knowledge-agent쪽에 저장해 재현성 확보. -
P3 — 회귀 테스트와 플레이북 부재
- 요구: 최소 smoke test: status code, JSON content-type, auth redirect, known missing-data graceful behavior.
- 요구:
knowledge-agent/500-signals/에 웹앱 감사 재현 플레이북 저장.
8. 추가 발견
/api/vault/note는 valid path를 넣으면200이다. 따라서 원본의/vault/note400은 “빈 query contract”에 가깝고, 실제 고장으로 보긴 약하다./admin/api/telemetry/summary는 비로그인에서302이며,curl -L은 최종 login HTML200을 반환한다. API 정상 여부를 판단할 때는 redirect follow 여부를 분리해야 한다./market/bond-study/2026-04-14와/market/bond-study/2026-04-14/pdf는 현재 404다. 원본이 bond index만 확인해 detail route 공백을 놓쳤다./market/api/summarize는 빈 입력에서ID 없음HTML을 반환한다. 정상일 수 있으나 route coverage에는 포함해야 한다./news/partial/semiconductor와/watchlist/news/sample은 200이지만 실제 내용은 빈 상태 메시지다. 데이터 missing 영향으로 분류해야 한다.
9. 최종 승인 여부
REVISE
이유:
- 원본 보고서의 핵심 고장 원인 자체는 대부분 정확하다.
- 그러나 route 전수 감사 요구를 만족하지 못했다. 등록 route 대비 누락된 API/partial/detail route가 있다.
- 회귀 테스트, 재현 manifest,
500-signals플레이북이 없다. - 일부 항목은 시간이 지나며 상태가 변했으므로 스냅샷 증거 없이 단정하면 재현성이 떨어진다.
원 담당자는 위 결함 목록을 반영해 보고서를 개정하거나, 별도 보완 보고서와 route smoke test 결과를 추가해야 한다.