docs: LLM 모델 SSOT 전환 계획 및 workspace-config 리서치 갱신

Made-with: Cursor
This commit is contained in:
happybell80 2026-03-16 09:33:58 +09:00
parent 53da59d673
commit 1b3a0a5632
2 changed files with 150 additions and 4 deletions

View File

@ -24,8 +24,16 @@
- `rb8001`의 모델 기본값/직접 handler 생성/직접 Gemini SDK 호출/fallback 배열 정리 - `rb8001`의 모델 기본값/직접 handler 생성/직접 Gemini SDK 호출/fallback 배열 정리
- `skill-slack`의 하드코딩 모델 문자열 정리 - `skill-slack`의 하드코딩 모델 문자열 정리
- 전수 확인으로 드러난 `rb8001`의 추가 직접 생성 경로 정리
- `skill-news`의 독자 `GEMINI_MODEL` SSOT와 직접 Gemini SDK 호출 정리
- 모델 변경 검증 체크리스트 고정 - 모델 변경 검증 체크리스트 고정
### 포함 근거
- 리서치 기준 `skill-news`는 단순 모델 문자열 1~2개가 아니라, `README`, `.env.example`, `docker-compose.yml`, 서비스 코드, API 모듈 import 시점 초기화가 함께 `.env``GEMINI_MODEL`을 독자 SSOT로 강화하고 있습니다.
- 따라서 `skill-news`를 범위 밖으로 두면 로빙 전체 관점에서 "주 모델 변경이 SSOT 1회 수정으로 닫힌다"는 목표를 충족할 수 없습니다.
- 특히 `skill-news/app/api/news_endpoints.py``news_summarizer = NewsSummarizer()`는 프로세스 시작 시점에 현재 모델 계약을 고정하므로, 단순 문서 수정이나 compose 수정만으로는 충분하지 않습니다.
### 제외 ### 제외
- 새 provider registry 파일 추가 - 새 provider registry 파일 추가
@ -53,6 +61,10 @@
- 직접 `genai.GenerativeModel(...)` 생성 제거 - 직접 `genai.GenerativeModel(...)` 생성 제거
- [rb8001/app/services/ir_analyzer.py](../../../../rb8001/app/services/ir_analyzer.py) - [rb8001/app/services/ir_analyzer.py](../../../../rb8001/app/services/ir_analyzer.py)
- 파일 내부 fallback 배열 제거 또는 중앙 정책으로 이동 - 파일 내부 fallback 배열 제거 또는 중앙 정책으로 이동
- [rb8001/app/services/diary/generator.py](../../../../rb8001/app/services/diary/generator.py)
- 직접 `GeminiHandler()` 생성 제거 검토
- [rb8001/app/services/workflows/headlines_workflow.py](../../../../rb8001/app/services/workflows/headlines_workflow.py)
- `or GeminiHandler(default_model)` 우회 제거
### 3. 스킬 계층 정리 ### 3. 스킬 계층 정리
@ -63,6 +75,38 @@
- [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py) - [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py)
- 필요 최소한의 기본 모델 설정만 추가 검토합니다. - 필요 최소한의 기본 모델 설정만 추가 검토합니다.
### 4. skill-news 정리
- [skill-news/app/services/news_summarizer.py](../../../../skill-news/app/services/news_summarizer.py)
- [skill-news/app/services/companyx_news_summarizer.py](../../../../skill-news/app/services/companyx_news_summarizer.py)
- [skill-news/app/services/sea_news_filter.py](../../../../skill-news/app/services/sea_news_filter.py)
- 직접 `genai.GenerativeModel(...)` 생성과 `GEMINI_MODEL` 독자 SSOT를 정리합니다.
- [skill-news/app/api/news_endpoints.py](../../../../skill-news/app/api/news_endpoints.py)
- import 시점 `NewsSummarizer()` 생성이 어떤 설정 계약을 고정하는지 정리합니다.
- [skill-news/docker-compose.yml](../../../../skill-news/docker-compose.yml)
- 모델 주입을 `workspace-config/runtime.env` 기준으로 재해석할지 확정합니다.
- [skill-news/README.md](../../../../skill-news/README.md)
- [skill-news/.env.example](../../../../skill-news/.env.example)
- 문서 SSOT 표현을 코드/운영 계약과 맞춥니다.
## 수정 규모 기준
### 1. 최소 직접 수정 범위
- `rb8001` 9개 내외
- `skill-slack` 4개 내외
- `skill-news` 6개 내외
- 서비스 코드 4개
- compose 1개
- 문서/예시 2개 중 계약상 필수 범위 포함
- 문서/테스트 제외 최소 직접 수정 예상은 `19개 내외`로 봅니다.
### 2. 완전 종결 범위
- 위 최소 범위 외에도 compose의 `.env` 오버라이드, `workspace-config` 주입 순서, 서비스별 예외를 더 확인해야 합니다.
- `skill-news`는 독자 제품 SSOT처럼 운영돼 온 흔적이 있어, 계획 실행 중 예외 선언으로 분리할지 통합 전환할지 정책 판단이 추가로 필요할 수 있습니다.
- 따라서 이 계획은 `핵심 SSOT 경로 복구 계획`이고, `로빙 전체 workspace-config 완전 종결 계획`과는 범위를 구분합니다.
## 정책 판단 ## 정책 판단
### 1. 유지 가능한 정책 ### 1. 유지 가능한 정책
@ -77,6 +121,8 @@
- router/service의 직접 handler 생성 - router/service의 직접 handler 생성
- 서비스 파일 안의 직접 Gemini/OpenAI SDK 생성 - 서비스 파일 안의 직접 Gemini/OpenAI SDK 생성
- `skill-slack`의 고정 모델 문자열 - `skill-slack`의 고정 모델 문자열
- `skill-news``.env` 기반 독자 모델 SSOT 표현
- import 시점 초기화가 특정 모델 계약을 고정하는 구조
### 3. 추가하지 않을 것 ### 3. 추가하지 않을 것
@ -104,25 +150,31 @@
- `genai.GenerativeModel(...)` 직접 생성이 공용 경로 밖에서 사라집니다. - `genai.GenerativeModel(...)` 직접 생성이 공용 경로 밖에서 사라집니다.
- `ir_analyzer.py`의 파일 내부 fallback 배열이 제거되거나 중앙 정책으로 이동합니다. - `ir_analyzer.py`의 파일 내부 fallback 배열이 제거되거나 중앙 정책으로 이동합니다.
- `skill-slack`의 하드코딩 모델 문자열이 제거됩니다. - `skill-slack`의 하드코딩 모델 문자열이 제거됩니다.
- `diary/generator.py`, `headlines_workflow.py`의 직접 `GeminiHandler` 생성이 제거됩니다.
- `skill-news`의 직접 Gemini SDK 생성과 `GEMINI_MODEL` 독자 SSOT가 정리됩니다.
- `skill-news/app/api/news_endpoints.py`의 import 시점 초기화가 새 계약과 모순되지 않게 정리됩니다.
## 적용 순서 ## 적용 순서
1. `rb8001` 공용 경로 정리 1. `rb8001` 공용 경로 정리
- `config.py`, `llm_service.py`, `gemini_handler.py` - `config.py`, `llm_service.py`, `gemini_handler.py`
2. `rb8001` 우회 경로 제거 2. `rb8001` 우회 경로 제거
- `llm_endpoint.py`, `coldmail_llm_classifier.py`, `ir_analyzer.py` - `llm_endpoint.py`, `coldmail_llm_classifier.py`, `ir_analyzer.py`, `diary/generator.py`, `workflows/headlines_workflow.py`
3. `skill-slack` 모델 선택 정리 3. `skill-slack` 모델 선택 정리
- `digest.py`, `action_extractor.py`, `summarizer.py`, 필요 시 `app/core/config.py` - `digest.py`, `action_extractor.py`, `summarizer.py`, 필요 시 `app/core/config.py`
4. 기본값 변경 검증 4. `skill-news` 모델 경로 정리
- `news_summarizer.py`, `companyx_news_summarizer.py`, `sea_news_filter.py`, `docker-compose.yml`, `README.md`, `.env.example`
5. 기본값 변경 검증
- `workspace-config/runtime.env` - `workspace-config/runtime.env`
- 과도기 `rb8001/.env` - 과도기 `rb8001/.env`
- API 입력 `model` override - API 입력 `model` override
5. worklog 작성 후 닫힘 선언 6. worklog 작성 후 닫힘 선언
## 레포별 경계 ## 레포별 경계
- 1차 대상 레포: `robeing/rb8001` - 1차 대상 레포: `robeing/rb8001`
- 2차 대상 레포: `robeing/skill-slack` - 2차 대상 레포: `robeing/skill-slack`
- 3차 대상 레포: `robeing/skill-news`
- 문서/검증 기록 레포: `robeing/DOCS` - 문서/검증 기록 레포: `robeing/DOCS`
- 한 번에 여러 레포를 한 커밋으로 묶지 않습니다. - 한 번에 여러 레포를 한 커밋으로 묶지 않습니다.
@ -130,6 +182,7 @@
- `rb8001` 변경은 `rb8001` 레포 안에서만 롤백 판단합니다. - `rb8001` 변경은 `rb8001` 레포 안에서만 롤백 판단합니다.
- `skill-slack` 변경은 `skill-slack` 레포 안에서만 롤백 판단합니다. - `skill-slack` 변경은 `skill-slack` 레포 안에서만 롤백 판단합니다.
- `skill-news` 변경은 `skill-news` 레포 안에서만 롤백 판단합니다.
- 문서 변경은 `DOCS` 레포 안에서만 롤백 판단합니다. - 문서 변경은 `DOCS` 레포 안에서만 롤백 판단합니다.
- 과도기에는 `workspace-config/runtime.env``rb8001/.env`를 이전 값으로 되돌리는 것이 가장 작은 운영 롤백 단위입니다. - 과도기에는 `workspace-config/runtime.env``rb8001/.env`를 이전 값으로 되돌리는 것이 가장 작은 운영 롤백 단위입니다.
@ -139,4 +192,5 @@
2. 과도기에도 `workspace-config/runtime.env``rb8001/.env`까지만 수정하면 됩니다. 2. 과도기에도 `workspace-config/runtime.env``rb8001/.env`까지만 수정하면 됩니다.
3. API 입력 모델명으로 요청 단위 override가 가능합니다. 3. API 입력 모델명으로 요청 단위 override가 가능합니다.
4. 리서치 문서에 적힌 직접 우회 경로가 실제 코드에서 제거됩니다. 4. 리서치 문서에 적힌 직접 우회 경로가 실제 코드에서 제거됩니다.
5. 닫힘 선언은 `worklog`에서만 합니다. 5. `skill-news`까지 포함해 독자 모델 SSOT 표현이 정리됩니다.
6. 닫힘 선언은 `worklog`에서만 합니다.

