Files
joungmin eca097aa2c [02-Architect] #260 design spec + ADR-0005
- design/260-gemma-tool-calling/README.md — overall (12 AC + 7 OQ + 모듈 구조)
- fn-tool_dispatcher.md — multi-tool router (validate → confirm gate → handler → envelope)
- fn-add_habit_handler.md — destructive 대표 (R3/R7/R8 enforce)
- fn-confirm_gate.md — 모달 AlertDialog 흐름 (OQ-3 = 모달 확정)
- fn-chat_session_controller.md — multi-turn loop 상태 머신 (MAX_TURNS=4)
- ADR-0005 — in-app tool runtime + R 규칙 = 핸들러 책임 + schema SoT=Dart + 모달

OQ-1/2/4 = README §11 결정. OQ-3 = 모달 (사용자 결정).
신규 OQ-5/6/7 = Developer 가 구현 중 검증.

Refs #260
2026-06-15 10:15:44 +09:00

107 lines
4.5 KiB
Markdown

# 함수 설계서: `ConfirmGate.show` (#260)
> **부모 설계서**: ./README.md · **상태**: Draft
> **작성**: [AI] Architect · **구현**: `lib/ai/tools/confirm_gate.dart:show` · **테스트**: `test/ui/confirm_gate_test.dart`
## 1. 시그니처
```dart
class ConfirmGate {
static Future<bool> show(
BuildContext context,
ToolDefinition tool,
Map<String, dynamic> args,
);
}
```
## 2. 책임 (단일 책임, 1줄)
destructive tool 실행 직전 모달 AlertDialog 을 띄워 사용자 confirm 여부를 `Future<bool>` 로 반환한다.
## 3. 입력
| 파라미터 | 타입 | 제약 | 설명 |
|---|---|---|---|
| `context` | BuildContext | `context.mounted == true` | chat screen 의 context |
| `tool` | ToolDefinition | isDestructive=true | 어떤 도구인가 |
| `args` | Map<String, dynamic> | 이미 validate 통과 | 사용자에게 보여줄 인자 |
## 4. 출력
- **반환**: `Future<bool>`
- `true` = 사용자가 "수행" 탭
- `false` = 사용자가 "취소" 또는 outside-tap dismiss 또는 `context.mounted == false`
- **부수효과**: 모달 표시 (UI). DB 변경 없음.
## 5. 동작 / 알고리즘
```
1. if !context.mounted:
return false
2. summary = _summarize(tool.name, args)
// tool 별 사람 친화 요약 함수 (per-tool overridable)
3. result = await showDialog<bool>(
context: context,
barrierDismissible: true, // outside-tap = 취소
builder: (ctx) => AlertDialog(
title: Text('이 작업을 수행할까요?'),
content: Column(mainAxisSize: min, crossAxisAlignment: start, children: [
Text(tool.description, style: bodyMedium),
SizedBox(height: 12),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.surfaceVariant,
borderRadius: BorderRadius.circular(8),
),
child: Text(summary),
),
]),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('취소')),
FilledButton(
autofocus: true,
onPressed: () => Navigator.pop(ctx, true),
child: Text('수행'),
),
],
),
)
4. return result ?? false // dismiss 시 null → false
```
### `_summarize` 규칙 (tool 별)
- `add_habit` → "프로토콜 '$title'을 ${frame_level} 프레임으로 새 습관으로 추가합니다.\n • 문장: \"$framed_text\"\n • 앵커: ${anchor_when ?? '-'} / ${anchor_after_what ?? '-'}"
- `log_tracker_entry` → "$habit_title 의 ${date ?? '오늘'} 기록을 '${value == 'done' ? '완료' : '공란'}' 으로 저장합니다."
- 기타 → JSON pretty (fallback)
`title` 은 핸들러가 호출 직전 lookup 해서 args 에 채워줄 수 있지만, 단순화를 위해 ConfirmGate 가 직접 catalog/habit 조회는 안 함 — args 에 이미 있는 값만 사용. (안 채워졌으면 protocol_id 그대로 노출 — 트레이드오프 수용.)
## 6. 에러 & 실패 모드
| 조건 | 처리 | 반환 |
|---|---|---|
| context dispose 후 호출 | guard 즉시 | `false` |
| showDialog 자체 예외 (이론상 없음) | rethrow X — catch 후 false 반환 | `false` |
| args 가 _summarize 가 기대 안 한 형태 | toString fallback | 정상 동작 (dialog 노출) |
## 7. 엣지케이스
- **chat 모달 위에 chat 모달**: 동시에 호출되지 않도록 ChatSessionController 가 직렬화 (한 turn = 한 tool call 만). 다중 destructive tool 병렬 호출 시 첫 confirm 만 처리, 나머지는 `ToolCancelled` 자동 반환 (OQ-1 영향).
- **시스템 back press**: Android 뒤로가기 → dialog dismiss → false. 의도된 cancel.
- **autofocus + 키보드 enter**: 수행 버튼 기본 포커스. 의도치 않은 enter 누름 위험 — 사용자 결정으로 수용 (단축키 활용성 ↑).
## 8. 복잡도 / 성능
- O(1). 사용자 대기 시간 = 무한 (사용자 입력 대기).
- 호출 빈도: 사용자 대화 turn 당 0 또는 1.
## 9. 테스트 케이스 (필수)
| 케이스 | 셋업 | 입력 | 기대 |
|---|---|---|---|
| confirm | MaterialApp + 모달 진입 → "수행" tap | add_habit args | true |
| cancel | "취소" tap | 동일 | false |
| outside dismiss | barrier tap | 동일 | false |
| unmounted context | context dispose 후 호출 | — | false 즉시 |
## 10. 의존
- Flutter `showDialog`, `AlertDialog`
- `ToolDefinition` (description 출처)
- per-tool summary 규칙은 별도 함수로 분리 (`_summarize(toolName, args)`) — 단위 테스트 용이.