# چک‌لیست — تسک ۰۹ (موتور قوانین شش‌دسته‌ای)

**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** —

قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)

---

## ۰. خط سرخ

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۰.۲ | **هیچ قانونی روی حالت `slot` اعمال نمی‌شود** | ⏳ | ⭐ حتی اگر منطقی به نظر برسد |
| ۰.۳ | `DiscountRule` مهاجرت نکرد و دست‌نخورده ماند | ⏳ | |
| ۰.۴ | `NoPolicyRegressionTest`: بدون هیچ قانون، خروجی‌ها بیت‌به‌بیت مثل تسک ۰۸ | ⏳ | ⭐ |
| ۰.۵ | کد دلخواه در قانون **ممنوع** — فقط فهرست بسته | ⏳ | مستند بند ۸ |
| ۰.۶ | تودرتویی شرط ممنوع — فقط `all`/`any` یک‌سطحی | ⏳ | |

## ۱. بک‌اند

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `Policy` · `PolicyVersionLog` | ⏳ | |
| ۱.۲ | `active = false` پیش‌فرض | ⏳ | تسک ۱۰ آزمایش را اجبار می‌کند |
| ۱.۳ | `FieldRegistry` — سه مسئولیت روی یک آرایه (schema/extract/assert) | ⏳ | ⭐ فیلد نمایشیِ بی‌ارزیابی ممکن نشود |
| ۱.۴ | `OperatorRegistry` با یازده عملگر شامل `days_since` | ⏳ | |
| ۱.۵ | `EffectRegistry` — اثر خارج از دسته → ۴۲۲ | ⏳ | |
| ۱.۶ | `Combiner` — جدول ترکیب مستند بند ۸، خالص و بدون I/O | ⏳ | |
| ۱.۷ | `PolicyResolver` — اولویت → اختصاصی‌بودن → قدمت | ⏳ | |
| ۱.۸ | `specificity` هنگام **ذخیره** محاسبه می‌شود، نه اجرا | ⏳ | |
| ۱.۹ | شش موتور جدا، هر کدام یک کلاس | ⏳ | نه یک `PolicyEngine` بزرگ |
| ۱.۱۰ | `evaluateIsolated()` روی هر شش موتور | ⏳ | ⭐ تسک ۱۰ به آن نیاز دارد — اینجا اضافه شود |
| ۱.۱۱ | `SpacingPolicyEngine::forbiddenRanges()` — کوئری، **نه حلقه per slot** | ⏳ | ⭐ |
| ۱.۱۲ | بازهٔ ممنوعه **پیش از** تولید کاندید به `CandidateGenerator` می‌رود | ⏳ | نه فیلتر بعدی |
| ۱.۱۳ | `combinable=false` → short-circuit؛ `deny` همیشه short-circuit | ⏳ | |
| ۱.۱۴ | فیلد بی‌مقدار → `false` **با لاگ**، نه سکوت | ⏳ | ⭐ قانون خاموش بی‌صدا |
| ۱.۱۵ | `PATCH` محتوای قانون وجود ندارد؛ فقط `name` و `active` | ⏳ | نسخه‌بندی |
| ۱.۱۶ | `policy_version_log` snapshot **کامل** نگه می‌دارد، نه diff | ⏳ | |
| ۱.۱۷ | `valid_from` گذشته در نسخهٔ جدید → ۴۲۲ | ⏳ | قانون پنجم |
| ۱.۱۸ | شش endpoint شامل `GET /policy-schema` | ⏳ | |
| ۱.۱۹ | `PricingPolicyEngine` هر دو منبع (`DiscountRule` + `Policy`) را ترکیب می‌کند | ⏳ | |
| ۱.۲۰ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | |

## ۲. پر کردن قلاب‌های تسک‌های قبل

| # | قلاب | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | تسک ۰۴ — `ServiceSelectionValidator` → `SelectionPolicyEngine` | ⏳ | |
| ۲.۲ | تسک ۰۵ — `AppointmentPlanBuilder` مرحلهٔ ۷ → `Resource` + `Timing` | ⏳ | |
| ۲.۳ | تسک ۰۶ — `AvailabilityEngine` مرحلهٔ ۶ → `Spacing` | ⏳ | |
| ۲.۴ | تسک ۰۷ — `BookingService::confirm` مرحلهٔ ۳ → `Eligibility` | ⏳ | |
| ۲.۵ | تسک ۰۸ — `PricingEngine` مرحلهٔ ۳ → `Pricing` | ⏳ | |
| ۲.۶ | **هیچ امضایی عوض نشد** | ⏳ | ⭐ دلیل گذاشتن قلاب‌ها از روز اول |

