# زیرساخت مهامیار: سند مرحله قبلی

> وضعیت جاری ساختار و مالکیت داده در [معماری نسخه دوم](ARCHITECTURE_V2.fa.md) ثبت شده است. توضیحات زیر فقط سابقه مرحله دوماژولی است.

تاریخ: ۲۰۲۶-۱۰-۳۱. این سند وضعیت اسکلت پیاده‌شده را شرح می‌دهد؛ نسخه قابل انتشار محصول نیست.

آخرین بررسی در ۲۰۲۶-۱۱-۰۱: ۱۶ تست FoundationTest با ۱۳۹ assertion موفق، بررسی syntax برای ۶۶ فایل PHP موفق، قالب‌بندی Pint انجام‌شده و composer.json همراه lock معتبر است. پروژه Git ندارد؛ گزینه Pint --dirty قابل استفاده نبود و قالب‌بندی با مسیرهای مشخص انجام شد.

## ساختار انتخاب‌شده

فقط `Identity` و `AccessControl` ماژول مستقل‌اند. کسب‌وکار، شعب، عضویت، استان و شهر، تنظیمات پیامک، دامنه، سابقه امنیتی و استحقاق استفاده از ماژول‌ها در هسته‌اند. هیچ ماژول خالی برای CRM، حسابداری یا سایر امکانات ساخته نشده است.

```text
app/
  Core/Organizations/              # زمینه کسب‌وکار و عضویت جاری
  Http/Middleware/                 # وضعیت حساب و انتخاب زمینه درخواست
  Providers/AppServiceProvider.php # اتصال خدمات مشترک
database/
  migrations/                      # جدول‌های مشترک و قواعد اتصال زیرساخت
  foundation-manifest.json          # فهرست مایگریشن‌های این مرحله
Modules/
  Identity/
    app/Models/                    # User مرکزی سازگار با Passport
    app/Actions/                   # عملیات هویت
    app/PublicContracts/           # قرارداد مجاز برای سایر بخش‌ها
    app/Http/                      # Controller و API Resource
    app/Providers/
    routes/api.php
    database/migrations/
  AccessControl/
    app/Actions/                   # بررسی دسترسی جاری
    app/PublicContracts/
    app/Http/Middleware/
    app/Providers/
    database/migrations/
```

پکیج‌های نصب‌شده: Laravel 13.39.0، Passport 13.10.0، nwidart/laravel-modules 13.3.0. Boost 2.10.4 طبق AGENTS پروژه نصب شده است. ساختار Controller → Action → Eloquent/Query Builder است. برای هر جدول Repository، برای هر عملیات Interface و برای هر بخش کوچک ماژول جدا ساخته نشده است.

## مسئولیت و ارتباط

- Identity هویت مرکزی را نگه می‌دارد؛ `password_hash` با `getAuthPasswordName()` به Laravel معرفی شده است. مدل اصلی در ماژول است و `App\Models\User` صرفاً نام سازگار برای اتصال‌های قبلی است.
- هسته تعیین می‌کند شخص عضو کدام کسب‌وکار است. `ResolveBusinessContext` کاربر، عضویت و کسب‌وکار فعال را با هم بررسی می‌کند.
- AccessControl مشخص می‌کند آن عضویت در کدام شعبه چه عملیاتی دارد. نقش پلتفرم خودبه‌خود به کسب‌وکار دسترسی نمی‌دهد؛ مالک نیز از بررسی مجوز مستثنا نیست.
- سایر بخش‌ها فقط `PublicContracts` ماژول دیگر را import می‌کنند. Providerها و configهای اتصال، محل ترکیب پیاده‌سازی‌ها هستند. تغییر مستقیم جدول متعلق به ماژول دیگر از مسیرهای محصول مجاز نیست.
- خواندن مشترک برای بررسی دسترسی در همین مونولیت، در resolver و AccessChecker متمرکز شده است؛ هر Controller نباید join امنیتی خودش را بسازد.
- `CurrentBusiness` scoped است و middleware در finally آن را پاک می‌کند؛ زمینه نباید بین درخواست‌های worker باقی بماند. jobهای آینده باید زمینه را صریح بسازند؛ انتقال خودکار context HTTP به صف پیاده نشده است.
- تمام queryهای محصول باید business_id زمینه معتبر را اعمال کنند. وجود middleware به‌تنهایی query دلخواه را tenant-safe نمی‌کند؛ RLS در این مرحله پیاده نشده است.

## قرارداد درخواست محصول

مسیرها نسخه‌دارند. مسیر هویت ساخته‌شده: `GET /api/v1/identity/me` با `auth:api` و `account.active`. پاسخ موفق از API Resource زیر کلید data می‌آید. خطاهای API زیر error با code، message و fields هستند؛ جزئیات exception داخلی نمایش داده نمی‌شود. خطاهای OAuth را در قالب خود پروتکل حفظ می‌کنیم.

الگوی مسیر کسب‌وکاری آینده:

