Files
hanarang-rails/.plans/design/triggers.md
이랑이 565a2960e2 docs(design): transport 추상화 + 트리거 3종 + opt-in 원칙 확정
- design/transports.md — DiscordTransport (v1) / GatewayHttpTransport (v2) / MockTransport
  Rails invoke payload를 HTML 주석으로 감싸 디스코드 전송 (자기야는 자연어만 봄)
- design/triggers.md — opt-in 3종:
  1. /rails start 슬래시 커맨드
  2. 하랑이 제안 → 사용자 승인
  3. Gitea webhook → 자동 배포 진입
- OVERVIEW.md — scope에 opt-in 원칙 + 일상대화 Out of Scope 명시 + 문서 링크 9개로 확장

결정: 자기야 승인 — 2026-04-10 확인
- DiscordTransport 를 v1 기본 transport 로 채택
- 자매 자연어 포스팅은 그대로 유지 (가시성)
- gateway HTTP API 는 v2 에서 스펙 확인 후 전환

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:04:43 +09:00

221 lines
7.2 KiB
Markdown

# Design — Rails 트리거 모드
> **Rails 는 opt-in**. 4자매의 일상 대화를 가로채지 않는다.
> Rails 가 "켜지는" 경로는 명시적으로 3가지 뿐.
## 원칙
- **Casual 대화 vs Project 모드의 경계가 명확해야 한다.**
- Casual: 자매 자유 대화. Rails 개입 0. SQLite 기록 없음.
- Project: Rails FSM 이 주도. sprint contract + enforcement 적용.
- **자동 가로채기 금지.** 사용자 의도 없이 project 모드로 전이할 수 없다.
- **하랑이는 제안만 할 수 있다.** 실제 시작은 사용자 승인 필요.
## 트리거 모드 3종
### 트리거 1 — Slash Command (가장 명시적)
Rails Discord 봇이 등록한 슬래시 커맨드:
```
/rails start <project> "<요구사항>"
/rails start arang "Sprint 002 — Live2D 아바타 추가"
/rails status [pipeline-id]
/rails resume <pipeline-id>
/rails abort <pipeline-id>
/rails inspect <pipeline-id>
/rails timeline <pipeline-id>
```
**흐름**:
1. 사용자가 디스코드에서 `/rails start arang "..."` 실행
2. Rails 봇이 파이프라인 ID 생성 + Discord 포럼 스레드 생성
3. 하랑이 호출 (DiscordTransport invoke)
4. FSM 이 `idle → planning` 전이
5. 이후 파이프라인 끝까지 자동 진행
**장점**:
- 가장 명확. opt-in 이 100% 확실
- Rails 봇이 권한 체크 가능 (자기야만 실행 허용 등)
- 파라미터 검증 즉시
### 트리거 2 — 하랑이의 제안 (자연스러움)
평소 채팅 중 프로젝트 스케일 요청이 감지되면 **하랑이가 제안만** 함. 자동 실행 금지.
```
자기야: "아랑이 proactive 엔진 진짜 필요할 것 같아. 한 번 만들어볼래?"
하랑이 🦊: 이거 Sprint 단위 작업 같은데, rails 파이프라인으로 넘길까?
요구사항: "Arang Proactive 엔진 프로토타입"
예상 타입: feature
예상 스프린트: 2~3개 분해 예정
승인하면 다음 커맨드를 실행해줘:
/rails start arang "Proactive 엔진 프로토타입"
(또는 "ㅇㅇ" 이라고 답하면 내가 propose 파일 만들어두고
자기야가 확인만 하면 실행되도록 할게)
```
**흐름**:
1. 하랑이 시스템 프롬프트에 "project-scale 감지 시 rails 제안" 룰 내장
2. 감지 조건 (예시):
- "프로젝트", "기획", "구현", "배포", "Sprint", "만들어줘" 등 키워드
- 다단계 작업 필요 판단
- 여러 자매 협업 필요 판단
3. 하랑이가 **제안 메시지** 포스팅 (실행 아님)
4. 사용자가:
- 옵션 A: 슬래시 커맨드 직접 실행
- 옵션 B: "ㅇㅇ" / "응" 같이 짧게 동의
5. 옵션 B 인 경우, 하랑이는 `.rails/proposals/` 에 proposal 파일 생성하고, rails 봇이 이를 감지해 `proposal` 상태로 등록
6. 사용자가 `/rails approve <proposal-id>` 또는 제안 메시지의 확인 버튼을 누르면 실제 `rails start` 실행
**자동 실행 안 하는 이유**:
- 하랑이 LLM 이 잘못 감지할 수 있음
- 사용자 의도 확인이 반드시 필요
- `principles.md` 원칙 4 (강제 > 권고)
**하랑이 제안 → 승인 트랜잭션**:
```
proposal 상태 (.rails/proposals/<id>.json):
{
"id": "01HW0PR0P",
"proposedBy": "harang",
"at": "2026-04-10T13:00:00Z",
"project": "arang",
"title": "Proactive 엔진 프로토타입",
"rationale": "다단계 작업 + proactive 감지 + 설정 UI 필요",
"status": "pending",
"expiresAt": "2026-04-10T14:00:00Z" // 1시간 후 자동 만료
}
사용자 승인:
→ status: "approved"
→ rails start 트리거
→ pipelineId 생성 후 status: "consumed"
```
### 트리거 3 — Gitea Webhook (자동 배포 진입)
특정 이벤트 발생 시 rails 가 **배포 단계부터** 자동 진입. 기획/구현은 안 함.
#### 지원 이벤트
| Gitea 이벤트 | Rails 액션 | 진입 상태 |
|---|---|---|
| `push` to main with tag (`v*`) | 이랑이 deploy actor 자동 호출 | `deploying` |
| `push` to main without tag | 이랑이 `smoke-check` actor | `verifying` |
| `pull_request` merged to main | 다랑이 post-merge review | `post-review` |
| `release created` | 이랑이 배포 수행 | `deploying` |
#### 흐름 (tag push 예시)
```
1. 개발자가 push tag v0.2.0 to hanarang/arang
2. Gitea webhook → POST https://rails.hanarang.local/webhook/gitea
3. Rails webhook receiver:
- Gitea signature 검증 (HMAC)
- 이벤트 타입 + 저장소 + ref 확인
4. Rails 가 새 파이프라인 생성 (`type: deploy-only`)
5. FSM 을 `idle → deploying` 상태로 직접 전이
6. 이랑이(erang) actor 호출 — project type 감지 → 배포 수행
7. Discord 파이프라인 스레드 생성 + 진행 알림
8. 배포 완료 후 `done` 전이
9. 사용자에게 배포 결과 알림 (디스코드 멘션)
```
#### 보안
- Webhook URL 은 Gitea 내부망에서만 접근 가능 (vmbr1)
- HMAC 서명 검증 필수
- IP 화이트리스트 (Gitea 서버 IP 만)
- secret 은 `.env``GITEA_WEBHOOK_SECRET`
- 실패 시 즉시 로그 + 알람
#### 저장소별 구성
`.rails/webhooks.yaml`:
```yaml
repositories:
- org: hanarang
repo: arang
events: ['push', 'tag', 'release']
branches: ['main']
pipelineType: deploy-only
deployTarget: production
- org: hanarang
repo: hanarang-rails
events: ['tag']
pipelineType: deploy-only
deployTarget: self-install
```
## 비트리거 (안 함)
Rails 는 다음 상황에서 **자동 개입하지 않는다**:
- 자매 간 일상 대화
- 자기야와의 일상 대화 (잡담, 질문, 상담)
- Discord 의 일반 메시지 (rails 마커 없는)
- 멘션 (`@나랑이` 등) — 기존 자매 봇 경로로만 작동
- 단순 파일 편집 (git commit 등)
- CI 실행 결과 (별도 CI 시스템 이미 있으면 그쪽 우선)
## 모드 구분 가드 (자매 봇 쪽)
자매가 메시지를 받으면:
```pseudo
onDiscordMessage(msg):
# Project 모드 (Rails invoke)
if msg.content has '<!-- rails:invoke v1 -->':
return handleRailsInvoke(msg)
# Casual 모드 (기존 대화)
llmRespond(msg)
```
- 마커 없으면 무조건 Casual. LLM 정상 응답.
- 마커 있으면 Project. structured 처리.
- 한 메시지 안에 두 모드 섞을 수 없음 (동시 발생 X).
## 제안 메시지의 최소 스펙 (하랑이 쪽)
하랑이가 Project 감지 시 생성하는 제안 메시지 템플릿:
```markdown
**📋 Rails 파이프라인 제안**
감지된 project-scale 작업:
- 프로젝트: `{project}`
- 제목: {title}
- 예상 타입: `{feature|bugfix|scaffold|infra|migration|refactor}`
- 근거: {rationale}
실행하려면:
```
/rails start {project} "{title}"
```
또는 "ㅇㅇ" / "응" 이라고 답하면 proposal 파일을 만들어둘게.
<!-- rails:proposal v1 -->
```json
{
"proposalId": "01HW0PR0P",
"project": "{project}",
"title": "{title}",
"type": "{type}",
"rationale": "{rationale}"
}
```
<!-- /rails:proposal -->
```
**주의**: 마커는 rails 봇이 감지. 자기야는 주석 없이 자연어 메시지만 봄.
## 관련 문서
- `transports.md` — DiscordTransport (구조화 payload 전달)
- `handoff.md` — HandoffMessage 스키마
- `skill-enforcement.md` — Rails 모드 진입 후 hook 동작
- `principles.md` — 원칙 1, 4