# Phase 1 — Engine Foundations + Seed Configuration

> **Goal:** All 6 CoreApp engines migrated, modelled, seeded. All 5 feature packs authored.
> `setup:crm` runs end-to-end. No Inertia pages.
> **Done-when:** `setup:crm --profile=taskco-crm-education` seeds 13 lead fields + 2 pipelines + 1 rule. Idempotent on re-run.

---

## Implementation Order

| Step | File | Spec |
|------|------|------|
| 1.1 | Migrations (all 16) | Run together — see `_core/database.md` |
| 1.2 | FieldEngine | [`engines/field-engine.md`](../engines/field-engine.md) |
| 1.3 | PipelineEngine | [`engines/pipeline-engine.md`](../engines/pipeline-engine.md) |
| 1.4 | ActivityEngine | [`engines/activity-engine.md`](../engines/activity-engine.md) |
| 1.5 | WorkflowEngine | [`engines/workflow-engine.md`](../engines/workflow-engine.md) — **read loop guard before coding**. Includes `workflow:tick` command + `scheduled` trigger type. |
| 1.6 | EntityEngine | [`engines/entity-engine.md`](../engines/entity-engine.md) — **includes `entity_relationships` seed + `EntityRegistry::convertsTo()` helper** (used by Lead convert in Phase 2.1). |
| 1.7 | LabelEngine | [`engines/label-engine.md`](../engines/label-engine.md) |
| 1.8 | Proposal scaffold | Migrations only (see `_core/database.md`) + `ProposalService` stub |
| 1.9 | Status Enums | `LeadStatusEnum` (40-44), `DealStatusEnum` (50-53) in `app/Enums/` |
| 1.10 | Feature packs (JSON) | Create all 5 JSON files in `AdminApp/data/crm/feature-packs/` — see `packs/*.md` |
| 1.11 | `FeaturePackSeeder` | [`packs/overview.md`](../packs/overview.md) |
| 1.12 | `CrmEntityTypeSeeder` | [`engines/entity-engine.md`](../engines/entity-engine.md) |
| 1.13 | `setup:crm` command | [`setup.md`](../setup.md) |
| 1.14 | Product profiles (PHP) | [`setup.md`](../setup.md) — 5 PHP files |
| 1.15 | AppRegistry wiring | [`setup.md`](../setup.md) — CoreApp.php + CrmApp.php + uncomment in registry |

## `CoreAppServiceProvider` bindings (all at once in 1.15)
```php
public function register(): void
{
    $this->app->singleton(\CoreApp\Services\FieldEngine\CustomFieldResolver::class);
    $this->app->singleton(\CoreApp\Services\EntityEngine\EntityRegistry::class);
    $this->app->singleton(\CoreApp\Services\LabelEngine\LabelResolver::class);
    $this->app->singleton(\CoreApp\Services\WorkflowEngine\WorkflowDispatcher::class);
    $this->app->bind(
        \CoreApp\Contracts\AiHookInterface::class,
        \CoreApp\Services\WorkflowEngine\NullAiHook::class
    );
}
```

---

## Done-When Checklist

- [ ] `php artisan migrate --pretend` prints all 16 new tenant tables (no errors)
- [ ] `php artisan setup:crm --profile=taskco-crm-education --dry-run` exits 0
- [ ] `php artisan setup:crm --profile=taskco-crm-education` seeds: 13 lead fields, 7 deal fields, 8 contact fields, 2 pipelines, 1 workflow rule
- [ ] `php artisan setup:crm --profile=taskco-crm-general` seeds: 1 lead field, 2 deal fields, 2 pipelines, 1 workflow rule
- [ ] Running `setup:crm` twice leaves row counts unchanged
- [ ] `grep -r 'delete()\|truncate()\|destroy(' CoreApp/database/seeders/` returns empty
- [ ] `LeadStatusEnum::CONVERTED->value` + `DealStatusEnum::WON->value` don't collide with `StatusEnum` values
- [ ] `WorkflowDispatcher` has `static array $running` property (grep confirms)
- [ ] `EntityRegistry::convertsTo('lead')` returns `CrmApp\Deal\Models\Deal::class` after `CrmEntityTypeSeeder` runs
- [ ] `php artisan workflow:tick` registered + listed in `php artisan list workflow`
- [ ] `app/Console/Kernel.php` schedule includes `workflow:tick` every 5 minutes

## Unit Tests
```
tests/Unit/CRM/FeaturePackSeederIdempotencyTest.php
tests/Unit/CRM/WorkflowDispatcherLoopGuardTest.php
tests/Unit/CRM/LabelResolverTest.php
tests/Unit/CRM/CustomFieldResolverTest.php
tests/Unit/CRM/EntityRegistryTest.php           // covers convertsTo() + capability lookup
tests/Unit/CRM/WorkflowTickCommandTest.php      // covers cron match + condition evaluation + dispatch
tests/Unit/CRM/ConditionEvaluatorRelativeTimeTest.php  // covers now-7d, now+1h tokens
```
