---
id: CORE-BOOT
tipo: arquitetura
onda: 1
status: concluido
unidades: [CORE-BOOT]
fontes:
  - V2/Core/Providers/
  - V2/Core/Contracts/
  - V2/Core/Console/
  - V2/Core/README.md
depende_de: []
consumidores: [CORE-AUTH, CORE-CMS, CORE-DATA, CORE-RUNTIME, MOD-INDEX, todos os MOD-*, todos os SVC-*]
criticidade: alta
dono: BootNavigation
---

# Arquitetura: boot, auto-descoberta e contrato de módulo

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Uma pasta em `V2/Modules` ou `V2/Services` passa a existir no runtime (rotas, views, migrations) sem nenhum registro manual. |
| Entrada | `boot()` do `V2\Core\Providers\V2ServiceProvider`, chamado uma vez por ciclo de vida da aplicação. |
| Saída | Tabela de rotas montada em ordem determinística; namespaces de view `v2-<modulo>` e `v2-service-<servico>`; migrations carregadas; binding `v2.modules.configs`. |
| Efeitos colaterais | `File::directories()` sobre `V2/Modules` e `V2/Services`; consultas `Schema::hasTable` e seed de busca no console; log de warning em falha de sincronização. |
| Particularidade central | O nome do diretório é a chave de tudo: namespace PHP, namespace de view, prefixo de API e chave de permissão. O `group` é injetado a partir do disco e sobrescreve o `getConfig()`. |
| Como se integra | `V2\Core\Contracts\ModuleProviderInterface`; `ModuleProvider.php` por módulo; pastas `Migrations/`, `Views/`, `Routes/`. |
| Se removido, quebra | Nenhuma rota é registrada no sistema (não existe `RouteServiceProvider` substituto). |
| Superfície pública | `boot()`, `register()`, contratos `ModuleProviderInterface` e `RegionalOwnable`, comando `v2:clear-page-cache`. |

## 1. Efeito

Depois do boot:

1. Existem as rotas do Core (`/busca`, `/login`, `/cms/*`, `/{slug}`).
2. Existem as rotas de cada módulo, com namespace de controller `V2\Modules\<Grupo>\<Modulo>\Controllers`, views em `v2-<modulo-minusculo>` e migrations registradas.
3. Todo módulo que declara `ModuleProvider implements ModuleProviderInterface` está no array `v2.modules.configs`, indexado pelo **nome do diretório**, com `group` igual ao **nome do diretório do grupo**.
4. Existem as rotas de cada serviço, com namespace `V2\Services\<Servico>\Controllers`, views em `v2-service-<servico-minusculo>` e API prefixada em `api/v2/<slug>`.
5. No console, páginas `BasicPage` sem linha correspondente em `v2_public_search_index` são sincronizadas; no HTTP, isso não ocorre.

O que **não** acontece:

- O provider não cria prefixo nem nome de rota para rotas CMS de módulo; ele só aplica middleware e namespace. O `prefix` e o `name` vêm do arquivo `Routes/cms.php` (`V2/Modules/_Exemplos/ExemploAPI/Routes/cms.php:20-31`).
- O provider não valida acessibilidade, não compila assets e não registra comandos artisan; comandos são registrados em `app/Console/Kernel.php:14-22`.
- O provider não impede colisão de nomes de módulo entre grupos: a última chave sobrescreve (`V2/Core/Providers/V2ServiceProvider.php:110`).

## 2. Abordagem

### 2.1 Ordem exata de boot

`register()` (executado antes de qualquer `boot()`):

1. `mergeConfigFrom(__DIR__ . '/../../../config/v2.php', 'v2')` (`:345`).
2. `require_once base_path('V2/Core/Helpers/helpers.php')` se existir (`:348-351`).
3. `PermissionService` como singleton **somente** se `V2\Services\ActivityLogger\ActivityLoggerService` existir (`:354-359`).
4. `ModuleFlagService` como singleton incondicional (`:362-364`).

`boot()`:

