راهنمایی میگوید چه کاری مطلوب است؛ enforcement میگوید چه کاری اجازهٔ عبور ندارد. یک Skill خوب چرخهٔ TDD را قابلتکرار میکند، اما بهتنهایی جلوی `rm` یا تغییر assertion را نمیگیرد. Hookهای deterministic باید قبل از tool call و بعد از edit، ورودی و خروجی قابلممیزی بسازند: format، guard و audit. این جداسازی برای `booking-platform` حیاتی است، چون یک جملهٔ خوب نمیتواند جای permission یا sandbox را بگیرد.
## اهداف
پس از این درس میتوانی Skill چندمرحلهای بنویسی؛ pre-tool guard و post-edit formatter/auditor را از prompt جدا کنی؛ تفاوت guidance و enforcement را توضیح دهی؛ و برای هر کنترل تصمیم fail-open یا fail-closed را با ریسک و recovery توجیه کنی.
## جایگاه در چرخه
درس ۲۸ محل ذخیرهٔ راهنما را انتخاب کرد. اکنون در مرز `Implement → Observe → Verify`، همان قرارداد را به workflow و gate تبدیل میکنیم. تست A-04 باید اول Red دیده شود؛ Agent نباید با تغییر expected آن را سبز کند؛ و هیچ hookی نباید deadline یا tenant policy را بهطور مخفی تغییر دهد.
## Skill چیست؟
یک Skill portable معمولاً metadata کوتاه، trigger یا ورودی روشن، مراحل، خروجی، stop condition، ابزارهای مجاز و verification دارد. مراحل را به سه سطح تقسیم کن: **discovery** برای baseline و authority؛ **execution** برای یک تغییر محدود؛ **verification** برای تست و diff. جزئیات syntax هر ابزار در adapter میرود. Skill نباید ادعا کند خودش sandbox است یا approval را دور میزند.
مثال بد
وقتی هر کاری خواستی انجام دادی، formatter را اجرا کن و اگر تست fail شد
assertion را با مقدار واقعی هماهنگ کن تا CI سبز شود.این guidance مبهم است، هدف را به خروجی تبدیل میکند و test oracle را قربانی میکند. همچنین «هر کاری» scope و rollback ندارد.
مثال خوب: `tdd-reschedule`
Input: acceptance A-04، baseline revision، مسیر policy و test.
1. Read-only: authority، command و baseline را ثبت کن.
2. Test author: تست جدید را بدون implementation اجرا و Red را ثبت کند.
3. Implementer: فقط policy و فایل production مجاز را تغییر دهد.
4. Verify: targeted test، typecheck و diff test/production را بررسی کن.
5. Stop: ambiguity، network، migration، credential یا تغییر oracle.
Output: Red/Green log، diff، limitation و recovery.این متن guidance است؛ enforcement را Hook و permission profile انجام میدهند.
## سه Hook و دو سیاست خطا
**Pre-tool guard** پیش از اجرای command، executable و argument را parse میکند. `pnpm exec vitest run ...` در allowlist میتواند عبور کند؛ `deploy`, `curl` به endpoint تولید، `git push` و هر حذف بازگشتناپذیر block یا approval میخواهند. Guard نباید با regex سادهای که quote و path traversal را نمیفهمد بهعنوان sandbox جا زده شود.
**Post-edit format** پس از تغییر فایلهای TypeScript، formatter را در workspace ایزوله و بدون network اجرا میکند. اگر formatter fail شد، diff را نگه میداریم و status را failure میکنیم؛ formatter نباید خودش assertion را عوض کند.
**Post-edit audit** diff را برای path خارج از allowlist، تغییر test oracle، credential pattern، production URL و تغییر permission میخواند و report میدهد. این audit باید line/path/evidence داشته باشد، نه فقط «امن است».
**Fail-closed** یعنی در خطای hook، عمل متوقف میشود. برای production command، secret، migration destructive، permission و تغییر acceptance مناسب است؛ availability کمتر ولی ریسک false negative پایینتر است.
**Fail-open** یعنی اگر hook در دسترس نبود، عمل ادامه مییابد. برای یک lint تزئینی یا telemetry کماهمیت ممکن است پذیرفتنی باشد، اما باید visible warning، محدودیت scope و owner داشته باشد. fail-open برای guard امنیتی ادعای حفاظت کاذب میسازد. در هر دو حالت timeout، crash، bypass و version mismatch را test کن.
## سناریوی booking-platform
Skill فقط S2 policy را میسازد. Pre-tool اجازهٔ خواندن `packages/contracts` و اجرای Vitest را میدهد؛ network و database واقعی deny است. Post-edit audit اگر `apps/api` یا migration تغییر کرد، fail-closed میکند و وضعیت `DECIDE` میسازد. formatter در fixture محلی مجاز است. اگر audit service unavailable شد، patch merge نمیشود؛ recovery شامل ذخیرهٔ diff، ثبت revision و اجرای audit از یک runner trusted است.
## آزمایشگاه گامبهگام
**ورودی:** Skill متن بالا، دو فایل policy/test، commandهای مجاز و یک fixture بدون secret واقعی.
۱. جدول policy بساز: action، risk، hook، on-success، on-failure. برای `vitest`، `prettier --check` و `git push` مقدار بده.
۲. پیش از tool call، command را بدون اجرا parse کن و فقط allowlist را قبول کن. تستهای بیخطر:
ALLOW pnpm exec vitest run packages/contracts/src/reschedule-policy.test.ts
ALLOW pnpm exec prettier --check packages/contracts/src/reschedule-policy.ts
BLOCK git push origin main
BLOCK curl https://production.invalid/health`production.invalid` صرفاً یک مقدار نمونهٔ غیرواقعی برای آموزش است و endpoint واقعی نیست.
۳. دو edit مصنوعی بساز: یکی فقط rename محلی، دیگری تغییر `expect(result.status)` و افزودن `EXAMPLE_TOKEN_DO_NOT_USE`. audit اول را pass و دومی را fail-closed کند.
**خروجی مورد انتظار:** JSON یا log شامل event، path، revision، decision، reason و recovery؛ هیچ command شبکه یا destructive اجرا نشده باشد. **Recovery:** اگر parser نتوانست command را با اطمینان تحلیل کند، deny و درخواست بررسی انسانی بده؛ اگر formatter خروجی متفاوت داد، format را ثبت کن و oracle را دست نزن.
## خطاهای رایج
- پنهان کردن enforcement داخل متن Skill و تصور اینکه مدل همیشه obey میکند.
- اجرای formatter با network یا روی production workspace.
- audit پس از merge، وقتی برگشت هزینهبر شده است.
- fail-open برای guard و گزارش آن بهعنوان security control.
- hookی که output را تغییر میدهد ولی diff یا log قابلردیابی ندارد.
- match کردن فقط نام command و نادیده گرفتن argument، cwd، symlink و environment.
## Verification
- [ ] Skill ورودی، مراحل، stop، خروجی و recovery دارد.
- [ ] pre-tool قبل از اجرا تصمیم میدهد و command مبهم را deny میکند.
- [ ] post-edit format و audit deterministic و قابل تکرارند.
- [ ] تغییر assertion، credential و path خارج از scope detectable است.
- [ ] fail-closed برای کنترلهای امنیتی انتخاب و دلیلش ثبت شده است.
- [ ] hook خودش trusted و version-pinned است و bypass آن قابل مشاهده است.
برای جلوگیری از false confidence، hookها را با fixtureهای مثبت و منفی version-control کن. یک command معتبر باید در دو اجرای مستقل همان تصمیم را بدهد؛ یک command با quoting متفاوت، symlink یا argument اضافه نباید بدون بررسی عبور کند. همچنین hook باید event id، revision و نسخهٔ parser را ثبت کند تا بعداً معلوم باشد کدام policy تصمیم گرفته است. اگر hook به service خارجی وابسته است، آن dependency خودش surface حمله و نقطهٔ fail است؛ برای gate امنیتی، اجرای محلی و deterministic یا runner مورد اعتماد بهتر از call مبهم بیرونی است.
guidance و enforcement را در گزارش جدا بنویس. مثلاً Skill میگوید «تست را پیش از implementation اجرا کن»؛ pre-tool guard میتواند command ممنوع را متوقف کند؛ اما هیچکدام بهتنهایی ثابت نمیکنند Red به علت درست رخ داده است. شاهد Red باید از test output و baseline بیاید و شاهد audit باید از diff. این separation به reviewer اجازه میدهد یک کنترل ناقص را با ادعای کنترل کامل اشتباه نگیرد.
## تمرین و پاسخ نمونه
برای hookی که پیش از `pnpm exec vitest` اجرا میشود، تعیین کن اگر binary پیدا نشد یا parser timeout کرد چه رخ دهد.
**پاسخ نمونه:** چون hook بخشی از gate تست و درستی شواهد است، هر دو حالت fail-closed: command اجرا نمیشود، diff و error در audit ثبت، و recovery اجرای همان revision در runner trusted است. اگر hook فقط telemetry بود، fail-open با warning ممکن بود. معیار پاسخ باید تفاوت safety property و convenience check، idempotence و نبود secret/network باشد.
## جمعبندی
Skill روش کار را portable میکند، اما hook enforcement محدود و قابلآزمون میدهد. قبل از tool call guard کن، بعد از edit format و audit کن، و fail-open را فقط برای کنترل غیرامنیتی با warning بپذیر. شکست یا دورزدن hook باید evidence تولید کند، نه سکوت.
## منابع مستقیم
- [Agent Skills specification](https://agentskills.io/specification) — ساختار Skill؛ execution و enforcement را تضمین نمیکند.
- [Cursor Hooks](https://cursor.com/docs/hooks.md) — schema و lifecycle محصول؛ version و surface را در adapter ثبت کنید.
- [Claude Code hooks guide](https://code.claude.com/docs/en/hooks-guide) — رویدادها و کنترلهای hook؛ جای sandbox OS نیست.
- [NIST SSDF](https://csrc.nist.gov/pubs/sp/800/218/final) — کنترلهای توسعهٔ امن و شواهد؛ الگوی hook خاصی الزام نمیکند.
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/) — محدودیت اعتبارسنجی و اجرای command؛ راهنمای عمومی است.
به زبان ساده
Skill میگوید چه مراحلی مطلوباند، اما Hook و permission باید واقعاً عمل خطرناک را کنترل کنند. Guard پیش از tool call و audit پس از edit میتوانند با ریسک مشخص fail-closed یا در کنترل کماهمیت fail-open شوند.
مثال واقعی
در آزمایش فرضی دوره، اجرای Vitest مجاز است اما `git push` و curl به endpoint تولید block میشوند؛ تغییر matcher از دقیق به `toBeDefined()` باید در audit دیده شود.