```php
Route::middleware(['api', 'auth:api', 'account.active', 'business.context', 'permission:sales.invoice.create'])
    ->post('/api/v1/businesses/{business}/branches/{branch}/invoices', CreateInvoiceController::class);
```

`business` و `branch` شناسه UUID عمومی‌اند. Header یا پارامتر body به‌عنوان مجوز انتخاب کسب‌وکار پذیرفته نمی‌شود. برای عملیات سطح کسب‌وکار، بخش branch حذف می‌شود. `permission` باید بعد از `business.context` اجرا شود. انتخاب زمینه هرگز جای permission عملیاتی را نمی‌گیرد.

`AccessChecker::allows` در حال حاضر مسیر کارکنان کسب‌وکار و پلتفرم را بررسی می‌کند. مجوز CUSTOMER در مسیر کارکنان پذیرفته نمی‌شود. کنترل مالکیت رکوردهای مشتری و محدودیت خدمت، دمو و بازدید پشتیبانی در مرحله ماژول‌های مربوط تکمیل می‌شوند؛ از این checker به‌تنهایی برای پنل مشتری استفاده نشود.

برای عملیات شعبه: عضویت فعال + حساب فعال + کسب‌وکار فعال + شعبه فعال + رکورد صریح دسترسی + نبود درخواست تأیید مجدد معلق + نقش و مجوز فعال لازم است. برای امکانات محصول، استحقاق VALID و فعال‌بودن ماژول نیز کنترل می‌شود. `core`، `identity` و `access_control` نیازمند خرید ماژول نیستند. هیچ cache مجوزی در این مرحله نداریم؛ لغو نقش در بررسی بعدی اثر می‌کند.

## مایگریشن‌ها

۲۳ جدول زیرساخت توافق‌شده وجود دارد: ۱۸ جدول اختصاصی و ۵ جدول Passport. شش جدول فنی Laravel شامل sessions، cache، cache_locks، jobs، job_batches و failed_jobs نیز موجودند؛ جدول migrations را خود Laravel مدیریت می‌کند.

- Identity: users، authentication_otp_challenges، authentication_rate_events و پنج oauth_*.
- AccessControl: roles، permissions، role_permissions، membership_roles، membership_branch_access و membership_branch_reauthorizations.
- هسته: states، cities، businesses، memberships، branches، security_audit_events، business_module_entitlements، sms_settings و business_domains.

جدول `rbac_owner_reauthentications` و ستون `branch_access_scope` وجود ندارند. دسترسی صریح شعبه در AccessControl است و رابطه با شعبه و عضویت از FK ترکیبی محافظت می‌شود. تغییر دامنه اختصاصی در business_domains انجام می‌شود؛ subdomain داخلی در businesses مانده است.

فایل پیش‌فرض Laravel برای users و password_reset_tokens از مسیر اجرا خارج و بدون حذف محتوای قبلی در `database/reference/laravel_default_users_migration.php.txt` نگهداری شده است. این مجموعه برای نصب جدید طراحی شده؛ تبدیل دیتابیس قبلی یا مهاجرت داده‌ها نیست. اگر قبلاً مایگریشن پیش‌فرض اجرا شده باشد، قبل از اجرا باید وضعیت واقعی آن دیتابیس و مسیر تبدیل جدا بررسی شود.

guard ابتدایی، PostgreSQL 15 یا بالاتر را الزام می‌کند. حداقل 15 به‌دلیل استفاده از NULLS NOT DISTINCT است. پیکربندی پیش‌فرض و env.example برای PostgreSQL تنظیم شده، ولی فایل واقعی `.env` تغییر نکرده است. توابع اختصاصی از schema public استفاده می‌کنند؛ schema دیگری بدون اصلاح و آزمون سازگار نیست.

ترتیب ثبت با نام فایل بین مسیرهای root و دو ماژول سراسری است. برای نصب مشترک آینده از migrator کل برنامه استفاده شود؛ اجرای مستقل `module:migrate Identity` یا `module:migrate AccessControl` برای نصب خالی درست نیست، چون FKهای مشترک دارند. هر دو ماژول زیرساخت باید فعال بمانند. فعال‌سازی ماژول برای یک کسب‌وکار با business_module_entitlements انجام می‌شود، نه با خاموش‌کردن ماژول در modules_statuses.json.

مایگریشن‌های Passport از نسخه نصب‌شده کپی شده‌اند؛ دوباره publish کردن آن‌ها به مسیر root باعث تعریف تکراری می‌شود. ارتقای Passport باید با مقایسه migrationهای همین نسخه و نسخه جدید انجام شود. از `install:api --passport` برای این اسکلت استفاده نشود، چون می‌تواند migration اجرا کند.

قواعد FK و CHECK، تاریخچه اعطا/لغو، تطابق audit با هدف، محدودیت تغییر شناسه‌ها و جلوگیری از حذف تاریخچه در فایل‌ها تعریف شده‌اند. برخلاف ZIP، وابستگی به PreparedMembershipTriggers یا RbacAuditSchema خارج از مجموعه ندارند. این جایگزین‌ها باید روی PostgreSQL نیز آزموده شوند؛ تست تولید SQL اثبات اجرای trigger نیست.