```mermaid
flowchart TD
  S0[0.0.0 diretivas @v2can/@endv2can] --> S1[0. layouts globais v2-cms e v2-portal]
  S1 --> S2[0.0.1 SidebarComposer em v2-cms::layouts.sidebar]
  S2 --> S3[0.0.2 composer global de v2-portal::*]
  S3 --> S4[0.0.1 migrations do Core]
  S4 --> S5[0.1 rotas do Core]
  S5 --> S6[1. módulos: provider, migrations, views, rotas]
  S6 --> S7[instance v2.modules.configs]
  S7 --> S8[1.5 fallback GET /slug]
  S8 --> S9[booted: refreshNameLookups + refreshActionLookups]
  S9 --> S10[console: sync BasicPage -> v2_public_search_index]
  S10 --> S11[2. serviços: migrations, views, rotas]
```

A ordem é documentada pelo próprio código: `// 1.5 Fallback Route (deve ser registrado APÓS todos os módulos...` (`:135-137`).

### 2.2 Algoritmo de descoberta (reproduzível)

```
para cada groupPath em File::directories(base_path('V2/Modules')):
    groupName = basename(groupPath)                       # ex.: "Publicacoes"
    para cada modulePath em File::directories(groupPath): # ordem retornada pelo FS
        moduleName = basename(modulePath)                 # ex.: "Noticias"
        moduleNamespace = strtolower(moduleName)          # "noticias"
        providerClass = "V2\\Modules\\{groupName}\\{moduleName}\\ModuleProvider"
        se class_exists(providerClass):
            provider = new providerClass()
            se provider instanceof ModuleProviderInterface:
                config = provider.getConfig()
                config['group'] = groupName               # sobrescreve
                moduleConfigs[moduleName] = config        # chave = nome do módulo
        se existe modulePath/Migrations: loadMigrationsFrom(...)
        se existe modulePath/Views:      loadViewsFrom(..., 'v2-' + moduleNamespace)
        mapModuleRoutes(moduleName, modulePath, groupName)
```

Fontes: `V2/Core/Providers/V2ServiceProvider.php:92-130,287-313`.

Observações de fidelidade:

- **Ordem**: `File::directories()` instancia `Symfony\Component\Finder\Finder` com `directories()->depth(0)` e **não** chama `sortByName()` (`vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php:416-425`; `vendor/symfony/finder/Finder.php:51,730-733`); a ordem efetiva é a do `RecursiveDirectoryIterator`, sem ordenação explícita. O comentário do `Core/README.md` afirma "carrega módulos em ordem alfabética" (`V2/Core/README.md:20`); o código **não** ordena. [C] sobre a ausência de `sort()`/`sortByName()`; [I] que a ordem seja alfabética no filesystem de produção — não é garantida entre sistemas de arquivos.
- **Namespace de controller**: `"V2\\Modules\\{$groupName}\\{$moduleName}\\Controllers"` (`:296`).
- **Prefixo de API de módulo**: `'api/v2/' . strtolower($moduleName)` (`:310`).
- **Rotas CMS de módulo**: middleware `['web', 'module.active:' . $moduleName]` (`:304`); não inclui `CmsAuthenticate` — cada arquivo de rotas deve declará-lo.
- **Rotas web de módulo**: `['web', MaintenanceMiddleware::class, 'page.cache', 'public.cache']` (`:298`).

Para serviços (`:168-191`):

```
para cada servicePath em File::directories(base_path('V2/Services')):
    serviceName = basename(servicePath)
    migrations, views 'v2-service-' + strtolower(serviceName)
    mapServiceRoutes(serviceName, servicePath):

cms.php: middleware('web')                                   + namespace V2\Services\<S>\Controllers
web.php: middleware(['web','maintenance','page.cache','public.cache'])
api.php: prefix 'api/v2/' + getServiceApiSlug(serviceName)   + middleware('api')
```

`getServiceApiSlug()` remove o sufixo `Service` (7 caracteres) e aplica `strtolower` (`:239-251`): `MailService → mail`, `Gallery → gallery`, `FeedRSS → feedrss`.

### 2.3 Trechos de código pivô

```php
// V2/Core/Providers/V2ServiceProvider.php:92-110 — descoberta e injeção de grupo.
$groups = File::directories($modulesPath);

foreach ($groups as $groupPath) {
    $groupName = basename($groupPath);
    $modules = File::directories($groupPath);

    foreach ($modules as $modulePath) {
        $moduleName = basename($modulePath);
        $moduleNamespace = strtolower($moduleName);

        $providerClass = "V2\\Modules\\{$groupName}\\{$moduleName}\\ModuleProvider";
        if (class_exists($providerClass)) {
            $provider = new $providerClass();
            if ($provider instanceof \V2\Core\Contracts\ModuleProviderInterface) {
                $config = $provider->getConfig();
                $config['group'] = $groupName;
                $moduleConfigs[$moduleName] = $config;
```

