Files

4290 lines
163 KiB
Markdown
Raw Permalink Normal View History

# 74일차 Lab 테스트 결과 · 실제로 만든 시나리오와 `test -v` 결과
> **정답 자료** · 74일차 n8n 워크플로우를 **만드는 프롬프트(codex) → push → `call -v` · `bench` · `test -v` · `gate` · `release` 결과**를 학생이 가는 순서대로 한 문서에 모았습니다. 수업이 끝난 뒤 공개합니다.
- 실행: 2026-10-27, 실습을 Lab마다 5개(예제 3 + 과제 2)로 늘린 뒤 강사 노트북의 65일차 n8n 2.42.6 + 74일차 판 데이터 API(지시문 저장소 · 릴리스 · 점검 기록 · 배포 정책), OpenAI `gpt-4.1-mini` · `gpt-5.4-nano`, 케이스 파일마다 **한 번씩** (Lab 01 예제 3 · Lab 02 예제 3만 `--repeat 3`), 모두 31단계 (00~30)
- 명령: `~/aio-01d4-stu/Last3Mon/74`에서 `uv run tools/flow.py ...`. 아래 단계마다 **실행한 명령**과 **화면에 나온 출력 전부**를 그대로 붙이고, 그 아래 **쉽게 말하면**으로 결과를 풀어 썼습니다 (특히 ✘가 나온 까닭과 그것이 예상한 것인지). 같은 출력이 `NN-<이름>.txt`에, `test`의 마지막 보고서가 `NN-<이름>.json`에 있습니다 (`diff` · `call` · `bench`만 한 단계와 28단계는 보고서 없음).
- LLM 문장과 **시간은 실행마다 다릅니다.** 이번 실행에서 정답의 잘못으로 실패한 케이스는 없습니다. ✘는 모두 "고치기 전" 단계와 일부러 보여 주는 단계(나쁜 릴리스 · 나쁜 카나리)에서 본 것입니다.
- 수업 준비 중 실제로 일어난 일: v2와 2.0.0의 작은 모델 `gpt-4.1-nano`가 OpenAI에서 **지원 종료**되어 500이 났습니다. `gpt-5.4-nano`로 바꾼 뒤 처음부터 다시 돌린 기록입니다.
## `-v`가 보여 주는 것
```text
✔ 후보 시험 · 1.1.0 고정 · 1.6s ← 케이스 이름 · 시간 (틀리면 틀린 곳)
→ request POST /webhook/my-helpdesk ← 보낸 요청 JSON (--set release=... 이 더해진 것)
{ "question": "VPN …", "user": "이준호", "release": "1.1.0" }
← response HTTP 200 ← 받은 응답 JSON
{ "route": "자동 답변", "answer": "… [FAQ-003]", "version": { "release": "1.1.0", "channel": "pinned", … } }
```
오늘은 응답의 **`version`**(release · prompt_version · model · channel · prompt_source)을 봐야 **어느 릴리스가 답했는지** 알 수 있습니다.
## 이 문서 하나로 따라가기
단계마다 **① codex에 넣는 프롬프트 → ② codex가 `my-work/*.json`(또는 `cases/my-golden.json`)을 쓰고 `check` → ③ 내가 `push` → ④ 내가 `test -v` · `gate` · `release`** 순서입니다. 재기만 하는 예제(Lab 01 예제 1 · 3, Lab 02 예제 2 · 3, Lab 03 예제 1 · 3)와 고치기 전 단계는 프롬프트가 없습니다.
```mermaid
flowchart LR
P["① 프롬프트<br/>터미널 ① codex"] --> C["② codex<br/>my-work/*.json · check"] --> U["③ push<br/>터미널 ②"] --> T["④ test -v · gate · release<br/>version · 시간"]
T -->|✘| X["실패 줄 · 보고서 경로를<br/>codex에 그대로"] --> C
```
- **프롬프트는 정답본(`LAB-0x-A-*.md`)의 요청문 예**를 그대로 썼습니다 (실습 문서 빈칸 채우기 뼈대의 `____`를 채운 글자라 빈칸 자리에 띄어쓰기가 남아 있습니다).
- **이 기록의 워크플로우는 수업 준비 때 만든 정답 파일**입니다. 프롬프트를 codex에 보내 다시 만들어 재지는 않았습니다 (68일차부터 수업 준비는 정답 워크플로우만 실행).
- 학생은 `push my-work/helpdesk.json`, 이 기록은 같은 웹훅(`my-helpdesk`)의 정답 파일을 push했습니다. 그래서 `test` 명령과 결과 모양은 같습니다.
## 시나리오 한눈에
```mermaid
flowchart LR
L1["Lab 01 (00~12)<br/>diff 7개<br/>v1 9/15 · v2 15/15 (×3)<br/>과제 2 2/4 → 4/4"] --> L2["Lab 02 (13~21)<br/>약한 golden 1.2.0 PASS<br/>강한 golden 1.2.0 1/9 FAIL<br/>2.1.0 PASS → FAIL"]
L2 --> L3["Lab 03 (22~30)<br/>카나리 3/6 → 6/6<br/>나쁜 카나리 4/6 → 6/6<br/>점검 기록 · 정책 5/5"]
```
| 단계 | 먼저 올린 것 (`push`) | 테스트 · 재기 | 결과 | 파일 |
| --- | --- | --- | --- | --- |
| 00 | `reference/helpdesk-v1.json` · `v2.json` | `release --reset` · `doctor` | ready | `00-prep` |
| 01 | (없음) | `diff` · `diff --full` | 7개 | `01-lab01-diff` |
| 02 | (같음) | `call rel-v1` · `rel-v2` | 2.8초 / 1.0초 `[FAQ-003]` | `02-lab01-call-v1-v2` |
| 03 | `reference/lab01-example2-version.json` | `lab01` | 3/3 | `03-lab01-version` |
| 04 | (같음) | `lab01-compare --webhook rel-v1 --repeat 3` | 9/15 | `04-lab01-compare-v1` |
| 05 | (같음) | `lab01-compare --webhook rel-v2 --repeat 3` | 15/15 | `05-lab01-compare-v2` |
| 06 | (같음) | `bench rel-v1` · `rel-v2` | p50 1.23 / 0.98초 | `06-lab01-bench-v1-v2` |
| 07 | (같음) | `lab01-challenge` | 2/3 | `07-lab01-challenge-before` |
| 08 | `answers/74-lab01-challenge-registry.json` | `lab01-challenge` | 3/3 | `08-lab01-challenge` |
| 09 | (같음) | `lab01` | 3/3 | `09-lab01-after-challenge` |
| 10 | (같음) | `lab01-challenge2` | 2/4 | `10-lab01-challenge2-before` |
| 11 | `answers/74-lab01-challenge2-fallback.json` | `lab01-challenge2` | 4/4 | `11-lab01-challenge2` |
| 12 | (같음) | `lab01-challenge` | 3/3 | `12-lab01-challenge-after2` |
| 13 | (같음) | `release` · `lab02` | 1/5 | `13-lab02-before` |
| 14 | `reference/lab02-example1-release.json` | `lab02` | 5/5 | `14-lab02-release` |
| 15 | (같음) | `lab01-challenge` · `lab01-challenge2` | 3/3 · 4/4 | `15-lab02-lab01-again` |
| 16 | (같음) | `gate golden` × 4 · `call 1.2.0` | 모두 PASS | `16-lab02-golden` |
| 17 | (같음) | `bench --set` × 2 · `gate --repeat 3` | 1.23 / 1.12초 · 18/18 | `17-lab02-baseline` |
| 18 | (같음) | `gate my-golden` × 4 | 1.2.0만 FAIL 1/9 | `18-lab02-my-golden` |
| 19 | (같음) | `lab02-challenge2` · `gate my-golden 2.1.0` | 4/5 · PASS | `19-lab02-challenge2-before` |
| 20 | `answers/74-lab02-challenge2-broken.json` | `lab02-challenge2` · `gate my-golden 2.1.0` | 5/5 · FAIL | `20-lab02-challenge2` |
| 21 | (같음) | `lab02` · `lab01-challenge2` | 5/5 · 4/4 | `21-lab02-after-challenge2` |
| 22 | (같음) | `release` · `call` × 2 · `lab03` | 2/2 | `22-lab03-deploy` |
| 23 | (같음) | `release --canary` · `lab03-canary` | 3/6 | `23-lab03-canary-before` |
| 24 | `reference/lab03-example2-canary.json` | `lab03-canary` · `lab02` · `lab02-challenge2` | 6/6 · 5/5 · 5/5 | `24-lab03-canary` |
| 25 | (같음) | `lab03-watch` | 4/6 | `25-lab03-watch` |
| 26 | (같음) | `--no-canary` · `lab03-watch --no-before` | 6/6 | `26-lab03-watch-after` |
| 27 | `answers/74-lab03-challenge-release-check.json` | `lab03-challenge` · `call 2.0.0` | 3/3 · promote | `27-lab03-challenge` |
| 28 | (같음) | `lab03-challenge2` | 점검 둘 ✘ · 1.1.0 배포 409로 멈춤 | `28-lab03-challenge2-before` |
| 29 | `answers/74-lab03-challenge2-gated.json` | `lab03-challenge2` | 5/5 | `29-lab03-challenge2` |
| 30 | (같음) | `--require-gate on` · `gate` · `release` · `doctor` | 2.1.0 거부 · 2.0.0 배포 · ready | `30-lab03-gate-flow` |
## 준비
### 00 · 수업 준비 · 처음 상태 · v1 · v2 게시 · doctor
실행한 명령:
```bash
uv run tools/flow.py release helpdesk --reset
uv run tools/flow.py push reference/helpdesk-v1.json reference/helpdesk-v2.json
uv run tools/flow.py doctor
```
출력 (그대로):
```text
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate promote 6/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate rollback 0/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
✔ pushed '74 IT helpdesk v1 (provided)' (id w0ed73a832099852)
POST http://localhost:5678/webhook/rel-v1
✔ pushed '74 IT helpdesk v2 (provided)' (id w8e65b72e125f790)
POST http://localhost:5678/webhook/rel-v2
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ releases /releases/helpdesk · gates /gates/helpdesk (day 74 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
```
**쉽게 말하면**
- 릴리스를 처음 상태(active 1.0.0, 카나리 없음)로 돌리고, 비교용 v1 · v2(`rel-v1` · `rel-v2`)를 게시하고, `doctor`가 `ready`입니다.
- v2는 처음에 `gpt-4.1-nano`였는데 OpenAI가 지원 종료해 500이 났습니다. 10/27 `gpt-5.4-nano`로 바꾼 뒤의 기록입니다 (1단계 강의 2절).
## Lab 01 · 무엇이 바뀌었나, 답마다 버전, 지시문은 저장소로
### 01 · Lab 01 예제 1 · v1 → v2에서 바뀐 것 → 7개
실행한 명령:
```bash
uv run tools/flow.py diff reference/helpdesk-v1.json reference/helpdesk-v2.json
uv run tools/flow.py diff reference/helpdesk-v1.json reference/helpdesk-v2.json --full
```
출력 (그대로):
```text
74 IT helpdesk v1 (provided) → 74 IT helpdesk v2 (provided)
+ node 'Review check' (code)
~ 'Answer' parameters.options.systemMessage: changed (307 → 419 chars)
~ 'Answer model' parameters.model.value: gpt-4.1-mini → gpt-5.4-nano
~ 'Question webhook' parameters.path: rel-v1 → rel-v2
+ link Answer → Review check (main)
+ link Review check → Build result (main)
- link Answer → Build result (main)
7 change(s)
74 IT helpdesk v1 (provided) → 74 IT helpdesk v2 (provided)
+ node 'Review check' (code)
~ 'Answer' parameters.options.systemMessage: changed (307 → 419 chars)
before: You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say "찾지 못했습니다" and that a staff member will help. Use 합니다체.
after: You are the company IT help desk. Answer in Korean in at most 3 sentences, using only the FAQ entries and the ticket record given. Never invent steps, dates or names. For a ticket, say its status and note. If the data does not answer the question, say "찾지 못했습니다" and that a staff member will help. Use 합니다체.
When the answer comes from a FAQ entry, end with its id in brackets, for example [FAQ-003]. Keep answers short.
~ 'Answer model' parameters.model.value: gpt-4.1-mini → gpt-5.4-nano
~ 'Question webhook' parameters.path: rel-v1 → rel-v2
+ link Answer → Review check (main)
+ link Review check → Build result (main)
- link Answer → Build result (main)
7 change(s)
```
**쉽게 말하면**
- 노드 단위로 비교하니 바뀐 것이 7개: 새 노드 `Review check`, 지시문 307 → 419자, 모델 mini → nano, 웹훅 경로, 연결 셋.
- `--full`로 보면 지시문에 두 문장(출처 번호 붙이기 · 짧게)이 더해졌습니다.
- 셋(모델 · 지시문 · 흐름)이 **한꺼번에** 바뀌었다는 것이 핵심입니다. 예상한 결과.
### 02 · 예제 1 · 같은 질문을 v1 · v2에
실행한 명령:
```bash
uv run tools/flow.py call rel-v1 requests/faq-vpn.json -v
uv run tools/flow.py call rel-v2 requests/faq-vpn.json -v
```
출력 (그대로):
```text
HTTP 200 · 2.8s
→ request POST /webhook/rel-v1
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
]
}
HTTP 200 · 1.0s
→ request POST /webhook/rel-v2
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. [FAQ-003]",
"sources": [
"FAQ-003"
]
}
```
**쉽게 말하면**
- v1은 2.8초, 출처 번호 없음. v2는 1.0초, 끝에 `[FAQ-003]`.
- 답이 달라진 것은 보이지만, 이 한 번으로는 지시문 · 모델 · 노드 중 무엇 때문인지 모릅니다. 시간도 한 번 잰 값입니다 (예제 3에서 다시).
### 03 · Lab 01 예제 2 · 답마다 version → `lab01` 3/3
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `reference/lab01-example2-version.json`):
```text
my-work/helpdesk.json (v1 을 복사한 것) 의 웹훅 경로를 my-helpdesk 로, 이름을 "74 IT helpdesk (my)" 로 바꿔 줘.
Has question? 뒤에 Release info(Set) 를 넣어 release 1.0.0, prompt_version p1, model gpt-4.1-mini.
Answer model 의 모델 값은 ={{ $('Release info').first().json.model }} 로 Release info 의 model 을 읽게.
Build result 결과에 version : {release, prompt_version, model} 을 Release info 에서 더해 줘. 지시문은 그대로 .
기대 결과 cases/lab01.json.
```
실행한 명령:
```bash
uv run tools/flow.py push reference/lab01-example2-version.json
uv run tools/flow.py test cases/lab01.json -v
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + release API (reference)' (w2caac7c1a6981b8): it used the same webhook path
✔ pushed '74 IT helpdesk + version (reference)' (id w473a152b356a298)
POST http://localhost:5678/webhook/my-helpdesk
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 버전이 결과에 · 2.5s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 접수 현황도 버전과 함께 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 3/3
report: runs/lab01-20261027-183702.json
```
**쉽게 말하면**
- 답에 `version: {release 1.0.0, prompt_version p1, model gpt-4.1-mini}`가 붙었습니다. 모델 노드와 결과가 같은 `Release info`를 읽습니다.
- 빈 질문은 그대로 400. 3/3, 예상한 결과.
### 04 · Lab 01 예제 3 · `lab01-compare`를 v1에 3번 → 9/15
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-compare.json --webhook rel-v1 --repeat 3
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/rel-v1 · 5 case(s) × 3
✔ VPN · 해결 낱말 #1 · 1.2s
✔ VPN · 해결 낱말 #2 · 1.2s
✔ VPN · 해결 낱말 #3 · 1.1s
✘ VPN · 출처 번호 #1 · 1.1s · answer has none of ['[FAQ-003]']
✘ VPN · 출처 번호 #2 · 1.2s · answer has none of ['[FAQ-003]']
✘ VPN · 출처 번호 #3 · 1.3s · answer has none of ['[FAQ-003]']
✔ 프린터 · 해결 낱말 #1 · 1.0s
✔ 프린터 · 해결 낱말 #2 · 1.1s
✔ 프린터 · 해결 낱말 #3 · 1.1s
✘ 프린터 · 출처 번호 #1 · 1.1s · answer has none of ['[FAQ-']
✘ 프린터 · 출처 번호 #2 · 1.2s · answer has none of ['[FAQ-']
✘ 프린터 · 출처 번호 #3 · 1.0s · answer has none of ['[FAQ-']
✔ 접수 현황 #1 · 0.9s
✔ 접수 현황 #2 · 1.0s
✔ 접수 현황 #3 · 1.1s
Passed 9/15
report: runs/lab01-compare-20261027-183719.json
```
**쉽게 말하면**
- 출처 번호 두 케이스가 **세 번 모두** ✘, 나머지 셋은 세 번 모두 ✔ (9/15).
- 흔들림이 아니라 늘 같은 결과입니다. v1은 출처 번호를 붙이지 않는 버전이라 ✘가 **예상한 것**입니다.
### 05 · 예제 3 · 같은 테스트를 v2에 3번 → 15/15
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-compare.json --webhook rel-v2 --repeat 3
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/rel-v2 · 5 case(s) × 3
✔ VPN · 해결 낱말 #1 · 1.0s
✔ VPN · 해결 낱말 #2 · 1.0s
✔ VPN · 해결 낱말 #3 · 1.0s
✔ VPN · 출처 번호 #1 · 0.9s
✔ VPN · 출처 번호 #2 · 1.1s
✔ VPN · 출처 번호 #3 · 0.9s
✔ 프린터 · 해결 낱말 #1 · 1.4s
✔ 프린터 · 해결 낱말 #2 · 1.1s
✔ 프린터 · 해결 낱말 #3 · 1.2s
✔ 프린터 · 출처 번호 #1 · 1.4s
✔ 프린터 · 출처 번호 #2 · 1.1s
✔ 프린터 · 출처 번호 #3 · 1.2s
✔ 접수 현황 #1 · 1.1s
✔ 접수 현황 #2 · 1.0s
✔ 접수 현황 #3 · 1.0s
Passed 15/15
report: runs/lab01-compare-20261027-183736.json
```
**쉽게 말하면**
- v2는 다섯 케이스 모두 세 번 다 ✔. 출처 번호의 차이는 **흔들림이 아니라 바뀐 것**(신호)입니다.
- 그래도 지시문 · 모델 · 노드 중 무엇 때문인지는 아직 모릅니다 → Lab 02의 한 가지씩 바꾼 릴리스.
### 06 · 예제 3 · v1 · v2 시간 (`bench -n 5`)
실행한 명령:
```bash
uv run tools/flow.py bench rel-v1 requests/faq-vpn.json -n 5
uv run tools/flow.py bench rel-v2 requests/faq-vpn.json -n 5
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/rel-v1 × 5
✔ #1 · HTTP 200 · 1.26s · route=자동 답변
✔ #2 · HTTP 200 · 1.11s · route=자동 답변
✔ #3 · HTTP 200 · 1.23s · route=자동 답변
✔ #4 · HTTP 200 · 1.34s · route=자동 답변
✔ #5 · HTTP 200 · 1.18s · route=자동 답변
p50 1.23s · p95 1.34s · max 1.34s · failures 0/5
report: runs/bench-rel-v1-20261027-183742.json
POST http://localhost:5678/webhook/rel-v2 × 5
✔ #1 · HTTP 200 · 1.20s · route=자동 답변
✔ #2 · HTTP 200 · 0.97s · route=자동 답변
✔ #3 · HTTP 200 · 0.98s · route=자동 답변
✔ #4 · HTTP 200 · 1.02s · route=자동 답변
✔ #5 · HTTP 200 · 0.98s · route=자동 답변
p50 0.98s · p95 1.20s · max 1.20s · failures 0/5
report: runs/bench-rel-v2-20261027-183747.json
```
**쉽게 말하면**
- v1 p50 1.23초 · p95 1.34초, v2 p50 0.98초 · p95 1.20초. v2가 조금 빠릅니다.
- 모델이 작아진 덕일 것 같지만 셋이 함께 바뀌어 단정할 수 없습니다. 시간은 실행마다 다릅니다.
### 07 · Lab 01 과제 1 하기 전 → `lab01-challenge` 2/3
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-challenge.json -v
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✘ 지시문은 저장소에서 · 1.2s · version.prompt_source = null (expected registry)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
✔ 접수 현황 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini"
}
}
Passed 2/3
report: runs/lab01-challenge-20261027-183751.json
```
**쉽게 말하면**
- 첫 케이스 ✘: `version.prompt_source`가 없습니다. 지시문이 아직 워크플로우 안에 있기 때문입니다.
- 나머지 둘(p1은 출처 번호 없음, 접수 현황)은 ✔. 예상한 결과.
### 08 · Lab 01 과제 1 · 지시문을 저장소에서 → 3/3
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab01-challenge-registry.json`):
```text
my-work/helpdesk.json 의 Release info 뒤에 Get prompt(HTTP GET) 를 넣어 줘.
주소는 http://data-api:8800/prompts/helpdesk/ prompt_version (Release info 의 값을 표현식으로).
Answer 의 systemMessage 는 지금 글자 대신 Get prompt 의 text 를 표현식으로 읽게.
Build result 의 version 에 prompt_source : ' registry ' 를 더해 줘. 다른 것은 그대로.
기대 결과 cases/lab01-challenge.json, cases/lab01.json.
```
실행한 명령:
```bash
uv run tools/flow.py push answers/74-lab01-challenge-registry.json
uv run tools/flow.py test cases/lab01-challenge.json -v
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + version (reference)' (w473a152b356a298): it used the same webhook path
✔ pushed '74 IT helpdesk + prompt registry (answer)' (id w10c91b5c7439d37)
POST http://localhost:5678/webhook/my-helpdesk
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 2.7s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 3/3
report: runs/lab01-challenge-20261027-183810.json
```
**쉽게 말하면**
- `Get prompt`가 `/prompts/helpdesk/p1`을 읽고 Agent의 지시문이 그 `text`가 됐습니다. `prompt_source: registry`.
- 3/3, 예상한 결과.
### 09 · 과제 1 뒤 `lab01` → 3/3
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01.json -v
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 버전이 결과에 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황도 버전과 함께 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 3/3
report: runs/lab01-20261027-183812.json
```
**쉽게 말하면**
- 예제 2의 테스트도 그대로 통과. 지시문을 밖으로 꺼내도 답은 같습니다 (p1 글이 같으니까).
### 10 · Lab 01 과제 2 하기 전 → `lab01-challenge2` 2/4
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-challenge2.json -v
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✘ 저장소 고장 · 기본 지시문으로 답 · 0.1s · status 500 (expected 200); route = null (expected 자동 답변); version.prompt_source = null (expected fallback); answer has none of ['인증서']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 500
{
"message": "Error in workflow"
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "slow",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "slow",
"slow_seconds": 8.0
}
✘ 저장소 느림 · 오래 기다리지 않음 · 9.1s · took 9.1s (expected at most 6s); version.prompt_source = registry (expected fallback)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 2/4
report: runs/lab01-challenge2-20261027-183824.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
```
**쉽게 말하면**
- 저장소 고장(`prompts error`): **HTTP 500** `Error in workflow` (0.1초). 지시문 하나 못 가져와서 답을 통째로 못 했습니다.
- 저장소 느림(`prompts slow` 8초): **9.1초** 기다린 뒤 답 (`max_seconds 6` ✘). 답은 맞았지만 너무 오래 걸렸습니다.
- 평소 · 되돌린 뒤 두 케이스는 ✔. `after`가 고장 스위치를 매번 `ok`로 되돌렸습니다. 예상한 결과.
### 11 · Lab 01 과제 2 · 저장소가 멈추면 기본 지시문 → 4/4
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab01-challenge2-fallback.json`):
```text
my-work/helpdesk.json 의 Get prompt 에 options.timeout 2000 과 onError continueErrorOutput 를 넣어 줘.
성공 쪽은 Registry prompt(Set, text = $json.text), 실패 쪽은 Fallback prompt(Set, text = data-api/data/prompts/helpdesk- p1 .md 와 같은 글).
두 Set 모두 Search FAQ 로 이어 줘. Answer 의 systemMessage 는 Registry prompt 가 실행됐으면 그 text, 아니면 Fallback prompt 의 text.
version.prompt_source 는 Registry prompt 가 실행됐으면 'registry', 아니면 ' fallback '.
기대 결과 cases/lab01-challenge2.json, cases/lab01-challenge.json.
```
실행한 명령:
```bash
uv run tools/flow.py push answers/74-lab01-challenge2-fallback.json
uv run tools/flow.py test cases/lab01-challenge2.json -v
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + prompt registry (answer)' (w10c91b5c7439d37): it used the same webhook path
✔ pushed '74 IT helpdesk + prompt fallback (answer)' (id w5215c7f75644ed0)
POST http://localhost:5678/webhook/my-helpdesk
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 2.6s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "slow",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "slow",
"slow_seconds": 8.0
}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 4/4
report: runs/lab01-challenge2-20261027-183845.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
```
**쉽게 말하면**
- 고장: 200, `fallback`, "인증서" (1.3초). 느림: 2초에 포기하고 기본 지시문으로 3.0초.
- 고장이 끝나면 다시 `registry`. 4/4, 예상한 결과.
### 12 · 과제 2 뒤 `lab01-challenge` → 3/3
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-challenge.json -v
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ p1은 출처 번호를 붙이지 않음 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 접수 현황 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "T-1002 처리 상태 알려 주세요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "T-1002 티켓은 현재 처리 중이며, 인증서 발급 승인 대기 중입니다. 10월 20일에 완료될 예정입니다. 추가 문의가 있으시면 연락 주시기 바랍니다.",
"sources": [],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
Passed 3/3
report: runs/lab01-challenge-20261027-183849.json
```
**쉽게 말하면**
- 평소에는 여전히 저장소 지시문(`registry`)으로 답합니다. 기본 지시문 길을 더해도 평소 동작은 그대로.
## Lab 02 · 릴리스 고르기, 후보 시험, 배포 전 게이트
### 13 · Lab 02 예제 1 하기 전 → `lab02` 1/5
실행한 명령:
```bash
uv run tools/flow.py release helpdesk
uv run tools/flow.py test cases/lab02.json -v
```
출력 (그대로):
```text
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate promote 6/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate rollback 0/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✘ 운영 중인 릴리스 (active) · 1.2s · version.channel = null (expected stable)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 후보 시험 · 1.1.0 고정 · 1.6s · version.release = 1.0.0 (expected 1.1.0); version.channel = null (expected pinned); version.prompt_version = p1 (expected p2); answer has none of ['[FAQ-003]']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다만, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 후보 시험 · 2.0.0 (작은 모델) · 1.3s · version.release = 1.0.0 (expected 2.0.0); version.model = gpt-4.1-mini (expected gpt-5.4-nano)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✘ 없는 릴리스 · 1.2s · status 200 (expected 400); known has none of ['1.0.0']
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"prompt_source": "registry"
}
}
✔ 빈 질문 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 1/5
report: runs/lab02-20261027-183854.json
```
**쉽게 말하면**
- `release helpdesk`: active 1.0.0, 릴리스 다섯 개.
- 고정한 1.1.0 · 2.0.0도 1.0.0으로 답했고, 9.9.9도 200으로 답했습니다. 릴리스가 아직 워크플로우 안의 Set이라 본문 `release`를 보지 않습니다. `channel`도 없습니다.
- 빈 질문 하나만 ✔ (1/5). 예상한 결과.
### 14 · Lab 02 예제 1 · 릴리스를 밖에서 읽고 고정 → 5/5
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `reference/lab02-example1-release.json`):
```text
my-work/helpdesk.json 의 Release info(Set) 를 Get release(HTTP GET http://data-api:8800/releases/helpdesk) → Pick release(Code) → Known release?(If) 로 바꿔 줘.
Pick release: 본문 release 가 있으면 그 릴리스 · channel pinned , 없으면 active · channel stable . 고른 릴리스의 prompt_version · model 을 돌려준다.
releases 에 없으면 Known release? false → Reject release(400, {error: 'unknown release', release, known }).
Get prompt · Answer model · Build result 는 Release info 대신 Pick release 를 읽게. 기본 지시문(Fallback prompt)은 그대로.
기대 결과 cases/lab02.json, cases/lab01-challenge.json, cases/lab01-challenge2.json.
```
실행한 명령:
```bash
uv run tools/flow.py push reference/lab02-example1-release.json
uv run tools/flow.py test cases/lab02.json -v
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + prompt fallback (answer)' (w5215c7f75644ed0): it used the same webhook path
✔ pushed '74 IT helpdesk + release API (reference)' (id w2caac7c1a6981b8)
POST http://localhost:5678/webhook/my-helpdesk
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 2.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 후보 시험 · 1.1.0 고정 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속이 끊기는 경우 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속해 보시기 바랍니다. 접수 건은 찾지 못했습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.0.0",
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 없는 릴리스 · 0.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ 빈 질문 · 0.2s
→ request POST /webhook/my-helpdesk
{
"question": "",
"user": "이준호"
}
← response HTTP 400
{
"error": "question is required"
}
Passed 5/5
report: runs/lab02-20261027-183911.json
```
**쉽게 말하면**
- 본문 `release`가 없으면 1.0.0 · `stable`, 있으면 그 릴리스 · `pinned`. 2.0.0은 `gpt-5.4-nano`로 답했고, 9.9.9는 400 `known` 목록.
- 5/5, 예상한 결과.
### 15 · 예제 1 뒤 `lab01-challenge` 3/3 · `lab01-challenge2` 4/4
실행한 명령:
```bash
uv run tools/flow.py test cases/lab01-challenge.json
uv run tools/flow.py test cases/lab01-challenge2.json
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 3 case(s) × 1
✔ 지시문은 저장소에서 · 1.2s
✔ p1은 출처 번호를 붙이지 않음 · 1.3s
✔ 접수 현황 · 1.2s
Passed 3/3
report: runs/lab01-challenge-20261027-183915.json
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.4s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.1s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.1s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.3s
Passed 4/4
report: runs/lab01-challenge2-20261027-183922.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
```
**쉽게 말하면**
- Lab 01의 테스트도 그대로 통과. 릴리스를 밖에서 읽게 바꿔도 지시문 저장소 · 기본 지시문은 그대로 일합니다.
### 16 · Lab 02 예제 2 · 약한 golden으로 네 릴리스 → 모두 PASS
실행한 명령:
```bash
uv run tools/flow.py gate cases/golden.json --set release=1.0.0 --max-p95 3
uv run tools/flow.py gate cases/golden.json --set release=1.1.0 --max-p95 3
uv run tools/flow.py gate cases/golden.json --set release=1.2.0 --max-p95 3
uv run tools/flow.py gate cases/golden.json --set release=2.0.0 --max-p95 3
uv run tools/flow.py call my-helpdesk '{"question": "프린터 출력이 안 돼요", "user": "최유진", "release": "1.2.0"}' -v
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.2s
✔ 비밀번호 · 1.2s
✔ 프린터 · 1.1s
✔ 메일함 · 1.3s
✔ 접수 처리 중 · 1.0s
✔ 접수 처리 완료 · 1.6s
Passed 6/6
report: runs/golden-20261027-183929.json
gate [release=1.0.0]: pass 6/6 (100%) · p95 1.6s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.0s
✔ 프린터 · 1.1s
✔ 메일함 · 1.1s
✔ 접수 처리 중 · 1.1s
✔ 접수 처리 완료 · 1.1s
Passed 6/6
report: runs/golden-20261027-183936.json
gate [release=1.1.0]: pass 6/6 (100%) · p95 1.1s → PASS
✔ gate record → /gates/helpdesk: 1.1.0 promote
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 0.8s
✔ 비밀번호 · 0.9s
✔ 프린터 · 0.8s
✔ 메일함 · 1.0s
✔ 접수 처리 중 · 1.2s
✔ 접수 처리 완료 · 1.5s
Passed 6/6
report: runs/golden-20261027-183942.json
gate [release=1.2.0]: pass 6/6 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 1.2.0 promote
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.1s
✔ 프린터 · 2.0s
✔ 메일함 · 1.2s
✔ 접수 처리 중 · 1.0s
✔ 접수 처리 완료 · 1.2s
Passed 6/6
report: runs/golden-20261027-183950.json
gate [release=2.0.0]: pass 6/6 (100%) · p95 2.0s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
HTTP 200 · 0.9s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.2.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
```
**쉽게 말하면**
- 1.0.0 · 1.1.0 · 1.2.0 · 2.0.0 **모두 6/6 PASS** (p95 1.6 · 1.1 · 1.5 · 2.0초).
- 그런데 1.2.0의 프린터 답은 "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요." 해결 방법이 없는 떠넘기기입니다.
- golden이 `status 200`과 `route`만 봐서 생긴 일입니다. **게이트는 테스트만큼만 막습니다.** 예상한 결과 (일부러 보여 주는 것).
### 17 · Lab 02 예제 3 · 기준선: 시간과 3번 반복
실행한 명령:
```bash
uv run tools/flow.py bench my-helpdesk requests/faq-vpn.json -n 5 --set release=1.0.0
uv run tools/flow.py bench my-helpdesk requests/faq-vpn.json -n 5 --set release=2.0.0
uv run tools/flow.py gate cases/golden.json --set release=1.0.0 --repeat 3 --max-p95 3
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk × 5
✔ #1 · HTTP 200 · 1.18s · route=자동 답변, release=1.0.0
✔ #2 · HTTP 200 · 1.42s · route=자동 답변, release=1.0.0
✔ #3 · HTTP 200 · 1.17s · route=자동 답변, release=1.0.0
✔ #4 · HTTP 200 · 1.23s · route=자동 답변, release=1.0.0
✔ #5 · HTTP 200 · 1.23s · route=자동 답변, release=1.0.0
p50 1.23s · p95 1.42s · max 1.42s · failures 0/5
report: runs/bench-my-helpdesk-20261027-183957.json
POST http://localhost:5678/webhook/my-helpdesk × 5
✔ #1 · HTTP 200 · 1.10s · route=자동 답변, release=2.0.0
✔ #2 · HTTP 200 · 1.24s · route=자동 답변, release=2.0.0
✔ #3 · HTTP 200 · 1.05s · route=자동 답변, release=2.0.0
✔ #4 · HTTP 200 · 1.12s · route=자동 답변, release=2.0.0
✔ #5 · HTTP 200 · 1.12s · route=자동 답변, release=2.0.0
p50 1.12s · p95 1.24s · max 1.24s · failures 0/5
report: runs/bench-my-helpdesk-20261027-184003.json
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 3
✔ VPN #1 · 1.1s
✔ VPN #2 · 1.1s
✔ VPN #3 · 1.2s
✔ 비밀번호 #1 · 1.1s
✔ 비밀번호 #2 · 1.3s
✔ 비밀번호 #3 · 1.3s
✔ 프린터 #1 · 1.1s
✔ 프린터 #2 · 1.0s
✔ 프린터 #3 · 1.3s
✔ 메일함 #1 · 1.1s
✔ 메일함 #2 · 1.1s
✔ 메일함 #3 · 1.1s
✔ 접수 처리 중 #1 · 1.1s
✔ 접수 처리 중 #2 · 1.0s
✔ 접수 처리 중 #3 · 1.0s
✔ 접수 처리 완료 #1 · 1.5s
✔ 접수 처리 완료 #2 · 1.1s
✔ 접수 처리 완료 #3 · 1.1s
Passed 18/18
report: runs/golden-20261027-184024.json
gate [release=1.0.0]: pass 18/18 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
```
**쉽게 말하면**
- VPN p50 · p95: 1.0.0 1.23 · 1.42초, 2.0.0 1.12 · 1.24초. 지시문이 같은(1.1.0과) 상태에서 모델만 바뀐 2.0.0이 조금 빠릅니다.
- 1.0.0 golden 3번 반복 18/18 (p95 1.5초). 흔들리지 않으니 이 게이트의 FAIL은 믿을 수 있습니다. `--max-p95 3`은 약 두 배 여유.
### 18 · Lab 02 과제 1 · 강한 golden → 1.0.0 · 1.1.0 PASS, 1.2.0 FAIL
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `cases/my-golden.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab02-challenge-golden.json`):
```text
cases/golden.json 을 cases/my-golden.json 으로 복사해 expect 만 강하게 바꿔 줘. 워크플로우는 바꾸지 않는다 .
질문마다 답에 꼭 있어야 할 낱말을 contains 로 (아래는 내가 FAQ · 접수 건 데이터에서 정한 것):
VPN 인증서 / VPN 업데이트 · 최신 / 비밀번호 비밀번호 찾기 · 휴대폰 / 프린터 대기열 · 전원 / 메일함 보관함 · 삭제 /
T-1002 처리 중 / T-1002 10월 20일 · 20일 / T-1001 처리 완료 · 완료.
프린터 질문에는 " IT 포털을 확인 · 포털을 참고 " 같은 떠넘기기 말이 없어야 한다 (not_contains).
기대: gate --set release=1.0.0 · 1.1.0 PASS, release= 1.2.0 FAIL.
```
실행한 명령:
```bash
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.0.0 --max-p95 3
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.1.0 --max-p95 3
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.2.0 --max-p95 3
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.0.0 --max-p95 3
```
출력 (그대로):
```text
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.2s
✔ VPN · 순서도 (업데이트) · 1.3s
✔ 비밀번호 · 해결 방법 · 1.4s
✔ 프린터 · 해결 방법 · 1.1s
✔ 메일함 · 해결 방법 · 1.2s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.1s
✔ 포털로 떠넘기지 않기 · 1.1s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184034.json
gate [release=1.0.0]: pass 9/9 (100%) · p95 1.4s → PASS
✔ gate record → /gates/helpdesk: 1.0.0 promote
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.0s
✔ VPN · 순서도 (업데이트) · 1.7s
✔ 비밀번호 · 해결 방법 · 1.1s
✔ 프린터 · 해결 방법 · 1.3s
✔ 메일함 · 해결 방법 · 1.0s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.0s
✔ 포털로 떠넘기지 않기 · 1.2s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184045.json
gate [release=1.1.0]: pass 9/9 (100%) · p95 1.7s → PASS
✔ gate record → /gates/helpdesk: 1.1.0 promote
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✘ VPN · 해결 방법 · 0.9s · answer has none of ['인증서']
✘ VPN · 순서도 (업데이트) · 0.7s · answer has none of ['업데이트', '최신']
✘ 비밀번호 · 해결 방법 · 0.9s · answer has none of ['비밀번호 찾기', '휴대폰']
✘ 프린터 · 해결 방법 · 0.9s · answer has none of ['대기열', '전원']
✘ 메일함 · 해결 방법 · 0.9s · answer has none of ['보관함', '삭제']
✘ 접수 처리 중 · 상태와 예정일 · 1.0s · answer has none of ['처리 중']
✘ 접수 처리 중 · 예정일 · 0.9s · answer has none of ['10월 20일', '20일']
✘ 접수 처리 완료 · 1.3s · answer has none of ['처리 완료', '완료']
✔ 포털로 떠넘기지 않기 · 0.7s
Passed 1/9
report: runs/74-lab02-challenge-golden-20261027-184053.json
gate [release=1.2.0]: pass 1/9 (11%) · p95 1.3s → FAIL · pass rate 11% < 100%
✔ gate record → /gates/helpdesk: 1.2.0 rollback
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.1s
✔ VPN · 순서도 (업데이트) · 1.3s
✔ 비밀번호 · 해결 방법 · 1.3s
✔ 프린터 · 해결 방법 · 1.3s
✔ 메일함 · 해결 방법 · 1.3s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.1s
✔ 접수 처리 완료 · 1.2s
✔ 포털로 떠넘기지 않기 · 1.1s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184104.json
gate [release=2.0.0]: pass 9/9 (100%) · p95 1.3s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
```
**쉽게 말하면**
- 1.0.0 9/9 · 1.1.0 9/9 PASS, **1.2.0 1/9 FAIL**, 2.0.0 9/9 PASS (p95 1.3초).
- 1.2.0이 통과한 하나는 "포털로 떠넘기지 않기"(`not_contains`)입니다. 답이 "IT 포털**에서** … 확인해 주세요"라 "IT 포털을 확인" · "포털을 참고"에 걸리지 않았습니다. 막은 것은 해결 낱말(`contains`) 여덟 케이스.
- 테스트만 고쳤고 워크플로우는 그대로입니다. 예상한 결과.
### 19 · Lab 02 과제 2 하기 전 → `lab02-challenge2` 4/5, 2.1.0 게이트 PASS
실행한 명령:
```bash
uv run tools/flow.py test cases/lab02-challenge2.json -v
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.1.0 --max-p95 3
```
출력 (그대로):
```text
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✘ 망가진 릴리스 2.1.0 · 400 release broken · 1.3s · status 200 (expected 400); error = null (expected release broken); prompt_version = null (expected p9)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "2.1.0",
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ 평소 · 1.1.0은 저장소의 p2 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 운영(active 1.0.0)은 그대로 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 4/5
report: runs/lab02-challenge2-20261027-184109.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✔ VPN · 해결 방법 · 1.2s
✔ VPN · 순서도 (업데이트) · 1.2s
✔ 비밀번호 · 해결 방법 · 1.5s
✔ 프린터 · 해결 방법 · 1.1s
✔ 메일함 · 해결 방법 · 1.3s
✔ 접수 처리 중 · 상태와 예정일 · 1.0s
✔ 접수 처리 중 · 예정일 · 1.0s
✔ 접수 처리 완료 · 1.0s
✔ 포털로 떠넘기지 않기 · 1.0s
Passed 9/9
report: runs/74-lab02-challenge-golden-20261027-184120.json
gate [release=2.1.0]: pass 9/9 (100%) · p95 1.5s → PASS
✔ gate record → /gates/helpdesk: 2.1.0 promote
```
**쉽게 말하면**
- 2.1.0(p9) 케이스 ✘: 400이어야 하는데 **200**으로 답했습니다. 저장소가 404를 줬지만 Lab 01 과제 2의 기본 지시문이 받아 버렸습니다.
- 그래서 강한 golden으로 2.1.0을 게이트하면 **9/9 PASS**. 망가진 릴리스가 게이트를 통과합니다 (위험!).
- 나머지 넷(평소 1.1.0, 저장소 고장 1.1.0 → fallback, 고장 중 없는 릴리스 400, 운영 1.0.0)은 ✔. 예상한 결과.
### 20 · Lab 02 과제 2 · 404는 400 release broken → 5/5, 2.1.0 게이트 FAIL
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab02-challenge2-broken.json`):
```text
my-work/helpdesk.json 의 Get prompt 에 options.response.response 로 neverError true, fullResponse true 를 더해 줘 (timeout 2000 · onError 는 그대로).
성공 쪽 Registry prompt 를 Code 로 바꿔: statusCode 200 이면 {state: 'registry', text: body.text}, 404 이면 {state: 'broken'}, 그 밖이면 {state: 'fallback', text: 기본 지시문}.
Prompt broken?(If, state == 'broken') true → Reject broken(HTTP 400 , {error: 'release broken', release, prompt_version}), false → Search FAQ.
prompt_source 는 Registry prompt 의 state 가 'registry' 면 registry, 아니면 fallback. 실패 쪽(시간 초과) Fallback prompt 는 그대로 둔다 .
기대 결과 cases/lab02-challenge2.json, cases/lab02.json.
```
실행한 명령:
```bash
uv run tools/flow.py push answers/74-lab02-challenge2-broken.json
uv run tools/flow.py test cases/lab02-challenge2.json -v
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=2.1.0 --max-p95 3
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + release API (reference)' (w2caac7c1a6981b8): it used the same webhook path
✔ pushed '74 IT helpdesk + broken release check (answer)' (id w86d00ad9781296e)
POST http://localhost:5678/webhook/my-helpdesk
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 망가진 릴리스 2.1.0 · 400 release broken · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "2.1.0"
}
← response HTTP 400
{
"error": "release broken",
"release": "2.1.0",
"prompt_version": "p9"
}
✔ 평소 · 1.1.0은 저장소의 p2 · 2.0s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "1.1.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "fallback"
}
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "error"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "error",
"slow_seconds": 8.0
}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호",
"release": "9.9.9"
}
← response HTTP 400
{
"error": "unknown release",
"release": "9.9.9",
"known": [
"1.0.0",
"1.1.0",
"1.2.0",
"2.0.0",
"2.1.0"
]
}
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok"
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ 운영(active 1.0.0)은 그대로 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 5/5
report: runs/lab02-challenge2-20261027-184139.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
→ request POST http://localhost:8800/admin/fault
{
"prompts": "ok",
"slow_seconds": 8
}
← response HTTP 200
{
"faq": "ok",
"tickets": "ok",
"prompts": "ok",
"slow_seconds": 8.0
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 9 case(s) × 1
✘ VPN · 해결 방법 · 0.1s · status 400 (expected 200); route = null (expected 자동 답변); answer has none of ['인증서']
✘ VPN · 순서도 (업데이트) · 0.1s · status 400 (expected 200); answer has none of ['업데이트', '최신']
✘ 비밀번호 · 해결 방법 · 0.1s · status 400 (expected 200); answer has none of ['비밀번호 찾기', '휴대폰']
✘ 프린터 · 해결 방법 · 0.0s · status 400 (expected 200); answer has none of ['대기열', '전원']
✘ 메일함 · 해결 방법 · 0.1s · status 400 (expected 200); answer has none of ['보관함', '삭제']
✘ 접수 처리 중 · 상태와 예정일 · 0.1s · status 400 (expected 200); answer has none of ['처리 중']
✘ 접수 처리 중 · 예정일 · 0.1s · status 400 (expected 200); answer has none of ['10월 20일', '20일']
✘ 접수 처리 완료 · 0.0s · status 400 (expected 200); answer has none of ['처리 완료', '완료']
✘ 포털로 떠넘기지 않기 · 0.0s · status 400 (expected 200)
Passed 0/9
report: runs/74-lab02-challenge-golden-20261027-184140.json
gate [release=2.1.0]: pass 0/9 (0%) · p95 0.0s → FAIL · pass rate 0% < 100%
✔ gate record → /gates/helpdesk: 2.1.0 rollback
```
**쉽게 말하면**
- 2.1.0은 400 `{error: release broken, release 2.1.0, prompt_version p9}`. 저장소 장애(500)는 여전히 기본 지시문.
- 같은 강한 golden으로 2.1.0을 게이트하면 **0/9 FAIL** (모두 400이라 p95는 0.0초로 나옴). 설정 오류를 크게 실패하게 하자 게이트가 잡았습니다.
- 5/5, 예상한 결과.
### 21 · 과제 2 뒤 `lab02` 5/5 · `lab01-challenge2` 4/4
실행한 명령:
```bash
uv run tools/flow.py test cases/lab02.json
uv run tools/flow.py test cases/lab01-challenge2.json
```
출력 (그대로):
```text
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 1.3s
✔ 후보 시험 · 1.1.0 고정 · 1.1s
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.2s
✔ 없는 릴리스 · 0.1s
✔ 빈 질문 · 0.1s
Passed 5/5
report: runs/lab02-20261027-184144.json
POST http://localhost:5678/webhook/my-helpdesk · 4 case(s) × 1
✔ 평소 · 저장소에서 · 1.2s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 기본 지시문으로 답 · 1.2s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "slow", "slow_seconds": 8.0}
✔ 저장소 느림 · 오래 기다리지 않음 · 3.0s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 되돌린 뒤 · 다시 저장소에서 · 1.0s
Passed 4/4
report: runs/lab01-challenge2-20261027-184150.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
```
**쉽게 말하면**
- 앞의 테스트가 그대로 통과. 응답 코드로 가르는 길을 더해도 고정 · 없는 릴리스 · 기본 지시문은 그대로입니다.
## Lab 03 · 배포 · 되돌리기, 카나리, 릴리스 점검
### 22 · Lab 03 예제 1 · 배포 · 되돌리기 → `lab03` 2/2
실행한 명령:
```bash
uv run tools/flow.py release helpdesk 1.1.0
uv run tools/flow.py call my-helpdesk requests/faq-vpn.json -v
uv run tools/flow.py release helpdesk 1.0.0
uv run tools/flow.py call my-helpdesk requests/faq-vpn.json -v
uv run tools/flow.py test cases/lab03.json -v
```
출력 (그대로):
```text
✔ helpdesk: active 1.1.0 · no canary
1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
* 1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
HTTP 200 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
HTTP 200 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 2 case(s) × 1
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.1.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.1.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 배포 · 1.1.0을 active로 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류가 발생하면 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 담당자가 도와드리겠습니다. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.0.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 되돌리기 · 1.0.0 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 2/2
report: runs/lab03-20261027-184155.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
```
**쉽게 말하면**
- `release helpdesk 1.1.0` 뒤 다음 요청이 1.1.0 · `[FAQ-003]`, `release helpdesk 1.0.0` 뒤 다음 요청이 1.0.0 · 출처 번호 없음.
- push도 n8n 재시작도 없었습니다. 값 하나. 2/2, 예상한 결과.
### 23 · Lab 03 예제 2 하기 전 → `lab03-canary` 3/6
실행한 명령:
```bash
uv run tools/flow.py release helpdesk --canary 1.1.0 --percent 50
uv run tools/flow.py test cases/lab03-canary.json -v
```
출력 (그대로):
```text
✔ helpdesk: active 1.0.0 · canary 1.1.0 50%
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
c 1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
canary users: 최유진(0), 정우성(42)
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.1.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.1.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.1.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✘ 최유진 (bucket 0) → 카나리 · 1.1s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✘ 정우성 (bucket 42) → 카나리 · 1.3s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 (bucket 56) → 운영 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건은 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 (bucket 80) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 고정이 카나리보다 먼저 · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✘ 같은 사람은 늘 같은 쪽 · 1.2s · version.release = 1.0.0 (expected 1.1.0); version.channel = stable (expected canary)
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 3/6
report: runs/lab03-canary-20261027-184202.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
```
**쉽게 말하면**
- 카나리 1.1.0 50%를 켰지만 최유진 · 정우성(카나리 대상)도 1.0.0 · `stable`로 답했습니다. 워크플로우가 아직 카나리를 보지 않습니다.
- 운영 쪽 둘과 "고정이 카나리보다 먼저"는 ✔ (3/6). 예상한 결과.
### 24 · Lab 03 예제 2 · 카나리 → 6/6, `lab02` 5/5, `lab02-challenge2` 5/5
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/helpdesk.json` · 이 기록에서 push한 같은 역할의 정답 `reference/lab03-example2-canary.json`):
```text
my-work/helpdesk.json 의 Pick release 에 카나리를 더해 줘.
bucket = user 이름 글자의 codePointAt 합 % 100 . 본문 release 가 없고 canary 가 있고 bucket < canary.percent 이면
canary.release 를 쓰고 channel ' canary '. 순서는 고정 > 카나리 > active ( 고정(본문 release) 가 맨 먼저).
기대 결과 cases/lab03-canary.json, cases/lab02.json.
```
실행한 명령:
```bash
uv run tools/flow.py push reference/lab03-example2-canary.json
uv run tools/flow.py test cases/lab03-canary.json -v
uv run tools/flow.py test cases/lab02.json
uv run tools/flow.py test cases/lab02-challenge2.json
uv run tools/flow.py release helpdesk --reset
```
출력 (그대로):
```text
! unpublished '74 IT helpdesk + broken release check (answer)' (w86d00ad9781296e): it used the same webhook path
✔ pushed '74 IT helpdesk + canary (reference)' (id w2e1e5fc96604a25)
POST http://localhost:5678/webhook/my-helpdesk
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.1.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.1.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.1.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ 최유진 (bucket 0) → 카나리 · 2.8s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 정우성 (bucket 42) → 카나리 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하면 문의해 주세요. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 이준호 (bucket 56) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주세요.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 (bucket 80) → 운영 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "메일함 용량이 꽉 찼대요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "메일함 용량이 초과된 경우, 오래된 메일과 큰 첨부 파일을 보관함으로 옮기거나 삭제하여 용량을 확보하시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-005"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 고정이 카나리보다 먼저 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진",
"release": "1.0.0"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "pinned",
"prompt_source": "registry"
}
}
✔ 같은 사람은 늘 같은 쪽 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
Passed 6/6
report: runs/lab03-canary-20261027-184226.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 운영 중인 릴리스 (active) · 1.6s
✔ 후보 시험 · 1.1.0 고정 · 1.3s
✔ 후보 시험 · 2.0.0 (작은 모델) · 1.4s
✔ 없는 릴리스 · 0.1s
✔ 빈 질문 · 0.1s
Passed 5/5
report: runs/lab02-20261027-184231.json
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
POST http://localhost:5678/webhook/my-helpdesk · 5 case(s) × 1
✔ 망가진 릴리스 2.1.0 · 400 release broken · 0.1s
✔ 평소 · 1.1.0은 저장소의 p2 · 1.2s
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 1.1.0도 기본 지시문으로 · 1.2s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ before: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "error", "slow_seconds": 8.0}
✔ 저장소 고장 · 없는 릴리스는 그대로 400 · 0.0s
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ 운영(active 1.0.0)은 그대로 · 1.1s
Passed 5/5
report: runs/lab02-challenge2-20261027-184235.json
✔ after: POST http://localhost:8800/admin/fault · HTTP 200 · 0.0s · {"faq": "ok", "tickets": "ok", "prompts": "ok", "slow_seconds": 8.0}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
```
**쉽게 말하면**
- 최유진(0) · 정우성(42)은 1.1.0 · `canary`, 이준호(56) · 김민지(80)는 1.0.0 · `stable`. 같은 사람은 다시 물어도 같은 쪽. 고정은 카나리보다 먼저.
- 앞의 테스트도 그대로. 끝에 `--reset`. 예상한 결과.
### 25 · Lab 03 예제 3 · 나쁜 카나리(1.2.0 50%) → 4/6
실행한 명령:
```bash
uv run tools/flow.py test cases/lab03-watch.json -v
uv run tools/flow.py release helpdesk
```
출력 (그대로):
```text
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": {"release": "1.2.0", "percent": 50}, "require_gate": false, "releases"
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "1.2.0",
"percent": 50
}
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": {
"release": "1.2.0",
"percent": 50
},
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✘ 최유진 · 프린터 · 0.9s · answer has none of ['대기열']
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✘ 정우성 · 프린터 · 0.9s · answer has none of ['대기열']
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "IT 포털에서 프린터 문제 해결 방법을 확인해 주세요.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.2.0",
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"channel": "canary",
"prompt_source": "registry"
}
}
✔ 한지훈 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 박서연 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 · 프린터 · 1.1s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 4/6
report: runs/lab03-watch-20261027-184241.json
✔ helpdesk: active 1.0.0 · canary 1.2.0 50%
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
c 1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
canary users: 최유진(0), 정우성(42)
```
**쉽게 말하면**
- 카나리 둘(최유진 · 정우성)은 "대기열"이 없는 한 문장 답(1.2.0)이라 ✘, 운영 넷은 ✔.
- `release helpdesk`가 카나리 1.2.0 50%를 보여 줍니다. 전원 배포였다면 여섯 명 모두 ✘였을 것. 예상한 결과.
### 26 · 예제 3 · `--no-canary` 뒤 → 6/6
실행한 명령:
```bash
uv run tools/flow.py release helpdesk --no-canary
uv run tools/flow.py test cases/lab03-watch.json --no-before -v
uv run tools/flow.py release helpdesk --reset
```
출력 (그대로):
```text
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ 최유진 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 정우성 · 프린터 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "정우성"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 접수된 문의 건은 없으며, 추가 도움이 필요하시면 담당자가 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 한지훈 · 프린터 · 1.0s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "한지훈"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 이준호 · 프린터 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 박서연 · 프린터 · 1.2s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "박서연"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ 김민지 · 프린터 · 1.4s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "김민지"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 현재 접수된 관련 문의는 없습니다. 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 6/6
report: runs/lab03-watch-20261027-184249.json
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스 gate promote 9/9
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 9/9
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 1/9
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 9/9
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요) gate rollback 0/9
```
**쉽게 말하면**
- 카나리를 끄고 `--no-before`로 다시 재니 여섯 명 모두 ✔. active는 처음부터 1.0.0이라 운영 쪽은 아무 일도 없었습니다.
### 27 · Lab 03 과제 1 · 릴리스 점검 → `lab03-challenge` 3/3
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/release-check.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab03-challenge-release-check.json`):
```text
my-work/release-check.json 을 새로 만들어 줘. 웹훅 my-release-check , 본문 release 가 없으면 400.
Golden questions(Code): cases/my-golden.json 의 질문 6개(question · user · expect 낱말)를 항목 6개로, 각 항목에 release.
Ask helpdesk(HTTP POST http://localhost:5678/webhook/my-helpdesk): 항목마다 {question, user, release } 로 (neverError, timeout 30000).
Judge(Code): 답에 expect 낱말 중 하나가 있고 답의 version.release 가 점검한 release 와 같으면 이면 통과.
결과 {release, passed, total, failed, seconds, verdict}: 모두 통과면 ' promote ', 아니면 'rollback'.
기대 결과 cases/lab03-challenge.json.
```
실행한 명령:
```bash
uv run tools/flow.py push answers/74-lab03-challenge-release-check.json
uv run tools/flow.py test cases/lab03-challenge.json -v
uv run tools/flow.py call my-release-check '{"release": "2.0.0"}' -v
```
출력 (그대로):
```text
! unpublished '74 Release check + gate record (answer)' (w9589252e9498695): it used the same webhook path
✔ pushed '74 Release check (answer)' (id w50ae3687e1cc859)
POST http://localhost:5678/webhook/my-release-check
POST http://localhost:5678/webhook/my-release-check · 3 case(s) × 1
✔ 1.1.0 · 내보내도 됨 · 3.1s
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 2,
"verdict": "promote"
}
✔ 1.2.0 · 되돌림 · 1.6s
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 1.6,
"verdict": "rollback"
}
✔ 릴리스 없음 · 0.0s
→ request POST /webhook/my-release-check
{}
← response HTTP 400
{
"error": "release is required"
}
Passed 3/3
report: runs/lab03-challenge-20261027-184308.json
HTTP 200 · 1.9s
→ request POST /webhook/my-release-check
{
"release": "2.0.0"
}
← response HTTP 200
{
"release": "2.0.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 1.8,
"verdict": "promote"
}
```
**쉽게 말하면**
- 1.1.0 → `promote` 6/6 (3.1초), 1.2.0 → `rollback` 0/6 (1.6초), `release` 없으면 400.
- `call my-release-check '{"release": "2.0.0"}'` → `promote` 6/6 (1.8초). 3/3, 예상한 결과.
### 28 · Lab 03 과제 2 하기 전 → 점검 둘 ✘, 1.1.0 배포가 409로 멈춤
실행한 명령:
```bash
uv run tools/flow.py test cases/lab03-challenge2.json -v
```
출력 (그대로):
```text
✔ before: POST http://localhost:8800/gates/helpdesk/clear · HTTP 200 · 0.0s · {"cleared": 19}
→ request POST http://localhost:8800/gates/helpdesk/clear
{}
← response HTTP 200
{
"cleared": 19
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/policy · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk/policy
{
"require_gate": true
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-release-check · 5 case(s) × 1
✘ 1.2.0 점검 · 기록 · 24.2s · recorded = null (expected true)
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 24.1,
"verdict": "rollback"
}
✘ 1.1.0 점검 · 기록 · 7.9s · recorded = null (expected true)
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 7.9,
"verdict": "promote"
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.2.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.2.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.2.0",
"last_gate": null
}
✔ 점검에 떨어진 1.2.0은 배포 거부 (409) · 운영은 1.0.0 · 7.9s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 관련 문의는 없으며, 추가 도움이 필요하시면 담당 직원이 도와드리겠습니다.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✘ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.1.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.1.0",
"last_gate": null
}
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✘ the 'before' step failed (http://localhost:8800/releases/helpdesk). Is it pushed / is the data API the day 74 version?
```
**쉽게 말하면**
- 점검 두 케이스 ✘: 결과에 `recorded`가 없습니다 (판단은 맞게 냄).
- 1.2.0 배포 거부 케이스는 ✔ (409). 그런데 기록이 **아예 없어서** 거부된 것입니다.
- 다음 케이스의 `before`(1.1.0 배포)가 **409 `not gated` · `last_gate: null`**로 실패해 테스트가 멈췄습니다 (그래서 이 단계는 보고서 `.json`이 없음). 점검이 promote라고 말했지만 남기지 않았기 때문. 예상한 결과.
- 이번 실행에서 1.2.0 점검이 24.2초 걸렸습니다 (OpenAI 응답이 이때 느렸음, 다음 단계에서는 10.6초).
### 29 · Lab 03 과제 2 · 점검 기록 · 지난 것만 배포 → 5/5
codex에 입력하는 프롬프트 (터미널 ①, 학생이 만들 파일 `my-work/release-check.json` · 이 기록에서 push한 같은 역할의 정답 `answers/74-lab03-challenge2-gated.json`):
```text
my-work/release-check.json 의 Judge 와 Send verdict 사이에 Record gate(HTTP POST http://data-api:8800/ gates/helpdesk ) 를 넣어 줘.
본문: {release, verdict, passed, total, failed, seconds, source: 'my-release-check'} (Judge 의 결과에서).
Record gate 는 timeout 3000, neverError, onError continueRegularOutput (기록이 실패해도 판단은 보냄).
Send verdict 는 Judge 의 결과에 recorded: (기록 응답의 stored 가 true 인지) 를 더해 보낸다.
워크플로우에서 /releases 에는 POST 하지 않는다 . 기대 결과 cases/lab03-challenge2.json, cases/lab03-challenge.json.
```
실행한 명령:
```bash
uv run tools/flow.py push answers/74-lab03-challenge2-gated.json
uv run tools/flow.py test cases/lab03-challenge2.json -v
uv run tools/flow.py release helpdesk
```
출력 (그대로):
```text
! unpublished '74 Release check (answer)' (w50ae3687e1cc859): it used the same webhook path
✔ pushed '74 Release check + gate record (answer)' (id w9589252e9498695)
POST http://localhost:5678/webhook/my-release-check
✔ before: POST http://localhost:8800/gates/helpdesk/clear · HTTP 200 · 0.0s · {"cleared": 0}
→ request POST http://localhost:8800/gates/helpdesk/clear
{}
← response HTTP 200
{
"cleared": 0
}
✔ before: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ before: POST http://localhost:8800/releases/helpdesk/policy · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk/policy
{
"require_gate": true
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
POST http://localhost:5678/webhook/my-release-check · 5 case(s) × 1
✔ 1.2.0 점검 · 기록 · 10.6s
→ request POST /webhook/my-release-check
{
"release": "1.2.0"
}
← response HTTP 200
{
"release": "1.2.0",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 9.5,
"verdict": "rollback",
"recorded": true
}
✔ 1.1.0 점검 · 기록 · 1.9s
→ request POST /webhook/my-release-check
{
"release": "1.1.0"
}
← response HTTP 200
{
"release": "1.1.0",
"passed": 6,
"total": 6,
"failed": [],
"seconds": 1.7,
"verdict": "promote",
"recorded": true
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "1.2.0", "last_gate": {"t": 1793094252.829, "release": "1.2.0", "verdict": "rollback",
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.2.0"
}
← response HTTP 409
{
"error": "not gated",
"release": "1.2.0",
"last_gate": {
"t": 1793094252.829,
"release": "1.2.0",
"verdict": "rollback",
"passed": 0,
"total": 6,
"failed": [
"VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"비밀번호를 잊어버렸어요. 재설정은 어떻게 하나요?",
"프린터 출력이 안 돼요",
"메일함 용량이 꽉 찼대요",
"T-1002 처리 상태 알려 주세요",
"T-1001 어떻게 됐나요?"
],
"seconds": 9.5,
"source": "my-release-check"
}
}
✔ 점검에 떨어진 1.2.0은 배포 거부 (409) · 운영은 1.0.0 · 7.7s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 현재 접수된 문의 건이 없어 추가 지원이 필요하시면 IT 담당자에게 문의해 주십시오.",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.0.0",
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.1.0", "canary": null, "require_gate": true, "releases": {"1.0.0": {"prompt_version": "
→ request POST http://localhost:8800/releases/helpdesk
{
"active": "1.1.0"
}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.1.0",
"canary": null,
"require_gate": true,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ 점검을 지난 1.1.0은 배포 · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "VPN 접속이 자꾸 끊겨요. 어떻게 하나요?",
"user": "이준호"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "VPN 접속 오류는 VPN 클라이언트를 최신 버전으로 업데이트하고, 인증서 만료 여부를 확인한 뒤 다시 접속하시기 바랍니다. 추가 도움이 필요하시면 문의해 주세요. [FAQ-003]",
"sources": [
"FAQ-003"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
✔ before: POST http://localhost:8800/releases/helpdesk · HTTP 409 · 0.0s · {"error": "not gated", "release": "2.0.0", "last_gate": null}
→ request POST http://localhost:8800/releases/helpdesk
{
"canary": {
"release": "2.0.0",
"percent": 50
}
}
← response HTTP 409
{
"error": "not gated",
"release": "2.0.0",
"last_gate": null
}
✔ 점검 안 한 2.0.0은 카나리도 거부 (409) · 1.3s
→ request POST /webhook/my-helpdesk
{
"question": "프린터 출력이 안 돼요",
"user": "최유진"
}
← response HTTP 200
{
"route": "자동 답변",
"answer": "프린터 전원과 네트워크 연결을 확인하고, 인쇄 대기열을 비운 뒤 다시 출력해 보시기 바랍니다. 추가 도움이 필요하면 담당 직원이 도와드리겠습니다. [FAQ-004]",
"sources": [
"FAQ-004"
],
"version": {
"release": "1.1.0",
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"channel": "stable",
"prompt_source": "registry"
}
}
Passed 5/5
report: runs/lab03-challenge2-20261027-184425.json
✔ after: POST http://localhost:8800/releases/helpdesk/reset · HTTP 200 · 0.0s · {"name": "helpdesk", "active": "1.0.0", "canary": null, "require_gate": false, "releases": {"1.0.0": {"prompt_version":
→ request POST http://localhost:8800/releases/helpdesk/reset
{}
← response HTTP 200
{
"name": "helpdesk",
"active": "1.0.0",
"canary": null,
"require_gate": false,
"releases": {
"1.0.0": {
"prompt_version": "p1",
"model": "gpt-4.1-mini",
"note": "첫 릴리스"
},
"1.1.0": {
"prompt_version": "p2",
"model": "gpt-4.1-mini",
"note": "FAQ 답 끝에 출처 번호"
},
"1.2.0": {
"prompt_version": "p3",
"model": "gpt-4.1-mini",
"note": "답을 한 문장으로 짧게"
},
"2.0.0": {
"prompt_version": "p2",
"model": "gpt-5.4-nano",
"note": "더 작은 모델 (비용)"
},
"2.1.0": {
"prompt_version": "p9",
"model": "gpt-4.1-mini",
"note": "출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)"
}
}
}
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용)
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
```
**쉽게 말하면**
- 1.2.0 점검 → `rollback` · `recorded` true, 1.1.0 → `promote` · `recorded` true.
- 정책이 켜진 상태에서 1.2.0 배포는 409(마지막 기록 rollback), 1.1.0 배포는 200 → 다음 답이 1.1.0 · `stable`, 점검 안 한 2.0.0 카나리는 409.
- 5/5. `after`가 reset해서 끝에 active 1.0.0 · 정책 꺼짐. 예상한 결과.
### 30 · 정책을 켜고 `flow.py gate` 기록으로 배포 · doctor
실행한 명령:
```bash
uv run tools/flow.py release helpdesk --require-gate on
uv run tools/flow.py release helpdesk 2.1.0
uv run tools/flow.py gate cases/golden.json --set release=2.0.0 --max-p95 3
uv run tools/flow.py release helpdesk 2.0.0
uv run tools/flow.py release helpdesk --reset
uv run tools/flow.py doctor
```
출력 (그대로):
```text
✔ helpdesk: active 1.0.0 · no canary · deploy needs a passing gate
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용)
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
✘ refused: 2.1.0 has no passing gate record (never gated)
→ run the gate (flow.py gate ... --set release=<version>) or my-release-check first
POST http://localhost:5678/webhook/my-helpdesk · 6 case(s) × 1
✔ VPN · 1.1s
✔ 비밀번호 · 1.1s
✔ 프린터 · 1.3s
✔ 메일함 · 1.4s
✔ 접수 처리 중 · 1.1s
✔ 접수 처리 완료 · 1.1s
Passed 6/6
report: runs/golden-20261027-184432.json
gate [release=2.0.0]: pass 6/6 (100%) · p95 1.4s → PASS
✔ gate record → /gates/helpdesk: 2.0.0 promote
✔ helpdesk: active 2.0.0 · no canary · deploy needs a passing gate
1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
* 2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 6/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
✔ helpdesk: active 1.0.0 · no canary
* 1.0.0 prompt p1 model gpt-4.1-mini 첫 릴리스
1.1.0 prompt p2 model gpt-4.1-mini FAQ 답 끝에 출처 번호 gate promote 6/6
1.2.0 prompt p3 model gpt-4.1-mini 답을 한 문장으로 짧게 gate rollback 0/6
2.0.0 prompt p2 model gpt-5.4-nano 더 작은 모델 (비용) gate promote 6/6
2.1.0 prompt p9 model gpt-4.1-mini 출처 번호 · 존댓말 다듬기 (지시문 이름 확인 필요)
! n8n install folder /Users/mcchae/day65-n8n
→ day 65 SETUP-N8N 5단계 (get.n8n.io 설치). 다른 곳에 설치했다면 Last3Mon/65/n8n/.env 에 N8N_HOME=<폴더>
✔ n8n container day65-n8n-n8n-1
✔ n8n http://localhost:5678
✔ data API http://localhost:8800
✔ releases /releases/helpdesk · gates /gates/helpdesk (day 74 data API)
✔ n8n → http://data-api:8800
✔ credential 'OpenAI (.env)' (openAiCourse0001)
✔ node catalog (for check)
✔ CLI coding agent: codex, claude, agy
ready
```
**쉽게 말하면**
- 정책을 켠 뒤 2.1.0 배포: `refused … (never gated)`.
- `gate cases/golden.json --set release=2.0.0` PASS → `/gates/helpdesk`에 2.0.0 promote 기록 → `release helpdesk 2.0.0`이 됩니다. 노트북의 게이트 기록도 정책이 인정합니다.
- `--reset` 뒤 `doctor` → `ready`. 내일을 위해 처음 상태로 돌려 두었습니다.
## 다시 돌려 보기
강사 노트북에서 같은 순서를 다시 돌리려면 (강사 저장소 `~/work/AIO-01D4/Last3Mon/74`, 정답 파일이 있는 곳):
```bash
cd ~/work/AIO-01D4/Last3Mon/74
uv run tools/flow.py release helpdesk --reset
uv run tools/flow.py push reference/helpdesk-v1.json reference/helpdesk-v2.json
uv run tools/flow.py push answers/74-lab02-challenge2-broken.json && uv run tools/flow.py test cases/lab02-challenge2.json -v
uv run tools/flow.py gate answers/74-lab02-challenge-golden.json --set release=1.2.0 --max-p95 3 # FAIL
uv run tools/flow.py push reference/lab03-example2-canary.json answers/74-lab03-challenge2-gated.json
uv run tools/flow.py test cases/lab03-canary.json -v
uv run tools/flow.py test cases/lab03-challenge2.json -v
uv run tools/flow.py release helpdesk --reset && uv run tools/flow.py doctor
```
- 결과의 ✔ · ✘(판정)는 같게 나와야 합니다. LLM 문장과 시간은 다를 수 있습니다. 특히 1.2.0 점검 · 게이트 시간은 OpenAI 응답에 따라 1.6초 ~ 24초까지 봤습니다.