# F1 — Fundação (governança · esqueleto · runtime · qualidade · observabilidade)

> **Épico:** `F1` · **Depende de:** F0 · **Habilita:** F2 · **Planos:** 02, 03, 04, 05, 06

## Objetivo

Ter o esqueleto Laravel 12 + `/src`, os containers reais (web/FPM/queue/PG18/Valkey/RustFS), os gates de
qualidade e a observabilidade, tudo verde — a base sobre a qual todo o domínio será construído.

## Issues e tasks

### `F1.I1` — Governança (plano 02)
- **T1** `AGENTS.md` vivo + `CONTRIBUTING.md` + `wiki/DESIGN.md` (stub) + convenções.
- **T2** Specs vivas: `08-ia-e-qualidade.md` (determinismo), `09-grep-friendly.md`, `docs-check`.
- **T3** Blueprint como fonte de stubs (`make:module`) e política vermelho→verde.

### `F1.I2` — Esqueleto (plano 03)
- **T1** `composer create-project laravel/laravel:^12` na raiz; limpar scaffold.
- **T2** Autoload PSR-4 das 4 camadas; `app/` só bootstrap; remover Kernel/User default.
- **T3** `bootstrap/app.php`: `/up` (+ `DiagnosingHealth`), `trustProxies`, `trustHosts`,
  `throttleWithRedis`, `forceScheme('https')` por config.
- **T4** `src/modules/Exemplos/Blueprint/` completo (rota, controller, action, model, migration,
  view, teste) + `phpunit.xml` por camada.

### `F1.I3` — Runtime (plano 04 / spec 16 + 17)
- **T1** `docker/php-fpm` Dockerfile `FROM php:8.4-fpm-trixie` (pin por digest) + extensões/oficiais
  + extensão nativa `blake3` (ExinOne, Apache-2.0) + `user_ini.filename=` etc. montados.
- **T2** `docker/web/Caddyfile` (validação `caddy validate/fmt`) + `docker/web/app.conf`.
- **T3** `docker-compose.yml`: `web`(non-root :8080) · `php-fpm` · `queue` · `postgres`(sem porta)
  · `valkey`(sem porta) · **`storage`(RustFS S3, sem porta)** + profiles `debug`/`telemetry`.
- **T4** `up_prod.sh` (lock, rollback, `optimize`, `queue:restart`, healthcheck, `caddy
  validate`+`restart`). **Validado** (spec 17 §9): `/up` 200, idempotente 2×, `flock`, rollback em
  falha (sem reset em `--allow-dirty`), config/route/view/event cacheados, `opcache.validate_timestamps=Off`.
- **T5** Storage S3 (`config/filesystems.php` disco `s3` + `storage:ensure-bucket` idempotente) e local
  (`STORAGE_PATH`/`DATA_PATH`); ignorar arquivos do cPanel. **Validado**: bucket `public-v7` criado
  (2ª execução idempotente) e `put/get/exists/allFiles` contra o RustFS. Correção de base: registrar
  `Core\CoreServiceProvider` em `bootstrap/providers.php` (sem ele, migrações/auditoria/comandos do Core
  não carregavam).

### `F1.I4` — Qualidade (plano 05)
- **T1** `phpstan.neon` (**max** + Larastan) + baseline (213) com **ratchet** (`tools/phpstan-ratchet.sh`);
  `pint.json` (strict types em todo PHP, inclusive `config/`/`public/`); `.editorconfig`.
  Correção: `blake3()` → `blake3_hash()` (ADR-029) em `AuditService`/`HashingService`/view Demo.
- **T2** Gates `tools/arch-check` (php-parser, matriz de camadas), `tools/name-check.sh` (ADR-004),
  `tools/migration-check` (ADR-017/028) e `tools/vendor-check.sh` (zero-CDN + licenças via
  `tools/licenses-check.php`); `make check/check-grep/check-migrations/check-vendor/check-gates`.
  Selftest em `tools/tests/fixtures` (falham de propósito) + árvore real; `THIRD_PARTY_LICENSES` (116 pacotes).
  Exceções justificadas registradas: `AreaRegistrar` (SEO core→services, F2), `Noticias.content`
  (rich-jsonb, F4/ADR-028), Google Fonts/gtag/VLibras (vendorização/consent, F3).