Mostra os três invariantes de identidade (nome do módulo, namespace e chave do mapa) e que a classe do provider é resolvida por string construída, sem PSR-4 explícito no código.

```php
// V2/Core/Providers/V2ServiceProvider.php:112-125 — migrations e views por convenção.
$migrationPath = $modulePath . '/Migrations';
if (File::exists($migrationPath)) {
    $this->loadMigrationsFrom($migrationPath);
}

$viewPath = $modulePath . '/Views';
if (File::exists($viewPath)) {
    $this->loadViewsFrom($viewPath, 'v2-' . $moduleNamespace);
}
```

`loadViewsFrom` **acrescenta** o path ao namespace (`FileViewFinder::addNamespace` faz append — `vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php:182-191`); se dois módulos homônimos em grupos diferentes colidirem no namespace `v2-<nome>`, o primeiro path registrado vence e o segundo fica inalcançável.

```php
// V2/Core/Providers/V2ServiceProvider.php:133-142 — publicação do mapa e fallback.
$this->app->instance('v2.modules.configs', $moduleConfigs);

// 1.5 Fallback Route (deve ser registrado APÓS todos os módulos,
//     para que NUNCA bloqueie rotas específicas como /noticias, /busca, etc.)
$this->mapFallbackRoutes();

$this->app->booted(function () {
    $this->app['router']->getRoutes()->refreshNameLookups();
    $this->app['router']->getRoutes()->refreshActionLookups();
});
```

`refreshNameLookups()` reconstrói o mapa nome→rota **na ordem de registro**; `refreshActionLookups()` reconstrói ação→rota. Sem essas chamadas, rotas registradas por `Route::group($arquivo)` não entrariam nos lookups de `route()`/`action()`. Com o refresh, nomes duplicados ficam com a **última** rota registrada (`RouteCollection::refreshNameLookups` percorre a coleção e sobrescreve `nameList[nome]`).

```php
// V2/Core/Providers/V2ServiceProvider.php:145-163 — seed de busca apenas no console.
if (class_exists(\V2\Modules\Layouts\BasicPage\Models\BasicPage::class)
    && $this->app->runningInConsole()
    && \Illuminate\Support\Facades\Schema::hasTable('v2_basic_pages')
    && \Illuminate\Support\Facades\Schema::hasTable('v2_public_search_index')
) {
    try {
        $unsynced = \V2\Modules\Layouts\BasicPage\Models\BasicPage::whereNotExists(function ($query) {
            $query->select(\Illuminate\Support\Facades\DB::raw(1))
                   ->from('v2_public_search_index')
                  ->whereRaw('v2_public_search_index.entity_id = v2_basic_pages.id');
        })->get();

        foreach ($unsynced as $page) {
            $page->syncToSearch();
        }
```

O `whereNotExists` compara **apenas** `entity_id`, sem filtrar `entity_type`; qualquer linha de índice de outra entidade com o mesmo UUID (improvável por causa de UUID, mas possível após importação) impede a sincronização.

```php
// V2/Core/Providers/V2ServiceProvider.php:203-231 — rotas de serviço.
if (File::exists($cmsRoutes)) {
    Route::middleware('web')
        ->namespace($controllerNamespace)
        ->group($cmsRoutes);
}

if (File::exists($webRoutes)) {
    Route::middleware(['web', \V2\Core\Middleware\MaintenanceMiddleware::class, 'page.cache', 'public.cache'])
        ->namespace($controllerNamespace)
        ->group($webRoutes);
}

if (File::exists($apiRoutes)) {
    Route::prefix('api/v2/' . $this->getServiceApiSlug($serviceName))
        ->middleware('api')
        ->namespace($controllerNamespace)
        ->group($apiRoutes);
}
```

O provider **não** aplica `CmsAuthenticate` nem `module.active` às rotas CMS de serviço: a proteção é responsabilidade do arquivo. `V2/Services/Gallery/Routes/cms.php:5` não declara middleware de autenticação, enquanto `ContentRichEditor` e `LandingSectionsEditor` declaram (`.../ContentRichEditor/Routes/cms.php:9`).

