Skill: fdk-uat
Vấn đề nó giải
medic --ci và fresh-install-smoke --local cài từ working-tree qua file://. Chúng chứng minh "code trong máy tôi lành", không chứng minh "người lạ curl về là chạy được". Hai thứ khác nhau: tarball thiếu file, .gitignore nuốt mất thứ cần ship, skill hỏng frontmatter bị CLI bỏ qua trong im lặng, engine global không refresh — mọi lỗi này chỉ lộ ra trên đường remote thật.
Đường remote chỉ tồn tại sau khi push — nhưng không nhất thiết là push lên nhánh chính. Một nhánh tạm cũng có raw URL. Nghịch lý con-gà-quả-trứng biến mất, với một điều kiện: phải override cả ba biến nguồn, vì install.sh mặc định kéo skill từ nhánh chính.
Và canary vẫn không đủ: nó chạy với ref khác + biến override, nên mù với chính các giá trị mặc định — mà mặc định là chỗ chứa chuỗi tên nhánh hardcode. Nên phải có pha hai: chạy đúng cái lệnh người mới gõ, không override gì cả.
When to use
- Sắp công bố một bản có năng lực mới (skill mới, rule mới, engine mới).
- Nghi ngờ "doc hứa mà user không nhận được" (lớp lỗi GH#77).
- Sau khi
medic --ciđã xanh — UAT là tầng TRÊN nó, không thay thế nó.
Steps
0. Tiền đề — không được bỏ
python3 fdk/tools/medic.py --ci # PHẢI xanh. Đỏ mà vẫn push là cố ý đẩy rác lên remote.
git status --short # sạch (không untracked lạc)
git log --oneline origin/<nhánh>..HEAD # in ĐÚNG những commit sắp đẩy — ghi lại, đây là thứ sẽ gỡ nếu fail
Ghi lại BASE=$(git rev-parse origin/<nhánh>) — mốc để quay về.
1. Khai năng lực mới của bản này (checklist UAT)
Trước khi push, viết ra cụ thể bản này hứa gì mới. Mỗi dòng phải kiểm được bằng lệnh:
- Skill mới →
~/.claude/skills/<tên>/SKILL.mdtồn tại sau khi cài, và có trongCAPABILITIES.mdcủa dự án mới. - Rule mới → phải CẮN THẬT trong dự án mới (viết một file BAD → bị chặn; file GOOD → qua).
- Engine/tool mới → reachable trong global harness home.
Không khai được thành lệnh thì không tính là năng lực — đó là lời hứa suông.
2. PHA 1 — nhánh CANARY (nhánh chính KHÔNG nhận gì cả)
Đường remote chỉ tồn tại sau khi push — nhưng không nhất thiết phải push lên nhánh chính. Một nhánh tạm cũng có raw URL, và curl không quan tâm nhánh nào.
CANARY="uat/$(date +%y%m%d-%H%M)"
git push origin HEAD:"$CANARY"
Dựng dự án trống, cài từ raw của chính nhánh canary — phải trỏ cả ba biến, không thì nó lặng lẽ kéo nội dung của nhánh chính:
D=~/orca/overstack-uat-$(date +%y%m%d-%H%M)
mkdir -p "$D" && cd "$D" && git init
RAW="https://raw.githubusercontent.com/<owner>/<repo>/$CANARY"
curl -fsSL "$RAW/harness/poc-vendor-neutral/bootstrap.sh" \
| HARNESS_BASE="$RAW/harness/poc-vendor-neutral" \
REPO_RAW="$RAW" \
SKILLS_REF="<owner>/<repo>#$CANARY" \
bash
git add -A && git commit -m "fresh overstack install" # Orca cần ≥1 commit mới nhận repo
Thiếu
SKILLS_REFlà bài UAT thành ẢO GIÁC.install.shmặc định cài skill từ<repo>#<nhánh chính>— nên nếu không override, canary sẽ cài skill của nhánh chính, tức nó chấm bản CŨ rồi báo PASS cho bản MỚI. Một cổng nói dối mà vẫn xanh còn tệ hơn không có cổng.
Chạy checklist bước 3. PASS → sang pha 2. FAIL → xoá canary, nhánh chính chưa hề bị bẩn:
git push origin --delete "$CANARY"
3. UAT — chạy checklist bước 1 trong dự án ĐÓ
Tối thiểu, theo fdk/docs/fresh-install-gate.md:
- 3 trụ có mặt:
harness/poc-vendor-neutral/policy.yaml·.claude/settings.json·.pre-commit-config.yaml·llmwiki/wiki/index.md·CAPABILITIES.md. - Harness cắn thật:
bash harness/poc-vendor-neutral/test-broad.sh→ PASS (validator GOOD-pass / BAD-block). - Năng lực MỚI tới tay — phần riêng của bản này (checklist bước 1). Đây là mục hay bị bỏ nhất, và cũng là mục duy nhất chứng minh bản MỚI có giá trị.
- Orchestration-ready: skill
orchestration/orca-cli/orca-dispatch-referencereachable. - BẮT BUỘC (không phải tuỳ chọn — feedback 2026-07-24, lần thứ 2 bị nhắc vì skip bước này): dựng workspace Orca thật + TỰ VERIFY nó hiện trong app, đừng chỉ chạy filesystem-level. Chỉ curl-cài vào một thư mục tạm rồi test bằng CLI KHÔNG tính là UAT hoàn chỉnh — user không thấy được gì, "không visual = không dùng được". Đây là cổng CỨNG: UAT KHÔNG được coi là PASS nếu bỏ qua bước này.
WT_NAME="uat-$(date +%y%m%d-%H%M)" # tên xác định, dùng để verify ở bước sau
REPO_ID=$(orca repo add --path "$D" --json | python3 -c "import json,sys; print(json.load(sys.stdin)['result']['repo']['id'])")
orca worktree create --repo "id:$REPO_ID" --name "$WT_NAME" --setup skip --no-parent --activate --json
Cạm bẫy đã dính:
orca repo add <path>positional →Unknown command. Repo chưa có commit → Orca không nhận.repo addkhông làm workspace hiện trong app — phảiworktree create.
Cổng verify — chạy NGAY sau lệnh trên, đây không phải bước phụ:
orca worktree list --repo "id:$REPO_ID" --json | python3 -c "
import json, sys
d = json.load(sys.stdin)
names = [w['displayName'] for w in d['result']['worktrees']]
assert '$WT_NAME' in names, f'KHÔNG THẤY worktree tên {\"$WT_NAME\"} — UAT KHÔNG ĐƯỢC TÍNH LÀ PASS. Có: {names}'
print(f'✓ worktree \"$WT_NAME\" hiện thật trong Orca — {names}')
"
Assertion FAIL ở đây → DỪNG, không được tiếp tục báo PASS cho user — quay lại sửa lệnh worktree create (thường do --repo sai định dạng selector hoặc repo add chưa commit) rồi verify lại. Đây chính là gate cấu trúc thay cho việc "phải nhớ tự giác dựng workspace mỗi lần" — nhớ tay đã fail 2 lần liền (2026-07-21, 2026-07-24), giờ ép bằng assert.
4. PHA 2 — main-URL smoke, NGAY SAU merge (canary KHÔNG thay được bước này)
Pha 1 chạy với ref khác và ba biến override → nó mù với chính các giá trị mặc định. Mà mặc định là chỗ chứa chuỗi nhánh hardcode (bootstrap.sh BASE=, install.sh REPO_RAW=, SKILLS_REF=). Chỉ pha này kiểm được đúng cái lệnh người mới thật sự gõ.
git push origin HEAD:<nhánh chính> # merge — bản này ĐÃ qua canary
git push origin --delete "$CANARY" # dọn nhánh tạm
CHỜ CDN PROPAGATE TRƯỚC KHI ĐO — bắt buộc. raw.githubusercontent.com không phục vụ bản mới ngay sau push, và độ trễ không đồng đều giữa các file: đã đo được cảnh engine (bin/llmwiki-validate.py) đã mới trong khi policy.yaml còn cũ → bản cài ra là một thứ lai, và cổng báo ĐỎ GIẢ. Nguy hiểm hơn: cùng cơ chế đó có thể cho XANH GIẢ nếu file cũ tình cờ vẫn qua được test.
Chốt bằng một sentinel — poll tới khi raw trả đúng nội dung mình vừa đẩy:
RAW="https://raw.githubusercontent.com/<owner>/<repo>/<nhánh chính>"
MARK="<một chuỗi CHỈ có ở bản mới>" # vd tên rule/field vừa thêm
# ⚠ GREP-VERIFY MARK trên file bản mới TRƯỚC (grep "$MARK" <file local>) — chọn pattern đoán
# mà không khớp file → vòng lặp chờ hết lượt rồi tưởng raw còn cũ, dù raw ĐÃ mới (p-32).
for i in $(seq 1 30); do
curl -fsSL "$RAW/harness/poc-vendor-neutral/policy.yaml" | grep -q "$MARK" && break
echo " raw còn cũ, chờ CDN… ($i)"; sleep 10
done
Rồi mới chạy đúng lệnh trong README — không override một biến nào:
D2=$(mktemp -d) && cd "$D2" && git init -q
curl -fsSL "$RAW/harness/poc-vendor-neutral/bootstrap.sh" | bash
Rút gọn (1 phút): 3 trụ có mặt · /.claude/skills/`.bash harness/poc-vendor-neutral/test-broad.sh PASS · skill mới reachable trong `
FAIL → gỡ ngay (bước 5). Cửa sổ rủi ro của nhánh chính thu từ "cả bài UAT dài" xuống "một lần smoke ~1 phút".
5. KHÔNG PASS → gỡ commit khỏi remote NGAY
Chỉ dùng khi PHA 2 đỏ (hoặc ai đó đã lỡ push thẳng, bỏ qua canary). Chọn theo tình huống:
# (a) Nhánh chia sẻ / đã có người kéo → REVERT (không viết lại lịch sử):
git revert --no-edit <sha>..HEAD && git push origin <nhánh>
# (b) Nhánh chỉ mình dùng, vừa đẩy xong, chắc chắn chưa ai kéo → RESET:
git reset --hard "$BASE" && git push --force-with-lease origin <nhánh>
--force-with-lease chứ không --force: nếu trong lúc đó có người đẩy lên, lệnh sẽ từ chối thay vì xoá mất việc của họ.
Gỡ xong: ghi lại vì sao fail vào llmwiki/html/fdk-problem-tree.html (node mới, status: open) — thất bại UAT là dữ liệu, không phải chuyện xấu hổ.
6. PASS → chốt
- Dọn dự án UAT (hoặc giữ lại
--keepnếu cần soi). - Ghi kết quả (số đo thật, không phải lời hứa) vào
wiki/log.md. - Bản trên remote coi như đã được nghiệm thu ở tầng "người mới cài".
Rules
- Nhánh chính KHÔNG BAO GIỜ nhận bản chưa qua canary. Pha 1 tồn tại để điều đó đúng.
- Canary KHÔNG thay được main-URL smoke. Nó chạy với ref khác + biến override → mù với các giá trị mặc định, mà mặc định chính là chỗ hardcode tên nhánh. Bỏ pha 2 là bỏ đúng thứ người dùng thật chạm vào.
- Không bao giờ push rồi bỏ mặc. Đã push để UAT thì phải chạy UAT trong cùng phiên. Push xong đi ngủ là để lại một bản chưa nghiệm thu cho người khác kéo về.
- Dọn nhánh canary ở CẢ HAI lối ra (pass và fail). Bước 0 liệt kê
git branch -r | grep uat/để dọn rác của phiên chết giữa chừng. - CDN propagate không đồng đều — CHỜ SENTINEL, đừng đo ngay sau push.
raw.githubusercontentcó thể trả file A đã mới còn file B còn cũ trong cùng một lần cài → bản lai, và cổng báo đỏ giả (hoặc xanh giả, nguy hiểm hơn). Poll một chuỗi CHỈ có ở bản mới cho tới khi raw trả đúng, rồi mới đo. Đo được thật ngày 2026-07-14: engine mới + policy cũ → test-broad 72/74, luật mới không cắn; chờ vài phút rồi chạy lại → 74/74. - Không đếm skill/rule bằng trí nhớ — đếm LIVE trong dự án vừa cài. Con số trên đĩa repo không phải con số tới tay người dùng (đã cháy: doc báo 74 skill, installer giao 67 — 7 skill hỏng frontmatter bị CLI nuốt im lặng).
- Rule mới phải CẮN, không chỉ có mặt. File
policy.yamlcó dòng luật ≠ luật chặn được. Luôn test bằng một file BAD thật. - Trần (ceiling) cố ý: UAT headless không spin được model LLM, nên "
/orchestrationchạy được" chỉ kiểm ở mức tất định (cài đủ + reachable + ping runtime). Live-run bằng model thật vẫn là acceptance làm tay. - Fail thì gỡ, không "để đó sửa sau". Remote là thứ người khác kéo về.
- Workspace Orca hiện thật là BẮT BUỘC, có assert kiểm tên, không phải "tuỳ chọn thấy được bằng mắt". Bị nhắc lặp lại 2 lần (2026-07-21, 2026-07-24) vì trước đây ghi là tuỳ chọn nên bị bỏ qua khi chạy vội — filesystem-only test KHÔNG tính là UAT hoàn chỉnh dù mọi lệnh CLI khác xanh hết.
Origin
- Luồng distill từ
fdk/docs/fresh-install-gate.md(§ "Luồng chuẩn: dựng workspace Orca mới rồi test fresh-install") + yêu cầu của user 2026-07-14: cho phép push trước để test thật, không pass thì gỡ commit khỏi remote. - Hai pha là kết quả của một phản biện của user cùng ngày: "nếu vậy thì nó có test được đúng cái curl của main không?" — không. Canary chứng minh nội dung cài được qua remote; chỉ main-URL smoke chứng minh cái lệnh mặc định chạy được. Kèm phát hiện
install.shhardcode#orcakhông có đường override — thiếu nó thì canary chấm nhầm bản cũ.