- **T3** `make check` (lint Pint+Prettier/ESLint · PHPStan max+ratchet · gates · PCOV clover/cobertura)
  e `make check` (anti-flaky). `phpunit.xml` com suites por camada (Unit/Feature/Core/Services/
  Layout/Modules) + `concordia_test` (`tools/test-db.sh`); `CoreTestCase` exige sufixo `_test`.
  `.prettierrc.json`/`eslint.config.js` + hook `.githooks/pre-commit`. `.gitlab-ci.yml`
  (`runtime → lint → static → test → e2e → build → deploy`, shell runner, cache + artefato de cobertura).
  Eloquent Strict Mode (fora de prod) + `DB::prohibitDestructiveCommands` (prod). Correções nos testes
  legados: wiring RFC 7807 (`bootstrap/app.php`), `MinifyHtml` (comentários), Gate Protocol (handler
  recebe `MessageEvent`), `Mockery` no `SecureMediaStorageTest`; `ModuleDiscoveryTest` skipado até F2.
  Baseline PHPStan 213→210.
- **T4** Gate de otimização `tools/optimize-check` (php-parser): `declare(strict_types=1)` em todo PHP,
  `env()` só em `config/`, zero closure como ação de rota (callbacks de `Route::group` ok). `make check`
  entrou no `quality`; `make check` roda `optimize` efêmero e exige `bootstrap/cache/{config,routes-v7,events}.php`
  + views. `make check` (composer+npm) e SBOM CycloneDX 1.5 determinístico (etapa `sbom` do `make check`).
  Correções de rota sem closure: `ApiStatusController`, `NoticiasAnalyticsController`, `Route::view` no Portal, e
  `api.stub`+`ApiController.stub` (gerador). Baseline PHPStan 210→209.

### `F1.I5` — Observabilidade (plano 06)
- **T1** Observabilidade base: canal `json` (`JsonFormatter` com campos obrigatórios sempre presentes),
  `RequestIdMiddleware` (UUIDv7; preserva `X-Request-ID` válido; `trace_id` via W3C `traceparent`;
  log `http.request` com `duration_ms`/`status`, config `observability.php`), `TraceContext`, `log_event()`
  (helper fino sobre o `Log`, autoload em `composer.json`). Spec `wiki/specs/06-observabilidade.md`
  (log/X-Request-ID prontos; RFC 7807 e OTel em T2/T3). Testes: `JsonFormatterTest`, `RequestIdMiddlewareTest`,
  `LogEventTest` (39 testes). Evidência manual: 1 request → 1 linha JSON com `request_id` no stderr.
- **T2** Handler global → **RFC 7807** (`ProblemDetailsRenderer`, com `trace_id`, já wired em `bootstrap/app.php`)
  e HTML acessível próprio (`ProblemDetailsHtmlRenderer` + `resources/views/errors/problem.blade.php`)
  com código de rastreio, independente do shell do Portal (que depende de F3/Lucide). `pii_redact()`
  (`PiiRedactor`: CPF/CNPJ/e-mail/telefone/token/senha) aplicado no formatter e nos erros. Bug corrigido:
  `Layout\LayoutServiceProvider` não estava registrado em `bootstrap/providers.php` (layout/portal não carregavam).
  Baseline PHPStan 209→208. Testes: `PiiRedactTest`, `ErrorHtmlRenderTest` (50 testes).
- **T3** Correlação `trace_id` (W3C `traceparent` ou gerado) no log, no span e no RFC 7807. Telemetria **opcional**
  atrás de `Core\Contracts\Telemetry` (`NullTelemetry` padrão, zero custo; `OtlpTelemetry`/`OtlpHttpSender`
  = OTLP/HTTP JSON nativo via `ext-curl`, sem SDK pesado). **Fail-open** com circuit breaker em **APCu**
  compartilhado entre workers, timeout curto e cooldown configurável (`config/observability.php` +
  `TELEMETRY_ENABLED`/`OTEL_*`; desligada por padrão e nos testes). Baseline PHPStan mantido em 208.
  Testes: `NullTelemetryTest`, `OtlpTelemetryTest`, `TelemetryFailOpenTest` (59 testes).
  Evidência manual: Jaeger exibe o trace `abcd…`/span `http.server`; com coletor fora, 1ª request paga o
  timeout e as seguintes ficam em ~6ms (breaker APCu).

### `F1.I6` — Ambiente mínimo e perfis de execução (revisão pós-F1.I5)
- **T1** `.env` mínimo (11 vars, identidade + segredos); defaults ideais em `config/`; remover
  `.env.docker.example`; `DATA_PATH`/`STORAGE_PATH` default `./s3-local`/`./storage` (git-ignored);
  documentação em `config/_OTHER_CONFIGS.md`. Evidência: 11 vars; `.env.docker.example` ausente;
  `docker compose config` OK.
- **T2** Perfis por `APP_ENV` (`config/environments.php`): dev/homolog/production (+ testing).
  Entrypoint gera `storage/framework/php-env/zzz-env.ini` (OPcache/`zend.assertions`/display/error_reporting).
  Evidência: dev `opcache.enable=0`, `zend.assertions=1`, `display_errors=On`; produção `opcache.enable=1`,
  `validate_timestamps=0`. Ver **ADR-032**.
- **T3** Logs locais: canal `stack` (`json` stderr + `daily`) com **rotação diária e retenção 730 dias**
  (`storage/logs/concordia-<data>.log`, `JsonFormatter`).
- **T4** `SECURITY_COOKIES` por perfil: `__Host-` + `Secure` no cookie de sessão e
  `session.use_strict_mode`. Evidência: `session.cookie_secure` dev 0 / produção 1.
- **T5** Reset determinístico do banco de teste (`CoreTestCase` derruba schemas não-`public`, ex. `core`,
  antes do `migrate:fresh`). Evidência: 62 testes OK; schema `core` recriado.

### `F1.I7` — Verificação do DoD de entrega (plano 01)
- **T1** `make up` + `/up` 200 → **verificado**.
- **T2** Clone limpo → `up_prod.sh` → `/up` 200 → **pendente** (requer commit; projeto opera "sem commits").
- **T3** `up_prod.sh --dry-run` + idempotência → **verificado** (dry-run EXIT 0; `up -d` 2× sem recriar).
- **T4** `make check` 2× → **verificado** (`quality-2x` EXIT 0, rodadas idênticas).
- **T5** Gates falham em fixtures inválidas → **verificado** (`gates-selftest: OK`).
- **T6** 1 request → 1 log JSON; erro → RFC 7807 → **verificado**.
- **T7** `user_ini` desligado / `.user.ini` ignorado → **verificado**.

## DoD (binário)

- [x] `make up` sobe os 6 serviços; `/up` responde 200 (com PG/Valkey/S3).
- [ ] **Clone limpo funciona:** `git clone` **sem `.import/`/`vendor/`/`node_modules/`** →
      `cp .env.example .env` → `./up_prod.sh` sobe o sistema e `/up` = 200 (contrato de entrega).
      *(Pendente de commit — bloqueado por decisão "sem commits".)*
- [x] `./up_prod.sh --dry-run` mostra o plano; `./up_prod.sh` 2× é idempotente.
- [x] `make check` verde **2× seguidas** (determinismo, ADR-001).
- [x] Gate de fronteiras/otimização falha em fixtures inválidas.
- [x] 1 request → 1 log JSON com `request_id`; erro → RFC 7807.
- [x] `php -i | grep user_ini` desligado; `.user.ini` do cPanel ignorado.

## Evidência

`--note "make check 2x ok; /up 200; clone limpo sobe; up_prod idempotente"`.