```php
// V2/Core/Providers/V2ServiceProvider.php:239-251 — slug de API de serviço.
$slug = $serviceName;
if (substr($slug, -7) === 'Service') {
    $slug = substr($slug, 0, -7);
}
return strtolower($slug);
```

Consequência: `MailService` publica em `api/v2/mail`, mas `ActivityLogger` publicaria em `api/v2/activitylogger`; não há conversão de CamelCase para kebab-case.

### 2.4 Decisões e restrições

- **Descoberta por filesystem** substitui configuração. O custo é I/O de diretórios por boot. Não há `App\Providers\RouteServiceProvider` nem `routesAreCached()` no projeto; portanto nada carrega `bootstrap/cache/routes.php`, e o provider mapeia as rotas em todo boot mesmo que `route:cache` seja executado. [C] `config/app.php:144-187`; ausência de `grep routesAreCached` em `app/`, `bootstrap/`, `config/` e `V2/`.
- **`group` vem do disco** para que mover a pasta mude o grupo sem editar código (`:109`).
- **Fallback por último**, porque a ordem de registro é a ordem de matching no Laravel 5.5.
- **`refreshNameLookups` dentro de `booted`** substitui o `RouteServiceProvider` padrão, que não existe no projeto.
- **Módulos não recebem `CmsAuthenticate` automaticamente**; o contrato está no README (`V2/Modules/README.md:30`), não no provider.
- **Compatibilidade**: `$namespace` é propriedade morta (`:20`) — nenhuma referência além da declaração; o namespace é passado por grupo de rotas.

## 3. Particularidades

### 3.1 O que só existe aqui

- **Descoberta de dois níveis** (grupo → módulo), com `group` injetado. Nenhum outro ponto do sistema sabe a que grupo um módulo pertence.
- **Namespace de view sem normalização de case**: `strtolower($moduleName)` (`:100`). `BasicPage` → `v2-basicpage`; `ParceirosInstitucionais` → `v2-parceirosinstitucionais`.
- **Serviços descobertos por pasta também**, mas com prefixo de API derivado do nome e remoção do sufixo `Service` (`:239-251`).
- **Sidebar tolerante a falha**: o `View::composer` é registrado dentro de `try/catch` que engole exceção com o comentário `// Sidebar não carregada — módulos ainda não disponíveis` (`:46-53`).
- **Composer global do portal** faz fallback silencioso para `app.name` quando `PortalConfigsService` falha (`:56-78`).

### 3.2 Invariantes

| ID | Regra | Efeito | Fonte |
|---|---|---|---|
| RN-CORE-BOOT-01 | `config['group']` é sempre o diretório do grupo. | Mover módulo de grupo muda menu e bloqueio Support/API. | `V2/Core/Providers/V2ServiceProvider.php:109` |
| RN-CORE-BOOT-02 | `moduleConfigs` é indexado pelo nome do módulo. | Dois módulos homônimos colidem. | `:110` |
| RN-CORE-BOOT-03 | View namespace é `v2-` + nome do módulo em minúsculas. | `v2-exemploapi`, nunca `v2-exemplo-api`. | `:100,123` |
| RN-CORE-BOOT-04 | Fallback é registrado após módulos e antes de serviços. | `/{slug}` não sombreia rotas de módulo. | `:135-137,168-191` |
| RN-CORE-BOOT-05 | Rotas CMS de módulo têm `module.active:<Modulo>`, mas não `CmsAuthenticate`. | Desativar módulo redireciona/404; autenticar é dever do arquivo. | `:304` |
| RN-CORE-BOOT-06 | Flag ausente em `v2_module_flags` = módulo ativo. | Instalação nova não bloqueia módulos. | `V2/Core/Services/ModuleFlagService.php:93-94` |
| RN-CORE-BOOT-07 | `v2.modules.configs` só é vinculado após a varredura de módulos. | Middleware/composer que rode antes vê mapa vazio. | `:133` |
| RN-CORE-BOOT-08 | Migrations do Core precedem as de módulos e serviços. | Ordem de criação de tabelas/funções (`v2_gen_uuid`). | `:79-82,114-118,178-183` |

