Files
smart-org/README.md
T
mjalmoudy94 3d1e98f4fb افزودن ارائه‌دهندهٔ agy (Google Antigravity) با لاگین headless و پول چرخشی اکانت
- Adapter اکانت‌محور با چرخش round-robin و بنچ‌شدن خودکار روی ۴۲۹/۴۰۱
- ورود ادمین از طریق URL بازگشتی گوگل (PKCE) و ذخیرهٔ رمزنگاری‌شدهٔ refresh token
- صفحهٔ مدیریت اکانت‌های agy در پنل ادمین به همراه تست و فعال/غیرفعال‌سازی
- مهاجرت Alembic b1c2d3e4f5a6 برای ستون‌های اکانت agy
- seed همیشه کاتالوگ agy را می‌سازد و فقط با AI_DEFAULT_PROVIDER=agy مسیریابی می‌شود
2026-10-13 08:20:27 +03:30

352 lines
16 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.
# Smart Organization — پلتفرم نیروی کار هوش مصنوعی
نسخهٔ وب مستقل و چندسازمانی محصول. این پروژه **کاملاً جدا از ربات تلگرام** است و
هیچ فایلی از ریشهٔ مخزن را در runtime خود import نمی‌کند.
- پلن محصول و معماری: [`PLAN.md`](./PLAN.md)
- چک‌لیست اجرایی کامل: [`TODO.md`](./TODO.md)
- یادداشت زیرساخت فعلی (فقط مرجع): [`docs/current-infra-notes.md`](./docs/current-infra-notes.md)
---
## ۱. محصول چیست
یک وب اپلیکیشن چندسازمانی برای «نیروی کار هوش مصنوعی سازمانی».
**کاربر نهایی این‌ها را می‌بیند:** سازمان، مدیر هوش مصنوعی، کارمندهای هوش مصنوعی،
پروژه، وظیفه، فایل/خروجی، مصرف و پرداخت.
**کاربر نهایی این‌ها را نمی‌بیند:** نام مدل، provider، `base_url`، API key، قیمت خام
provider. این‌ها فقط در **Admin Console** و فقط برای مالک سامانه قابل مدیریت‌اند.
پلتفرم سه لایهٔ نقش دارد:
1. **مالک سامانه (platform owner)** — providerها، مدل‌ها، routing policy، تعرفه‌ها،
sandbox templateها، مشاهدهٔ کل مصرف.
2. **مدیر سازمان مشتری** — ساخت سازمان، دعوت عضو، پروژه، فعال‌سازی کارمند، مصرف.
3. **عضو سازمان** — گفتگو با مدیر/کارمند، ساخت task، آپلود فایل، دیدن خروجی.
---
## ۲. پیش‌نیازها
| ابزار | نسخه |
|---|---|
| Node.js | >= 20 |
| pnpm | 9.x |
| Python | 3.13 |
| Docker + Compose | برای postgres/redis |
فعال‌سازی pnpm در صورت نبود:
```bash
corepack enable pnpm
corepack prepare pnpm@9.12.0 --activate
```
---
## ۳. راه‌اندازی سریع
```bash
# ۱) نصب وابستگی‌های JS
pnpm install
# ۲) ساخت venv پایتون برای backend
pnpm api:venv
# ۳) کپی فایل env و پر کردن مقادیر (پیش‌فرض‌ها همه mock هستند)
cp .env.example .env
# ۴) بالا آوردن postgres و redis
docker compose -f infra/docker/docker-compose.yml up -d postgres redis
# ۵) اجرای migration و seed
pnpm db:migrate
pnpm db:seed
# ۶) اجرای هم‌زمان frontend و backend
pnpm dev
```
آدرس‌ها پس از اجرا:
- Frontend: <http://localhost:3000>
- API: <http://localhost:8000>
- API docs (Swagger): <http://localhost:8000/docs>
- Health: <http://localhost:8000/health>
### حساب‌های نمونه (فقط محیط توسعه)
`pnpm db:seed` دو حساب می‌سازد و **هر دو عضو سازمان نمونه هستند**، پس بعد از ورود
بلافاصله یک داشبورد پر می‌بینید:
| نقش | ایمیل | رمز |
|---|---|---|
| کاربر نمونه | `demo@example.com` | `demo12345` |
| مالک سامانه (admin console) | `owner@example.com` | `owner12345` |
> این‌ها رمزهای **توسعه** هستند و در `app/seed.py` سخت‌کد شده‌اند. هیچ‌وقت در
> production از این‌ها استفاده نکنید و `JWT_SECRET` / `ENCRYPTION_KEY` واقعی
> بگذارید (نگهبان config در حالت production مقادیر placeholder را رد می‌کند).
>
> نکته: دامنهٔ ایمیل عمداً `example.com` است. اگر `.local` یا `.test` بگذارید،
> `email-validator` آن را رد می‌کند و حساب ساخته می‌شود ولی **ورود به آن ناممکن
> است**. `tests/test_seed.py` همین را تضمین می‌کند.
### اجرای کل استک با Docker
```bash
docker compose -f infra/docker/docker-compose.yml up --build
```
---
## ۴. اسکریپت‌ها
| دستور | کار |
|---|---|
| `pnpm dev` | اجرای هم‌زمان Next.js و FastAPI |
| `pnpm build` | build فرانت‌اند |
| `pnpm lint` | ESLint (web) + ruff (api) |
| `pnpm typecheck` | tsc (web) + mypy (api) |
| `pnpm test` | vitest (web) + pytest (api) |
| `pnpm format` | Prettier (web) + ruff format (api) |
| `pnpm db:migrate` | `alembic upgrade head` |
| `pnpm db:seed` | داده‌های نمونه (platform owner، کاربر نمونه، سازمان، templateها) |
| `pnpm api:venv` | ساخت venv پایتون |
| `pnpm worker:billing` | یک پاس worker صورتحساب |
| `pnpm worker:ai-runtime` | یک پاس worker اجرای taskها |
| `pnpm worker:sandbox-gateway` | یک پاس پاک‌سازی sessionهای بی‌استفاده |
---
## ۵. معماری
### ۵.۱ سبک معماری
- **Clean Architecture** + **Hexagonal / Ports & Adapters**
- **DDD** برای مرزبندی bounded contextها
- **Event-Driven** برای billing، audit و observability (الگوی Outbox)
- **Modular Monolith** در فاز اول — microservice زودهنگام ممنوع
قاعدهٔ سخت: لایهٔ `domain` هیچ dependency به FastAPI، SQLAlchemy، Redis، HTTP یا
provider خارجی ندارد. فقط `application` و `infrastructure` به آن وابسته‌اند، نه برعکس.
### ۵.۲ Bounded Contextها
`identity` · `billing` · `ai_control` · `workforce` · `sandbox` · `knowledge` ·
`audit` · `projects` · `platform`
### ۵.۳ Ports الزامی
`ModelProviderPort` · `PaymentProviderPort` · `SandboxProviderPort` ·
`GitProviderPort` · `DomainRouterPort` · `StoragePort` · `VectorStorePort` ·
`NotificationPort` · `AuditPort` · `UsageLedgerPort`
هر port حداقل یک adapter **mock** دارد تا کل استک بدون هیچ credential واقعی اجرا شود.
### ۵.۴ جریان درخواست
```text
Browser → Web (Next.js/BFF) → API (FastAPI)
├── Domain Modules
├── Event Bus / Outbox → Workers
├── AI Runtime → Model Providers
├── Sandbox Gateway → E2B / Daytona
└── Billing Processor → Stripe
```
---
## ۶. ساختار پوشه
```text
web/
PLAN.md پلن محصول و معماری
TODO.md چک‌لیست اجرایی
README.md همین فایل
package.json اسکریپت‌های ریشه
pnpm-workspace.yaml تعریف workspace
.env.example نمونهٔ env (هیچ secret واقعی ندارد)
apps/
web/ Next.js + TypeScript (UI)
api/ FastAPI + SQLAlchemy (backend)
services/
ai-runtime/ worker اجرای Agent
sandbox-gateway/ wrapper ایزوله‌سازی اجرا
billing-worker/ پردازش usage و invoice
packages/
contracts/ تایپ‌ها و قراردادهای مشترک
ui/ componentهای پایه (design system)
infra/
docker/ docker-compose
migrations/ محل migrationها
docs/
architecture/ مستندات معماری
product/ مستندات محصول
current-infra-notes.md
```
---
## ۷. کجا کد بنویسیم
| می‌خواهم... | محل |
|---|---|
| صفحه یا component UI بسازم | `apps/web/src/app` یا `apps/web/src/features` |
| component پایه/design system | `packages/ui` |
| endpoint جدید بسازم | `apps/api/app/interfaces/http` |
| use case بنویسم | `apps/api/app/application` |
| منطق دامنه بنویسم | `apps/api/app/domain/<context>` |
| adapter یا اتصال به سرویس بیرونی | `apps/api/app/infrastructure` |
| worker پس‌زمینه بنویسم | `apps/api/app/workers` (پوستهٔ اجرا در `services/<name>`) |
| خطا ثبت/پاک‌سازی کنم | `apps/api/app/core/redact.py` + `apps/api/app/infrastructure/error_log.py` |
| قرارداد مشترک front/back | `packages/contracts` |
**ممنوع:** نوشتن هر کد جدید در فایل‌های ریشهٔ مخزن (`bot.py`, `web_panel.py`,
`bot_menus.py`, `bot_messages.py`) و هر وابستگی به `telegram`, `chat_id`,
`callback_data`, `InlineKeyboardMarkup`.
---
## ۸. سرویس‌های خارجی و حالت mock
همهٔ سرویس‌های بیرونی پیش‌فرض روی `mock` هستند. برای فعال‌سازی واقعی، این envها را
پر کنید:
| قابلیت | env | مقادیر |
|---|---|---|
| مدل | `AI_DEFAULT_PROVIDER` | `mock` \| `openai_compatible` \| `agy` |
| Git | `GIT_PROVIDER` | `mock` \| `gitea` |
| دامنهٔ تست | `DOMAIN_ROUTER` | `mock` \| `caddy` |
| Sandbox | `SANDBOX_PROVIDER` | `mock` \| `daytona` \| `e2b` |
| پرداخت | `PAYMENT_PROVIDER` | `mock` \| `stripe` |
اگر credential واقعی نداریم، **قابلیت حذف نمی‌شود**: مدل دیتابیس، interface، UI و
adapter mock کامل ساخته می‌شود و adapter واقعی پشت همین flag آماده می‌ماند.
### ارائه‌دهندهٔ agy (Google Antigravity)
agy کلید API ندارد؛ **اکانت**‌ها هویت Google هستند و از طریق OAuth وارد
می‌شوند. برای فعال‌سازی:
1. `AI_DEFAULT_PROVIDER=agy` را در `.env` بگذارید و `pnpm db:seed` را دوباره
اجرا کنید (کتالوگ مدل‌های agy فعال و کاتالوگ mock غیرفعال می‌شود).
2. با حساب مالک سامانه وارد کنسول مدیریت شوید و از
`/admin/accounts` → «افزودن اکانت agy» شروع کنید: لینک احراز هویت گوگل
باز می‌شود، بعد از تأیید، URL بازگشت را از نوار آدرس مرورگر کپی و Paste
کنید (صفحهٔ مقصد عمداً بارگذاری نمی‌شود؛ پارامترها همان‌جا هستند).
3. هر تعداد اکانت Google که بخواهید اضافه کنید. مدل‌ها را در
`/admin/models` در صورت نیاز فعال کنید.
پول چرخشی، بار را بین اکانت‌ها پخش می‌کند و اکانتی که ۴۲۹ گرفته تا پایان
پنجرهٔ سهمیهٔ آن کنار گذاشته می‌شود (هم در حافظه و هم در دیتابیس، تا workerها
هم همان تصویر را ببینند). refresh tokenها فقط encrypted at rest ذخیره
می‌شوند و هیچ‌وقت از API برنمی‌گردند.
---
## ۹. Workerهای پس‌زمینه
سه worker وجود دارد. همه در `apps/api/app/workers/` پیاده شده‌اند و `services/<name>`
پوستهٔ اجرای آن‌هاست:
| worker | کار | دستور |
|---|---|---|
| `billing-worker` | جمع‌بندی usage ledger به فاکتور draft (هر دوره یک‌بار، idempotent) | `pnpm worker:billing` |
| `ai-runtime` | برداشتن taskهای در صف و اجرای آن‌ها از مسیر run engine | `pnpm worker:ai-runtime` |
| `sandbox-gateway` | متوقف‌کردن sessionهای بی‌استفاده و ثبت audit | `pnpm worker:sandbox-gateway` |
هر دستور **یک پاس** اجرا می‌کند و خارج می‌شود (شکل مناسب cron). برای اجرای
مستمر `--loop` بدهید:
```bash
cd apps/api && .venv/bin/python -m app.workers billing --loop --interval 3600
```
**اصل مهم:** workerها هیچ منطق تجاری ندارند. هر سه از
`app/application/container.py` همان سرویس‌هایی را می‌سازند که HTTP API می‌سازد،
پس worker و API هرگز نمی‌توانند دربارهٔ اینکه کدام adapter پشت یک port است اختلاف
داشته باشند. جزئیات هر کدام در `services/*/README.md`.
---
## ۱۰. اصول تغییرناپذیر
1. اجرای کد کاربر یا agent روی host اصلی **ممنوع** — فقط از مسیر Sandbox Gateway.
2. هر endpoint چندسازمانی باید `organization_id` را enforce کند.
3. هیچ secret، token یا API key در frontend یا لاگ چاپ نمی‌شود.
4. هر عملیات destructive باید confirmation و audit log داشته باشد.
5. مدل و provider از دید کاربر عادی پنهان است.
6. هر مصرفی در usage ledger داخلی ثبت می‌شود (Stripe فقط adapter است).
---
## ۱۱. کنسول خطا
هر خطایی که سامانه بگیرد — در درخواست HTTP، در worker پس‌زمینه، یا در زمان
راه‌اندازی — با جزئیات کامل در جدول `error_events` ثبت می‌شود و در
`/admin/errors` (فقط مالک سامانه) قابل مشاهده است.
**چه چیزی ثبت می‌شود**
| منبع | چه زمانی | شدت |
| --- | --- | --- |
| استثنای مدیریت‌نشده و خطاهای ۵xx | همیشه | `critical` / `error` |
| `ValueError` بدون نوع مشخص | همیشه | `warning` |
| سایر پاسخ‌های ۴xx | فقط با `ERROR_LOG_CLIENT_ERRORS=1` | `warning` |
۴xx به‌صورت پیش‌فرض ثبت نمی‌شود چون یک API عمومی دائماً ۴۰۴ از اسکنرها
می‌گیرد و ثبت همهٔ آن‌ها خطاهای واقعی را زیر خود دفن می‌کند. خطاهای ۵xx
همیشه ثبت می‌شوند، مستقل از این تنظیم.
**هر رکورد چه چیزی دارد**
- `kind`، `error_type`، `message` و `stack` (ردِّ کامل خطا)
- `method`، `path`، `status_code` و `request_id`
- `organization_id` و `user_id` (پس از احراز هویت)
- `fingerprint` — هش پایدار از هویت خطا، برای گروه‌بندی
- `metadata_json` — جزئیات اضافی (مثلاً `task_id`)
**گروه‌بندی و رسیدگی**
خطاهای یکسان بر اساس `fingerprint` در یک ردیف جمع می‌شوند، پس یک باگ که ۴۰۰۰
بار رخ داده یک خط با شمارنده است، نه ۴۰۰۰ خط. `fingerprint` عمداً سازمان و
کاربر را در نظر نمی‌گیرد: یک باگ که دو مستأجر را می‌زند هنوز یک باگ است.
بستن یک گروه همهٔ رخدادهای باز آن را می‌بندد و در audit log ثبت می‌شود.
**پاک‌سازی خودکار secretها**
هیچ خطایی بدون عبور از `app/core/redact.py` ذخیره نمی‌شود. سه لایه:
کلیدهایی که معنایشان secret است (`password`، `api_key`، …)، الگوهای
شناختنی (JWT، `Bearer`، `scheme://user:pass@host`، `sk-…`، `ghp_…`)، و
خودِ مقادیر secret تنظیم‌شده در `settings`. یک لاگ خطا که رمز عبور ذخیره کند
از نداشتن لاگ خطا بدتر است.
**استفاده از `request_id`**
هر پاسخ `X-Request-ID` دارد و هر بدنهٔ خطا هم همان شناسه را در
`error.request_id` برمی‌گرداند. کاربر می‌تواند فقط همین یک رشته را گزارش کند
تا دقیقاً یک رکورد پیدا شود.
```bash
# ۵۰۰ بگیر، سپس با همان شناسه رکورد را پیدا کن
curl -i http://localhost:8000/api/whatever -H "X-Request-ID: my-trace"
curl "http://localhost:8000/api/admin/errors?search=my-trace" -H "Authorization: Bearer $OWNER_TOKEN"
```
**اجرای دستی workerها و ثبت خطا**
خطای یک pass کامل با `source=worker` ثبت می‌شود و exit code غیرصفر می‌دهد تا
cron متوجه شود. خطای یک task مشخص نیز با `task_id` و سازمان مربوطه ثبت
می‌شود.
---
## ۱۲. مستندات
- [`docs/architecture/overview.md`](./docs/architecture/overview.md) — نمای کلی معماری
- [`docs/security-checklist.md`](./docs/security-checklist.md) — چک‌لیست امنیتی
- [`docs/current-infra-notes.md`](./docs/current-infra-notes.md) — زیرساخت فعلی (مرجع)