## تأیید مجدد و امنیت هویت

قرارداد CurrentPasswordVerifier رمز جاری را در برابر حساب فعال بررسی می‌کند و هیچ مجوز ماندگاری صادر نمی‌کند. مسیر تغییر حساس آینده باید `throttle:sensitive-action` داشته باشد، مالکیت/مجوز جاری را بررسی کند و تأیید رمز و تغییر داده و audit را در همان عملیات هماهنگ کند. متد verify به‌تنهایی مجوز انجام تغییر نیست.

برای کاربر بدون رمز، purpose برابر sensitive_action به جدول OTP اضافه شده و به user، operation_uid، operation_key، fingerprint و auth_epoch وصل است. endpoint ارسال و مصرف OTP، قفل‌گذاری، ابطال چالش منقضی قبل از صدور جدید و جلوگیری از مصرف هم‌زمان هنوز مرحله بعد است. کد خام و رمز در log یا audit قرار نمی‌گیرند. داده NEW/EXISTING/DENIED ثبت‌نام، داخلی است و نباید به کسب‌وکار نمایش داده شود.

تغییرات حساس باید audit با داده‌های مجاز محدود ایجاد کنند. details فقط previous_status، new_status و changed_fields را می‌پذیرد؛ محتوای آن‌ها نیز در سرویس نویسنده باید از کاتالوگ مجاز بیاید. محدودیت ساختار JSON جای کنترل محرمانگی در سرویس را نمی‌گیرد.

جریان کامل ورود، UI احراز هویت/رضایت PKCE، بازیابی رمز، ارسال پیامک، ابطال هم‌زمان access/refresh و bootstrap مدیر اولیه در این مرحله ساخته نشده‌اند. Passport guard، مدل، زمان اعتبار توکن و جدول‌ها آماده‌اند. کلیدهای Passport و client واقعی ایجاد نشده‌اند؛ تست HTTP از کاربر احراز‌شده شبیه‌سازی‌شده استفاده می‌کند. عمر پیشنهادی فعلی access برابر ۱۵ دقیقه، refresh برابر ۳۰ روز و personal token برابر ۷ روز است.

## اضافه‌کردن ماژول محصول بعدی

با `php artisan module:make CRM --api --no-interaction` ساختار ماژول تولید می‌شود. برای ساختار کم‌حجم‌تر از plain و افزودن فقط پوشه‌های لازم استفاده شود. Composer merge-plugin فایل‌های Modules/*/composer.json را می‌خواند؛ بعد از افزودن ماژول، autoload بازسازی شود. مدل داده، permissionها، entitlement و مسیرهای محافظت‌شده ماژول باید با هم طراحی شوند.

عملیات چندماژولی که باید اتمیک باشد، توسط Action هماهنگ‌کننده روی یک connection و در یک تراکنش اجرا شود. پیامک و سایر اثرهای خارجی بعد از commit انجام شوند. ساختار کنونی یک دیتابیس مشترک است؛ اجرای جداگانه حسابداری/فروش در دیتابیس مستقل جزو این قرارداد نیست.

## حفظ امکانات و حدود تحویل

[نقشه تطبیق امکانات](FEATURE_PARITY_MAP.fa.md) تمام ۲۱۴ قابلیت استخراج‌شده را با شناسه قبلی حفظ می‌کند. همه قابلیت‌های محصول در آن هنوز نیازمند پیاده‌سازی و بررسی کامل‌اند؛ آماده‌شدن زیرساخت به معنی ساخت CRM، فروش یا حسابداری نیست.

تست‌های FoundationTest شامل بارگذاری دو ماژول، پاسخ هویت، رد حساب تعلیق‌شده، جداسازی زمینه، مجوز صریح شعبه، نقش شعبه‌ای، لغو مجوز، entitlement و جلوگیری از نقش کسب‌وکار دیگر است. تست DDL از PostgresConnection در حالت pretend با اتصال ممنوع استفاده می‌کند و ترتیب FK، جدول تکراری و تابع trigger تعریف‌نشده را بررسی می‌کند. تست queryها فقط SQLite در حافظه است؛ PostgreSQL یا دیتابیس پروژه تغییر نکرده است.

دستور بررسی این محیط، با فعال‌سازی موقت درایور SQLite بدون تغییر php.ini:

```powershell
php -d extension=pdo_sqlite vendor/bin/phpunit tests/Feature/FoundationTest.php --no-progress
php artisan module:list --no-interaction
php artisan route:list --path=api --no-interaction
```

آزمون واقعی constraints، transitionها، rollback تراکنش و رقابت هم‌زمان روی PostgreSQL جداگانه لازم است. هیچ migration یا seed روی دیتابیس پروژه اجرا نشده است. APIهای ثبت و مدیریت کسب‌وکار، اعطا/لغو نقش، تغییر وضعیت و انتقال مالکیت هنوز ساخته نشده‌اند؛ تا پیاده‌سازی سرویس‌های آن‌ها، نوشتن مستقیم جدول‌ها مسیر محصول محسوب نمی‌شود.