### 3.3 Casos-limite

- **Diretório de grupo sem `ModuleProvider`**: módulo existe para rotas/views, mas não no menu nem no RBAC. Não há erro.
- **`ModuleProvider` que lança exceção no `getConfig()`**: exceção não é capturada; boot falha (`:104-110`).
- **Arquivo `Routes/cms.php` sem middleware**: a rota fica sob o grupo `web`, mas **sem autenticação nem checagem de papel** — o grupo `web` só provê sessão/CSRF, e o CSRF ainda pode estar isento. Exemplo atual: `V2/Services/Gallery/Routes/cms.php:5-13` registra `cms/v2/gallery` sem `CmsAuthenticate`; `GalleryController` não tem guarda de sessão, papel ou `v2_can()` (`V2/Services/Gallery/Controllers/GalleryController.php:33-36`); e `app/Http/Middleware/VerifyCsrfToken.php:15-17` isenta `cms/v2/gallery/*`, deixando `rotate`, `archive` e `destroy` alcançáveis por cliente anônimo sem `_token`.
- **Módulo desativado + requisição CMS não-AJAX**: redireciona para `v2.cms.dashboard` com `Módulo "<Nome>" está desativado. Ative-o em Suporte Técnico > Módulos.` (`V2/Core/Middleware/ModuleAccessMiddleware.php:56-58`). Para API/portal retorna 404 (JSON com `"error": "Modulo desativado: <Nome>", "code": 404`) (`:48-62`).
- **Sem tabela `v2_module_flags`**: `getAllFlags()` captura a exceção e assume lista vazia; `isActive()` devolve `true` (`ModuleFlagService.php:58-61,93-94`).
- **Duas chamadas de boot em um mesmo processo** (ex.: testes): `app->instance` e `loadViewsFrom` são idempotentes; já o seed de busca roda de novo, mas o `whereNotExists` evita duplicação.

### 3.4 Armadilhas de reimplementação

1. **Não registrar o fallback antes dos módulos** — `/noticias` deixaria de funcionar; o catch-all só aceita `[a-z0-9-]+` (sem barras), então rotas profundas sobreviveriam, mas as de um segmento não.
2. **Normalizar nome de view para kebab-case** — quebra todas as views existentes.
3. **Ordenar módulos alfabeticamente** — hoje a ordem é do FS; se a reimplementação ordenar, o comportamento de colisão de nomes de rota/views muda (a última rota vence no lookup).
4. **Esquecer `refreshNameLookups`** — `route()` pode falhar com `Route [x] not defined` em rotas registradas em arquivos carregados por `Route::group($file)`.
5. **Achar que serviço CMS é protegido pelo provider** — não é; a proteção depende do arquivo (ver Gallery).

### 3.5 Divergências documentação × código

| Documento | Afirma | Código |
|---|---|---|
| `V2/Core/README.md:20` | "carrega módulos em ordem alfabética" | Sem `sort`; ordem do filesystem (`:92-96`). |
| `V2/Core/README.md:13` | `Middleware/CmsAuthenticate.php` garante acesso administrativo às rotas de `cms.php` | O provider não aplica esse middleware a rotas CMS de módulo nem de serviço (`:203-214,304`). |
| `V2/Core/README.md:12` | `Providers/V2ServiceProvider.php` "faz o Auto-Discovery ... e mapeia as rotas separadas por contexto" | Confirmado, com a ressalva de que prefixo/nome são do arquivo de rotas. |
| `V2/Modules/README.md:26` | "Registre no namespace `v2-[slug]`" | O registro é automático com o nome em minúsculas; não há `slug` configurável. |
| `V2/Modules/README.md:30` | `cms.php` "deve sempre usar `CmsAuthenticate`" | Não há enforcement; Gallery não usa. |

## 4. Integrabilidade

### 4.1 Contrato para módulos e serviços