View File

@ -229,6 +229,47 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
- [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py) - [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py)
- 현재 LLM 기본 모델 설정 자체가 없습니다. - 현재 LLM 기본 모델 설정 자체가 없습니다.
### 6. 전수 확인으로 추가 확인된 누락 범위
- [rb8001/app/services/diary/generator.py](../../../../rb8001/app/services/diary/generator.py)
- `GeminiHandler()`를 직접 생성합니다.
- 기본 모델은 간접적으로 따라가더라도, 호출 진입점이 `LLMService` 단일 경로로 닫히지 않았습니다.
- [rb8001/app/services/workflows/headlines_workflow.py](../../../../rb8001/app/services/workflows/headlines_workflow.py)
- `llm_service.handlers.get(default_model) or GeminiHandler(default_model)` 경로가 남아 있습니다.
- 기본값은 `settings.DEFAULT_LLM_MODEL`을 보더라도 handler 직접 생성 우회가 살아 있습니다.
- [skill-news/app/services/news_summarizer.py](../../../../skill-news/app/services/news_summarizer.py)
- `os.getenv("GEMINI_MODEL", "gemini-2.5-flash-lite")` 기본값 문자열과 직접 `genai.GenerativeModel(...)` 생성이 남아 있습니다.
- [skill-news/app/services/companyx_news_summarizer.py](../../../../skill-news/app/services/companyx_news_summarizer.py)
- `os.getenv("GEMINI_MODEL", "gemini-2.5-flash-lite")` 기본값 문자열과 직접 `genai.GenerativeModel(...)` 생성이 남아 있습니다.
- [skill-news/app/services/sea_news_filter.py](../../../../skill-news/app/services/sea_news_filter.py)
- `GEMINI_MODEL`을 독자 SSOT처럼 요구하고 직접 `genai.GenerativeModel(...)`를 생성합니다.
- [skill-news/app/api/news_endpoints.py](../../../../skill-news/app/api/news_endpoints.py)
- 모듈 import 시점에 `news_summarizer = NewsSummarizer()`가 즉시 생성됩니다.
- 즉 `skill-news`는 단순 설정 조회가 아니라, 프로세스 시작 시점부터 `.env``GEMINI_MODEL` 계약을 전제로 동작합니다.
- [skill-news/docker-compose.yml](../../../../skill-news/docker-compose.yml)
- 현재 `workspace-config/runtime.env`를 읽지 않고 `${GEMINI_MODEL}`을 직접 전달합니다.
- [skill-news/README.md](../../../../skill-news/README.md)
- 모델명 SSOT를 `.env``GEMINI_MODEL`로 선언하고 있어, 현재 로빙 전체 목표 SSOT와 충돌합니다.
- [skill-news/.env.example](../../../../skill-news/.env.example)
- `GEMINI_MODEL` 예시값을 별도 SSOT처럼 유지합니다.
### 7. compose / env_file 계층 확인 결과
- [rb8001/docker-compose.yml](../../../../rb8001/docker-compose.yml)
- `env_file``.env`, `/home/admin/workspace-config/runtime.env`, `/home/admin/workspace-config/secrets.env`를 함께 읽습니다.
- 즉 구조는 workspace-config를 쓰지만, 서비스별 `.env` 오버라이드가 아직 실제 경로에 남아 있습니다.
- [skill-slack/docker-compose.yml](../../../../skill-slack/docker-compose.yml)
- `env_file``.env`, `/home/admin/workspace-config/runtime.env`, `/home/admin/workspace-config/secrets.env`를 함께 읽습니다.
- [skill-rag-file/docker-compose.yml](../../../../skill-rag-file/docker-compose.yml)
- `env_file` 3중 구조와 별도 `./.env:/app/.env:ro` 마운트가 함께 있습니다.
- 로빙 전체 SSOT 문제를 닫을 때 `.env` 오버라이드가 실제 런타임 우선순위를 어떻게 바꾸는지 별도 점검이 필요합니다.
- [skill-calendar/docker-compose.yml](../../../../skill-calendar/docker-compose.yml)
- [skill-email/docker-compose.yml](../../../../skill-email/docker-compose.yml)
- [robeing-monitor/docker-compose.yml](../../../../robeing-monitor/docker-compose.yml)
- [robeing-gateway/docker-compose.yml](../../../../robeing-gateway/docker-compose.yml)
- 공통으로 `workspace-config`와 서비스별 `.env`를 함께 읽는 구조가 남아 있습니다.
- 따라서 `LLM 모델 SSOT`를 넘어 `서비스별 .env 오버라이드` 문제까지 완전 종결하려면 후속 점검 범위가 더 큽니다.
## 확인 사실 요약표 ## 확인 사실 요약표
| 항목 | 확인 사실 | 해석 | | 항목 | 확인 사실 | 해석 |
@ -242,6 +283,10 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
| API 입력 model | `rb8001/app/services/llm/models.py``LLMRequest.model` 존재 | override 구조는 이미 있음 | | API 입력 model | `rb8001/app/services/llm/models.py``LLMRequest.model` 존재 | override 구조는 이미 있음 |
| OpenAI handler | `rb8001/app/services/llm/openai_handler.py` 존재 | provider 추상화 일부 존재 | | OpenAI handler | `rb8001/app/services/llm/openai_handler.py` 존재 | provider 추상화 일부 존재 |
| skill-slack 모델 선택 | `digest.py`, `action_extractor.py`, `summarizer.py`에 모델 문자열 존재 | 설정화 또는 정책화 필요 | | skill-slack 모델 선택 | `digest.py`, `action_extractor.py`, `summarizer.py`에 모델 문자열 존재 | 설정화 또는 정책화 필요 |
| rb8001 추가 직접 생성 | `diary/generator.py`, `workflows/headlines_workflow.py``GeminiHandler` 직접 생성 잔존 | 계획 누락 |
| skill-news 독자 SSOT | `skill-news``.env``GEMINI_MODEL`을 자체 SSOT로 유지 | 로빙 전체 SSOT와 충돌 |
| skill-news 초기화 시점 | `news_endpoints.py`가 import 시점에 `NewsSummarizer()` 생성 | 런타임 계약이 프로세스 시작 시점에 고정 |
| compose env 계층 | 여러 서비스가 `workspace-config``.env`를 함께 읽음 | 구조는 있으나 오버라이드 잔존 |
## 최소 변경 원칙 ## 최소 변경 원칙
@ -288,6 +333,49 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
- [skill-slack/app/services/action_extractor.py](../../../../skill-slack/app/services/action_extractor.py) - [skill-slack/app/services/action_extractor.py](../../../../skill-slack/app/services/action_extractor.py)
- [skill-slack/app/services/summarizer.py](../../../../skill-slack/app/services/summarizer.py) - [skill-slack/app/services/summarizer.py](../../../../skill-slack/app/services/summarizer.py)
- 하드코딩 모델 문자열을 설정값 또는 API 입력값으로 승격 - 하드코딩 모델 문자열을 설정값 또는 API 입력값으로 승격
- [rb8001/app/services/diary/generator.py](../../../../rb8001/app/services/diary/generator.py)
- 직접 `GeminiHandler()` 생성 제거 검토
- [rb8001/app/services/workflows/headlines_workflow.py](../../../../rb8001/app/services/workflows/headlines_workflow.py)
- `or GeminiHandler(default_model)` 우회 제거
- [skill-news/app/services/news_summarizer.py](../../../../skill-news/app/services/news_summarizer.py)
- [skill-news/app/services/companyx_news_summarizer.py](../../../../skill-news/app/services/companyx_news_summarizer.py)
- [skill-news/app/services/sea_news_filter.py](../../../../skill-news/app/services/sea_news_filter.py)
- `GEMINI_MODEL` 독자 SSOT와 직접 Gemini SDK 생성을 정리
- [skill-news/app/api/news_endpoints.py](../../../../skill-news/app/api/news_endpoints.py)
- import 시점 인스턴스 생성이 어떤 설정 계약을 고정하는지 함께 정리
- [skill-news/docker-compose.yml](../../../../skill-news/docker-compose.yml)
- `workspace-config/runtime.env` 기준 모델 주입으로 맞출지 검토
- [skill-slack/app/core/config.py](../../../../skill-slack/app/core/config.py)
- LLM 기본 모델 설정 경로를 둘지 여부 확정 필요
### 1-1. 현재 전수 확인 기준 최소 직접 수정 규모
- `rb8001` 9개 내외
- `app/core/config.py`
- `app/services/llm/llm_service.py`
- `app/services/llm/gemini_handler.py`
- `app/router/llm_endpoint.py`
- `app/services/coldmail_llm_classifier.py`
- `app/services/ir_analyzer.py`
- `app/services/diary/generator.py`
- `app/services/workflows/headlines_workflow.py`
- 필요 시 `app/services/llm/models.py`
- `skill-slack` 4개 내외
- `app/services/digest.py`
- `app/services/action_extractor.py`
- `app/services/summarizer.py`
- `app/core/config.py`
- `skill-news` 6개 내외
- `skill-news`는 서비스 코드 4개 + compose 1개 + 문서/예시 2개까지 걸쳐 있어, 코드 수정만으로 끝나지 않고 계약 문서도 같이 바뀌어야 합니다.
- `app/services/news_summarizer.py`
- `app/services/companyx_news_summarizer.py`
- `app/services/sea_news_filter.py`
- `app/api/news_endpoints.py`
- `docker-compose.yml`
- `README.md`
- `.env.example`
- 따라서 2026-03-16 전수 확인 기준으로, 문서/테스트 제외 최소 직접 수정 예상은 `19개 내외`로 보는 편이 맞습니다.
- 특히 `skill-news``.env` 기반 독자 SSOT를 README와 compose, 서비스 초기화 코드가 함께 강화하고 있으므로, 단순 코드 치환보다 "계약 전환"으로 다루는 편이 맞습니다.
### 2. 굳이 지금 추가하지 않아도 되는 것 ### 2. 굳이 지금 추가하지 않아도 되는 것
@ -310,6 +398,8 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
### 2. 로빙의 현재 모델 구조 문제도 같은 유형입니다 ### 2. 로빙의 현재 모델 구조 문제도 같은 유형입니다
- `DEFAULT_LLM_MODEL`은 선언돼 있지만, 실제 경로는 `GeminiHandler` 직접 생성, Gemini SDK 직접 호출, `gemini-*` fallback 리스트, 서비스별 개별 모델 하드코딩이 섞여 있습니다. - `DEFAULT_LLM_MODEL`은 선언돼 있지만, 실제 경로는 `GeminiHandler` 직접 생성, Gemini SDK 직접 호출, `gemini-*` fallback 리스트, 서비스별 개별 모델 하드코딩이 섞여 있습니다.
- `skill-news`는 이 문제의 바깥 예외가 아니라, 별도 제품 SSOT를 이미 갖고 있는 하위 서비스입니다.
- 그래서 로빙 전체 모델 SSOT를 하나로 묶으려면 `skill-news`를 예외로 선언하거나, 아니면 README/compose/service 초기화까지 함께 계약 전환해야 합니다.
- 그래서 `주 모델 변경``SSOT 1개 값 변경`으로 닫히지 않고, 여러 서비스의 코드 경로를 다시 건드려야 하는 구조가 됩니다. - 그래서 `주 모델 변경``SSOT 1개 값 변경`으로 닫히지 않고, 여러 서비스의 코드 경로를 다시 건드려야 하는 구조가 됩니다.
- 즉 지금의 직접 원인은 `gpt-5-mini` 전환 작업이고, 근본 원인은 `모델 호출 계약의 SSOT``실제 호출 구현`이 분리되지 않은 점입니다. - 즉 지금의 직접 원인은 `gpt-5-mini` 전환 작업이고, 근본 원인은 `모델 호출 계약의 SSOT``실제 호출 구현`이 분리되지 않은 점입니다.
- 이 해석은 서버와 로컬 모두 동일합니다. 서버별 모델 차이가 필요해도 그것은 코드 차이가 아니라 `runtime.env` 값 차이로만 표현돼야 합니다. - 이 해석은 서버와 로컬 모두 동일합니다. 서버별 모델 차이가 필요해도 그것은 코드 차이가 아니라 `runtime.env` 값 차이로만 표현돼야 합니다.
@ -322,6 +412,7 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
- 이 기준이 닫혀야 `DEFAULT_LLM_MODEL` 같은 값이 실제 SSOT가 됩니다. - 이 기준이 닫혀야 `DEFAULT_LLM_MODEL` 같은 값이 실제 SSOT가 됩니다.
- 닫혀야 할 최종 경로는 `runtime.env -> settings.DEFAULT_LLM_MODEL -> 단일 LLM factory/service -> handler`입니다. - 닫혀야 할 최종 경로는 `runtime.env -> settings.DEFAULT_LLM_MODEL -> 단일 LLM factory/service -> handler`입니다.
- `GEMINI_MODEL`, 코드 기본값 문자열, 요청별 모델 직접 주입, 서비스별 fallback 배열은 모두 이 경로의 우회로로 봐야 합니다. - `GEMINI_MODEL`, 코드 기본값 문자열, 요청별 모델 직접 주입, 서비스별 fallback 배열은 모두 이 경로의 우회로로 봐야 합니다.
- `skill-news`처럼 README와 `.env.example`, compose, import 시점 초기화 코드가 함께 특정 키를 독자 SSOT로 강화하는 경우도 같은 우회로로 봐야 합니다.
- 사용자 작업 기준으로는 목표 수정 지점과 과도기 최소 수정 지점을 함께 고정해야 합니다. - 사용자 작업 기준으로는 목표 수정 지점과 과도기 최소 수정 지점을 함께 고정해야 합니다.
- 목표 수정 지점: `workspace-config/runtime.env` - 목표 수정 지점: `workspace-config/runtime.env`
- 과도기 최소 수정 지점: `workspace-config/runtime.env``rb8001/.env` - 과도기 최소 수정 지점: `workspace-config/runtime.env``rb8001/.env`
@ -334,6 +425,7 @@ tags: [research, llm, ssot, workspace-config, hardcoding, robeing]
- 서비스별 `.env`와 코드 fallback 중 무엇이 아직 실제 런타임 경로에 남아 있는지 전체 인벤토리 - 서비스별 `.env`와 코드 fallback 중 무엇이 아직 실제 런타임 경로에 남아 있는지 전체 인벤토리
- 로컬 기준 공용 파일만 읽도록 바꿀 때, 프로젝트별 예외가 필요한 서비스가 있는지 여부 - 로컬 기준 공용 파일만 읽도록 바꿀 때, 프로젝트별 예외가 필요한 서비스가 있는지 여부
- 서버 기준 공용 파일만 읽도록 바꿀 때, 운영상 예외가 필요한 서비스가 있는지 여부 - 서버 기준 공용 파일만 읽도록 바꿀 때, 운영상 예외가 필요한 서비스가 있는지 여부
- `skill-news`를 로빙 전체 모델 SSOT 전환 범위에 포함할지, 아니면 별도 제품 SSOT 예외로 둘지 최종 정책 확정
## workspace-config 로컬 이식 기준 ## workspace-config 로컬 이식 기준