Files
aio-01d4-stu/Last3Mon/74/answers/LAB-TEST-RESULT.md
T

4291 lines
163 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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초까지 봤습니다.