| Item | Módulo | Serviço |
|---|---|---|
| Diretório | `V2/Modules/<Grupo>/<Modulo>/` | `V2/Services/<Servico>/` |
| Namespace de classe | `V2\Modules\<Grupo>\<Modulo>\...` | `V2\Services\<Servico>\...` |
| Provider | `ModuleProvider.php` + `ModuleProviderInterface` | Opcional; não é descoberto |
| Views | `Views/` → `v2-<modulo-minusculo>` | `Views/` → `v2-service-<servico-minusculo>` |
| Migrations | `Migrations/` | `Migrations/` |
| Rotas CMS | `Routes/cms.php` → `web` + `module.active:<Modulo>` | `Routes/cms.php` → apenas `web` |
| Rotas web | `Routes/web.php` → `web` + maintenance + caches | idem |
| Rotas API | `Routes/api.php` → `api` + prefixo `api/v2/<modulo-minusculo>` | `api` + prefixo `api/v2/<slug-sem-Service>` |
| Namespace controller | `V2\Modules\<Grupo>\<Modulo>\Controllers` | `V2\Services\<Servico>\Controllers` |

### 4.2 Como adicionar um módulo (receita)

1. Criar `V2/Modules/MeuGrupo/MeuModulo/`.
2. Criar `ModuleProvider.php`:

```php
namespace V2\Modules\MeuGrupo\MeuModulo;

use V2\Core\Contracts\ModuleProviderInterface;

class ModuleProvider implements ModuleProviderInterface
{
    public function getConfig(): array
    {
        return [
            'title' => 'Meu Módulo',
            'icon' => 'bi bi-box',
            'position' => 10,
            'active_pattern' => 'cms/meu-modulo*',
            'route' => 'v2.meumodulo.index',
            'subModulesLevel1' => [[
                'title' => 'Listagem',
                'route' => 'v2.meumodulo.index',
                'position' => 1,
                'subModulesLevel2' => [],
            ]],
        ];
    }
}
```

3. Criar `Routes/cms.php` com `prefix` e `name` explícitos e `CmsAuthenticate` no grupo.
4. Criar `Views/cms/index.blade.php` e referenciar como `v2-meumodulo::cms.index`.
5. Nada a registrar em `config/app.php`.

**Resultado esperado**: item de menu no grupo `MeuGrupo`; `GET /cms/meu-modulo` autenticado; view resolvida; rota sujeita a `module.active:MeuModulo`.

### 4.3 Como adicionar um serviço (receita)

1. Criar `V2/Services/MeuServico/`.
2. Opcionalmente `Routes/api.php`; o provider prefixa `api/v2/meuservico` e aplica `api`.
3. Views em `Views/`, referenciadas como `v2-service-meuservico::...`.
4. Se houver rota CMS, **declarar** `CmsAuthenticate` no arquivo (o provider não injeta).
5. Nenhum `ModuleProvider` é necessário; serviços não aparecem no menu.

### 4.4 Comando `v2:clear-page-cache`

Assinatura `v2:clear-page-cache`, descrição `Apaga as chaves laravel:page:* do Redis (cache de páginas do portal).` (`V2/Core/Console/Commands/ClearPageCache.php:21-23`). Efeito: `KEYS <cache.prefix>:page:*` e `DEL`; sem chaves imprime `Cache de páginas: nenhuma chave encontrada (<pattern>).` e retorna 0; em falha imprime `Falha ao limpar cache de páginas: <mensagem>`, loga warning e retorna 1 (`:25-44`). Registro: `app/Console/Kernel.php:16`.

### 4.5 Anti-uso

1. **Esperar que `CmsAuthenticate` seja automático** — um módulo/serviço novo sem middleware no arquivo fica acessível a qualquer sessão `web`.
2. **Depender da ordem de descoberta para resolver colisão de nome de rota** — a ordem não é garantida (`File::directories`).
3. **Colocar lógica de negócio no `register()`** — ele roda antes de views/rotas e antes de `v2.modules.configs` existir; qualquer acesso ao mapa falha.

## 5. Inventário

| Arquivo | Papel | Efeito | Fonte |
|---|---|---|---|
| `V2/Core/Providers/V2ServiceProvider.php` | Motor de boot | Registra views, migrations, rotas, mapa de módulos | `:27-364` |
| `V2/Core/Contracts/ModuleProviderInterface.php` | Contrato de módulo | `getConfig(): array` obrigatório para menu/RBAC | `:6-14` |
| `V2/Core/Contracts/RegionalOwnable.php` | Contrato regional | Escopo por unidade sem acoplamento do Core | `:12-38` |
| `V2/Core/Console/Commands/ClearPageCache.php` | Comando | Limpa `laravel:page:*` no Redis | `:19-45` |
| `V2/Core/README.md` | Documentação | Declara papéis e ordem (com divergência) | `:1-20` |