## ۳. دیتابیس

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `policies` + `policy_version_log` | ⏳ | |
| ۳.۲ | `idx_policies_lookup (entity_type, entity_id, category, active, valid_from)` | ⏳ | |
| ۳.۳ | `appointments.applied_policies` (JSON تهی‌پذیر) | ⏳ | |
| ۳.۴ | قرارداد `applied_policy_ids` با `{id, version, name}` | ⏳ | `name` کپی متنی |
| ۳.۵ | `policy_version_log` در `AGGREGATE_CHILDREN` | ⏳ | |
| ۳.۶ | `app:policy:seed-examples` — پنج نمونه، همه `active=false` | ⏳ | |
| ۳.۷ | `TenantSchemaCoverageTest` سبز | ⏳ | |

## ۴. کارایی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `AvailabilityPerformanceTest` **با قوانین فعال** سبز است | ⏳ | ⭐⭐ اگر قرمز شد، `spacing` حلقه می‌زند |
| ۴.۲ | `SpacingPolicyEngine` تعداد کوئری ثابت دارد، مستقل از تعداد اسلات | ⏳ | |

## ۵. UI

این تسک صفحه نمی‌سازد (تسک ۱۰ می‌سازد). فقط:

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | پیام‌های خطای `deny` فارسی و قابل فهم بیمار | ⏳ | نه نام قانون خام |
| ۵.۲ | خطای `add_requirement` بدون منبع شامل **نام قانون** | ⏳ | «قانون X جراح می‌خواهد ولی…» |

## ۶. تست

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `ConditionEvaluatorTest` — همهٔ عملگرها × نوع‌ها، `all`/`any`، فیلد ناموجود | ⏳ | واحد |
| ۶.۲ | `CombinerTest` — شش قاعدهٔ جدول مستند | ⏳ | واحد |
| ۶.۳ | `PolicyResolverTest` — سه سناریوی حل تناقض + short-circuit | ⏳ | |
| ۶.۴ | `SpacingPolicyEngineTest` — بازهٔ ممنوعه + تعداد کوئری ثابت | ⏳ | |
| ۶.۵ | `PolicyVersioningTest` — قانون پنجم | ⏳ | ⭐ |
| ۶.۶ | `PolicyIntegrationTest` — چهار دسته end-to-end | ⏳ | |
| ۶.۷ | `PolicySchemaTest` — هر فیلد schema قابل extract است | ⏳ | ⭐ |
| ۶.۸ | `NoPolicyRegressionTest` | ⏳ | ⭐ |

## ۷. مستندات

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | `docs/api/policy.md` با فهرست کامل فیلد/عملگر/اثر | ⏳ | |
| ۷.۲ | قاعدهٔ «`DiscountRule` یا `Policy`؟» صریح | ⏳ | ⭐ |
| ۷.۳ | `docs/architecture/policy-engine.md` — حل تناقض، ترکیب، دلیل ممنوعیت کد دلخواه، دلیل عدم مهاجرت | ⏳ | |

## ۸. بازبینی پایانی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۸.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | |
| ۸.۲ | `bin/phpunit` کامل سبز | ⏳ | |
| ۸.۳ | `--group=slot-mode-frozen` سبز | ⏳ | |
| ۸.۴ | `AvailabilityPerformanceTest` با قوانین فعال سبز | ⏳ | |
| ۸.۵ | `phpstan` بدون خطای جدید | ⏳ | |
| ۸.۶ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | |
| ۸.۷ | تست‌های tenant سبز | ⏳ | |
| ۸.۸ | `docs/api/*` به‌روز | ⏳ | |
| ۸.۹ | دو کلاینت دیگر بررسی شدند | ⏳ | پیام‌های `deny` در سایت درست نمایش داده می‌شوند؟ |
| ۸.۱۰ | commit، سپس `graphify update .` | ⏳ | |
| ۸.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | |
