# Spec 18 — Higiene de repositório, contexto Docker e nome derivado

> **Dono:** infra · **Status:** Proposta · **Plano:** 03/15
> **Relaciona:** ADR-003, ADR-005, ADR-006, ADR-032
> **Objetivo:** fixar o contrato de `.gitattributes`/`.gitignore`/`.dockerignore`, a derivação
> determinística do nome do projeto (Compose) a partir de `APP_NAME` e os gates que a verificam.

Especificação viva do "chão de fábrica" do repositório: normalização de EOL, contexto de build
mínimo, segredos fora do Docker e um **único nome** derivado do projeto/tenant. Subordinada aos
ADR-003 (raiz = Laravel típico) e ADR-006 (bind mount + storage local).

## 0. Raiz do repositório — o que pode existir

A raiz é **Laravel típico + runtime + documentação**, nada mais. Qualquer arquivo novo que não
seja framework (`app/`, `bootstrap/`, `config/`, `database/`, `public/`, `storage/`, `tests/`,
`lang/`, `artisan`, `composer.*`, `package.*`, `phpunit.xml`, `pint.json`, `eslint.config.js`),
runtime (`docker/`, `docker-compose*.yml`, `Makefile`, `scripts/update_prod.sh`, `VERSION`, `.env.example`) ou
documento de topo (`AGENTS.md`, `CONTRIBUTING.md`, `README.md`, `wiki/`, `THIRD_PARTY_LICENSES`)
entra em `tools/`, `src/` ou `wiki/` — nunca solto na raiz.

| Item | Onde vive | Nota |
|---|---|---|
| Ferramentas e gates | `tools/` | inclui `tools/stubs/` (stub do PHPStan para a extensão `blake3`) e `tools/infection.json5` |
| Domínio | `src/{core,services,layout,modules}` | stub/extensão de análise **não** entram aqui (ADR-003/030) |
| Documentação concluída | `wiki/backlog/concluido/` | material histórico já executado (`_INFRA/`, `update_docker/`) — preservado, fora do caminho ativo |
| Handoff de sessão | `.STAGING.md` (raiz, dotfile) | único nome canônico; `_STAGING.md`/`STAGING.md` foram consolidados nele |
| Hook de commit | — | **não existe**: `make check` é o caminho único de validação |


## 1. `.gitattributes` — EOL e classificação

- **LF no índice**, independente do SO: primeira regra `* text=auto eol=lf`.
- **Uma única regra por linha.** Nunca agrupar padrões (`*.php *.blade.php text eol=lf`): o git lê
  os globs extras como **nomes de atributo inválidos** e emite *warnings*. Arredondar com
  `git check-attr` (rc = 0, sem stderr).
- Texto por extensão (`*.php`, `*.blade.php`, `*.js`, `*.css`, `*.json`, `*.yaml`, `*.neon`,
  `*.md`, `*.sql`, `*.svg`, `*.sh`, `Makefile`, `scripts/update_prod.sh`, …) → `text eol=lf`.
- Windows: `*.bat`/`*.cmd`/`*.ps1` → `eol=crlf`.
- Binários (`*.png`, `*.avif`, `*.woff2`, `*.pdf`, `*.so`, `*.wasm`, …) → `binary`.
- Artefatos gerados (`composer.lock`, `package-lock.json`, `THIRD_PARTY_LICENSES`,
  `phpstan-baseline.neon`) → `-diff linguist-generated`.
- Dev-only (`.agents/`, `.knudge/`, `.gitlab-ci.yml`, `tests/`, `wiki/`, …) →
  `export-ignore` (fora de `git archive`/distribuição).
- Bloco `knudge:start`/`knudge:end` é **gerenciado por `kd`** — não editar à mão.

## 2. `.gitignore` — o que nunca é versionado

- Segredos: `.env`, `.env.*` — com `!.env.example`.
- Ferramentas/agentes: `/.pi/`, `/docker-compose.override.yml`.
- Derivados do build: `build/`, `/sbom.cdx.json`, `bootstrap/cache/modules_manifest.php`.
- Cache local do Composer na raiz (`/.composer/`): o composer roda **no container**; se rodar no host, o cache cai aqui e é descartável (entrou versionado por engano no commit `e2b2d97`).
- Derivados do knudge: `.idx/`, `cache/`, `.locks/`, `emb_cache.jsonl`.
- Runtime Laravel: `storage/{logs,framework/cache,sessions,views}`, `.phpunit.cache`, `/s3-local/`.
  O diretório de dados mantém um guard **versionado** (`s3-local/.gitignore` = `*` + `!.gitignore`) e
  ele é **recriado** por `tools/data-dirs.sh` sempre que o diretório é (re)criado — `make destroy`
  move o diretório inteiro para `.pi/trash/` e `make podman-prepare`/`scripts/update_prod.sh` o recriam; sem o
  helper o arquivo versionado voltava como deletado (` D s3-local/.gitignore`) e o dado sem ignore.
- sniff: `current/`. Editores: `.idea/`, `.vscode/` (exceto settings versionáveis, quando houver).

## 3. `.dockerignore` — contexto de build mínimo e sem segredos

- A imagem PHP copia **só** `docker/php-fpm/entrypoint.sh` + composer; o código é **bind-montado**
  em runtime (ADR-006).
- Exclui VCS/tooling (`.git`, `.gitattributes`, `.gitignore`, `.dockerignore`, `.editorconfig`,
  `.gitlab-ci.yml`, `.github`), dev-only (`.import`, `.agents`, `.knudge`, `.pi`),
  segredos (`.env`, `.env.*`, `!.env.example`, `auth.json`), dependências/build (`node_modules`,
  `vendor`, `public/build`, `public/hot`, `.composer`), dados locais (`s3-local`, `.data`, `storage/logs`,
  caches), docs (`wiki`, `AGENTS.md`, `CONTRIBUTING.md`, `README.md`, `THIRD_PARTY_LICENSES`,
  `sbom.cdx.json`, `docker-compose*.yml`).
- Verificação: `docker compose build php-fpm` reproduzível e sem `.env` no contexto.

## 4. Nome do projeto derivado (`APP_NAME` + instalação → Compose)

- **Fonte de verdade:** `APP_NAME` (produto) **+** a instalação. O nome do Compose é o **slug**
  do `APP_NAME` seguido de `-<hash8 do caminho físico do checkout>` (ADR-055): o mesmo produto
  instalado para dois clientes no mesmo host **nunca** compartilha containers/redes/volumes.
- Resolvedor único: `tools/project-name.sh` — ordem
  `$COMPOSE_PROJECT_NAME` → `.env` (`COMPOSE_PROJECT_NAME`) → `APP_NAME` + hash da instalação
  → `publicv7` + hash. Um nome explícito vence e **não** recebe sufixo (pin/migração/CI).
- Regras do slug:
  1. transliterar acentos (`iconv` → ASCII; "São" → "Sao");
  2. minúsculas;
  3. `[^a-z0-9_-]+` → `-`, sem `-` nas pontas, sem repetidos;
  4. remover conectivos PT: `a/o/as/os/de/da/do/das/dos/e/em/no/na/nos/nas`;
  5. garantir início `[a-z0-9]` (senão prefixa `p-`); vazio → `publicv7`.
  6. sufixo `-<sha256(realpath do checkout)[0..8]>` (ADR-055).
- Exemplos: `Prefeitura de Santa Barra` → `prefeitura-santa-barra-1a2b3c4d`;
  `Concordia` → `concordia-091fb33f`; `COMPOSE_PROJECT_NAME=publicv7_ci` → `publicv7_ci`.
- `docker-compose.yml` usa `name: ${COMPOSE_PROJECT_NAME:-concordia}` como fallback de
  inspeção manual; `Makefile`, `scripts/update_prod.sh`, `tools/*.sh` e o CI passam `-p`. No primeiro
  deploy o `scripts/update_prod.sh` **adota** a stack anterior do próprio checkout (`working_dir`).
- **Uma instalação = um projeto Compose** (`-p`), sem colisão de containers/volumes/redes
  entre instâncias; `WEB_PORT` é validado (o deploy aborta se pertencer a outra stack).

## 5. Verificação (gates)

- `tools/name-check` — o token canônico do domínio aparece grep-friendly (ADR-004).
- `make exec cmd='bash tools/gates-selftest.sh'` — selftest de `arch-check`/`name-check`/`migration-check`/`vendor-check` em
  fixtures **e** na árvore real.
- `git check-attr` em `*.php`/`*.md` → rc = 0 e stderr vazio (prova "um padrão por linha").
- `docker compose ls` mostra o nome derivado com o hash da instalação (ex.:
  `concordia-091fb33f running(6)`).
- `.dockerignore` impede `.env` no contexto: `docker compose build php-fpm` sem vazar segredos.

## 6. Versão oficial (`VERSION`)

- O arquivo **`VERSION`** na raiz é a fonte única da versão oficial, no formato
  `MAJOR.MINOR.PATCH - YYYY/MM/DD` (ex.: `7.0.0 - 2026/10/15`).
- Consumido por `Core\Internal\System\SystemVersion::get()` (sem cache — reflete o deploy atual)
  e exposto no health da API. Ausência do arquivo → `0.0.0 - unknown`.
- Bump de versão = editar `VERSION` (nada de constante duplicada no código).

## 7. Fora de escopo

- Convenções de commit/branch e o fluxo de PR (`AGENTS.md` §0 + `CONTRIBUTING.md`).
- O conteúdo do bloco `knudge` (gerenciado por `kd`).