## 6. Riscos de reimplementação

1. **Segurança**: rotas CMS de serviço sem middleware (Gallery) — decidir se a reimplementação reproduz a falha ou corrige (impacto de paridade).
2. **Cache de rotas**: sem `RouteServiceProvider`, `route:cache` não tem efeito de carregamento; uma reimplementação que adicione o provider padrão passaria a servir rotas cacheadas e ignoraria mudanças de pasta até novo `route:cache`.
3. **Colisão silenciosa** de módulos homônimos em grupos distintos.
4. **Seed de busca no console** pode ficar caro com muitas páginas; roda a cada comando artisan.
5. **Ordem de migrations** entre Core, módulos e serviços depende do boot; migrations de serviço são carregadas por último.

## 7. Lacunas

- **LG-CORE-BOOT-01** — Não foi possível determinar se `route:cache` é executado em produção. Como não existe `RouteServiceProvider`/`routesAreCached()` no código, o cache gerado não é lido; confirmar em `docker/`, `CI_CD.md` e scripts de deploy.
- **LG-CORE-BOOT-02** — O comportamento de `refreshNameLookups` em colisão de nomes (última vence) foi inferido da implementação do Laravel 5.5, não observado em execução. [I]
- **LG-CORE-BOOT-03** — A ordem real de `File::directories()` no filesystem de produção (ext4/overlay) não foi medida; o código não ordena.

## 8. Checklist de paridade

- [ ] `PC-CORE-BOOT-01` — Criar módulo sem `ModuleProvider` com `Routes/web.php`; a rota deve responder. Fonte: `V2ServiceProvider.php:104-130`.
- [ ] `PC-CORE-BOOT-02` — `ModuleProvider` com `group` divergente do diretório não deve mudar o grupo exibido. Fonte: `:109`.
- [ ] `PC-CORE-BOOT-03` — Rota CMS de módulo desativado em `v2_module_flags` deve redirecionar para o dashboard com a mensagem de `ModuleAccessMiddleware.php:58`. Fonte: `:304`; `ModuleAccessMiddleware.php:56-58`.
- [ ] `PC-CORE-BOOT-04` — Flag inexistente deve manter módulo ativo. Fonte: `ModuleFlagService.php:93-94`.
- [ ] `PC-CORE-BOOT-05` — `GET /{slug}` deve ir para `BasicPagePortalController@runtime` somente depois de todas as rotas de módulo. Fonte: `fallback.php:19-21`.
- [ ] `PC-CORE-BOOT-06` — `route('v2.exemplo-api.index')` deve funcionar após o boot. Fonte: `:139-142`.
- [ ] `PC-CORE-BOOT-07` — `php artisan v2:clear-page-cache` sem Redis deve imprimir `Falha ao limpar cache de páginas: ...` e retornar 1. Fonte: `ClearPageCache.php:40-43`.
- [ ] `PC-CORE-BOOT-08` — Serviço `MailService` deve publicar API em `api/v2/mail`. Fonte: `:239-251`.

## 9. Evidências consultadas

- `V2/Core/Providers/V2ServiceProvider.php:1-366`
- `V2/Core/Contracts/ModuleProviderInterface.php:1-14`
- `V2/Core/Contracts/RegionalOwnable.php:1-39`
- `V2/Core/Console/Commands/ClearPageCache.php:1-46`
- `V2/Core/README.md:1-20`
- `V2/Core/Routes/fallback.php:1-21`
- `V2/Core/Middleware/ModuleAccessMiddleware.php:1-67`
- `V2/Core/Services/ModuleFlagService.php:42-95`
- `V2/Core/ViewComposers/SidebarComposer.php:20-101`
- `V2/Modules/_Exemplos/ExemploAPI/{ModuleProvider.php,Routes/cms.php}`
- `V2/Services/Gallery/Routes/cms.php:1-13`; `V2/Services/MailService/Routes/api.php:15-27`; `V2/Services/ContentRichEditor/Routes/cms.php:9`
- `config/app.php:171-187`; `config/v2.php:4-11`; `app/Console/Kernel.php:14-22`
