Skip to content

Ambiente de desenvolvimento por slot (LUI-184) - #18

Closed
argentinaluiz wants to merge 2 commits into
testfrom
feature/lui-184-ambiente-por-slot
Closed

argentinaluiz wants to merge 2 commits into
testfrom
feature/lui-184-ambiente-por-slot

Conversation

@argentinaluiz

Copy link
Copy Markdown
Contributor

Aceite

docs/tickets/LUI-182/LUI-184/aceite.md

O que muda

O ambiente de desenvolvimento passa a seguir um slot (0 a 3), lido de um .env na raiz do checkout, que o Git ignora. O contrato do .env (variáveis, fórmula das portas e máscara do .git) está na LLD, seção 6, "Ambiente local por slot". Sem o .env, a configuração resolvida é a de antes.

  • compose.dev.yaml: portas da web, da API, do storage e dos e-mails por variável; o storage escuta e se anuncia na porta do slot; a API e o playwright recebem os endereços do slot por variável sem valor; a máscara do .git da API por tipo e origem (tmpfs por padrão, /dev/null num worktree).
  • scripts/git-mount.sh (novo) e scripts/verify.sh: a varredura de segredos monta o .git comum e aponta o GIT_DIR num worktree.
  • scripts/test/dev-env-slot.test.sh (CA1 a CA3) e scripts/test/git-mount.test.sh (novos).
  • Um teste da API e dois da web deixam de fixar localhost:3000 e localhost:4443.
  • Documentos: LLD, AGENTS.md da raiz, da API e da web, README e .env.example.

Desvios do plano

  • O .git de um worktree pode trazer caminho relativo (worktree.useRelativePaths), e a spec supunha absoluto. A varredura usa GIT_DIR e GIT_WORK_TREE, como o plano já decidiu depois de testar.
  • O teste do CA3 usa a imagem do storage (84 MB), e não a do postgres (645 MB) que o plano cita: é a menor do ambiente, o up já a baixa, e ela tem sh e grep. Veio da /simplify (achado 3 da revisão de conformidade).
  • O endereço do storage entre contêineres segue o slot (STORAGE_EMULATOR_HOST), consequência de o emulador escutar na porta do slot.

Harness

O PR altera compose.dev.yaml, scripts/ e AGENTS.md, então a Guarda do harness vai reprová-lo e ele só entra por bypass do humano.

Evidência do CA4 e do CA5 (revisão humana)

Worktree irmão techweekia16-live-slot1, com o .git de caminho relativo (gitdir: ../techweekia16-live/.git/worktrees/...) e o .env do slot 1 feito à mão, como no contrato da LLD (seção 6, "Ambiente local por slot").

CA4

  • Dois ambientes no ar ao mesmo tempo: slot 0 em 3000/3001/4443/8025 e slot 1 em 3010/3011/4453/8035, todos os serviços healthy.
  • scripts/verify.sh full aprovou nos dois (96 critérios com teste cobertos, entre eles LUI-184-CA1 a CA3).
  • E-mail de verificação do slot 1: link http://localhost:3010/verificar-email?token=..., lido na tela do slot 1 (porta 8035). O mesmo destinatário tem 0 e-mails no Mailpit do slot 0.
  • Envio e download no slot 1: uploadUrl e download-url com o host http://localhost:4453; o PUT respondeu 200, a conclusão 200 e o GET da URL de download 200, com o conteúdo ola!!.
  • docker compose down no slot 1: as portas 3010, 3011, 4453 e 8035 deixaram de responder, e o slot 0 seguiu de pé (web 307, API respondendo, Mailpit 200, todos healthy).

CA5

  • No worktree, o passo "Segredos nos commits da branch" do full passou (1 commit lido). Num worktree irmão com uma chave plantada num commit, o scripts/verify.sh prepush saiu com 1 e acusou 1 vazamento. Antes da mudança, o mesmo passo lia 0 commits e dava erro (not a git repository: /repo/../techweekia16-live/.git/worktrees/...).
  • No contêiner da API do slot 1: /repo/.git com 0 bytes e sem gitdir; variáveis WEB_ORIGIN, STORAGE_PUBLIC_URL e STORAGE_EMULATOR_HOST do slot.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ANcDtWf5vRMzv1r42Ax9ce

argentinaluiz and others added 2 commits October 19, 2026 12:43
Acceptance file docs/tickets/LUI-182/LUI-184/aceite.md (CA1-CA6). No route, contract or schema changes: the ticket changes the development Compose file, harness scripts and documents. The names of the slot variables and the port formula, which this ticket owns, are written in docs/lld.md, section 6, "Ambiente local por slot".

Decision: the slot reaches the Compose file only through a root .env that Compose reads by itself, with the same file and command. The .env carries the four ports already computed (WEB_PORT, API_PORT, STORAGE_PORT, MAILPIT_PORT), because Compose interpolation cannot do arithmetic; the formula (today's port plus slot x 10) lives in the LLD and in whoever writes the file. SLOT is in the file only to be read by the front script (LUI-186), and Compose ignores it. Every port has today's value as default, so no .env means slot 0.

Decision: the three addresses the API and the browser need (WEB_ORIGIN, STORAGE_PUBLIC_URL, STORAGE_EMULATOR_HOST) are passed through without a default (an environment entry with no value) to the api service, and STORAGE_PUBLIC_URL also to the playwright service. A default in the Compose file would make the variables exist at slot 0 and the resolved configuration would stop being the one of today (CA2); passed through, they are absent without the .env, and the API keeps using apps/api/.env. The process environment wins over apps/api/.env, so a slot value overrides the file. Verified with docker compose config: a pass-through variable reads from the .env and is null when the .env lacks it.

Decision: the storage emulator listens inside its container on the slot port (-port and -public-host both from STORAGE_PORT, and the healthcheck too), as the spec says, so the browser rule of the tests (resolve localhost to the storage service, keeping the port) stays valid. The consequence is that the storage address between containers also follows the slot (STORAGE_EMULATOR_HOST, http://storage:4453 at slot 1). It is the only inter-container address that changes; web, API, mailpit and database addresses stay.

Decision: the .git mask of the api container uses the long volume syntax with type and source from variables: type ${GIT_MASK_TYPE:-tmpfs} and source ${GIT_MASK_SOURCE:-}. Without the .env it resolves to the same read-only tmpfs of today; the worktree .env sets GIT_MASK_TYPE=bind and GIT_MASK_SOURCE=/dev/null, which covers the .git file with an empty one. Verified: Compose accepts a variable type and an empty source for tmpfs, and in a real worktree /dev/null bound over .git is an empty character device while the tmpfs does not mount over a file.

Decision: the secret scan in a worktree sets GIT_DIR (the absolute git dir of the worktree) and GIT_WORK_TREE=/repo and mounts the common .git read-only at the same absolute path, with --mount type=bind,...,readonly. This corrects a premise of the spec: the .git file of a worktree can hold a relative path (worktree.useRelativePaths is on in the maintainer's Git config), and for a sibling worktree the relative path resolved from /repo points nowhere, so mounting the common dir alone is not enough (verified: gitleaks reads 0 commits and errors; with GIT_DIR it reads the commit and finds a planted key). Git picks the dir from GIT_DIR, so it works whatever the .git file says. The short -v src:dst:ro form of the same mount failed in this Docker Desktop, the --mount form works. Outside a worktree (.git is a directory) the command is the one of today.

Decision: the argument building of the scan goes in a new scripts/git-mount.sh, sourced by scripts/verify.sh as full-lock.sh is, so a shell test can check the arguments for a repository and for a worktree without Docker. The scan itself (CA5) stays a human review with evidence in the PR.

Decision: the tests of CA1 to CA3 are shell tests in scripts/test/dev-env-slot.test.sh, in the report with the @LUI-184-CA<n> tag. They resolve the configuration with docker compose --env-file <file> config --format json and always pass the file, so a .env in the checkout of whoever runs them never changes the result; no container starts. CA2 checks today's values (ports, storage entrypoint, healthcheck, tmpfs mask, no slot variable in the api and playwright), and at implementation the configuration resolved without a .env is also compared once with the one of this plan commit's Compose file. CA3 checks the mask in real Docker, with the postgres image the environment already pulled: the same read-only bind of the folder plus the mask the resolved configuration declares, over a .git directory and over a .git file, plus the default mask over a file as a control that fails to start.

Decision: the other suites follow the slot, so the full suite passes at slot 1 (CA4). The API end-to-end test of the password recovery reads the web origin from the injected app configuration instead of the literal http://localhost:3000, and the two web end-to-end tests that expect the storage host localhost:4443 (the emulator proof and the download one) read it from STORAGE_PUBLIC_URL, with today's value as default. The mail links need no change: the web tests keep only the path of a link.

Decision: CA4 and CA5 are human review, as the ticket says, with the commands and outputs in the PR. There is no front script yet, so the worktree and its .env are made by hand, following the LLD table. Each environment weighs about 2 GB of memory at rest.

Files and interfaces the implementation will touch:
- compose.dev.yaml: ports of web, api, storage and mailpit from WEB_PORT, API_PORT, STORAGE_PORT and MAILPIT_PORT; storage entrypoint and healthcheck from STORAGE_PORT; pass-through of WEB_ORIGIN, STORAGE_PUBLIC_URL and STORAGE_EMULATOR_HOST to api and of STORAGE_PUBLIC_URL to playwright; the .git mask from GIT_MASK_TYPE and GIT_MASK_SOURCE.
- .gitignore: the root .env.
- scripts/git-mount.sh (new): a function that prints the docker run arguments the secret scan needs for the current checkout, nothing extra for a .git directory, the common .git mount, GIT_DIR and GIT_WORK_TREE for a .git file.
- scripts/verify.sh: secret_scan builds its docker run from that function.
- scripts/test/dev-env-slot.test.sh (new): the tests of CA1 to CA3.
- scripts/test/git-mount.test.sh (new): the arguments for a repository and for a worktree, without tag.
- apps/api/test/auth-password-recovery.e2e-spec.ts: the web origin from the injected configuration.
- apps/web/e2e/storage-emulator.spec.ts, apps/web/e2e/download.spec.ts and apps/web/e2e/support/storage.ts: the storage public host from STORAGE_PUBLIC_URL, default http://localhost:4443.
- apps/api/.env.example: a comment saying that in a slot the Compose file overrides WEB_ORIGIN, STORAGE_PUBLIC_URL and STORAGE_EMULATOR_HOST.
- docs/lld.md: the section of the storage emulator and the variable table point to the slot (the slot contract of section 6 is already in the plan).
- AGENTS.md (root), apps/api/AGENTS.md, apps/web/AGENTS.md and README.md: the address table of the root is the one of slot 0, and the others point to the slot address where they cite localhost with a fixed port.

Existing tests whose behavior changes: the password recovery end-to-end test of the API (origin from the configuration), and the emulator and download tests of the web (host from the variable); all keep their result at slot 0.

Data changes: none.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANcDtWf5vRMzv1r42Ax9ce
A root .env sets the slot: the published ports of web, API, storage and
mailpit, the storage emulator port and public host, and the web origin and
public storage address that the API reads. Without the .env the resolved
configuration is the one of before. The .git mask of the API container takes
its type and source from the .env, so a worktree, where .git is a file, is
covered by /dev/null instead of a tmpfs. The secret scan mounts the common
.git and sets GIT_DIR in a worktree (scripts/git-mount.sh).

The test of the mask uses the storage image, not the postgres one the plan
names: it is the smallest of the environment and has sh and grep.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANcDtWf5vRMzv1r42Ax9ce
Copilot AI lite review requested due to automatic review settings October 19, 2026 17:25
@argentinaluiz

Copy link
Copy Markdown
Contributor Author

Revisão de conformidade: LUI-184

# Severidade Achado Situação Motivo
1 média O parágrafo novo do AGENTS.md repetia a fórmula das portas e a regra do storage, que têm um dono só, a LLD. Corrigido O parágrafo agora diz só que a tabela é a do slot 0 e aponta para a seção 6 da LLD, sem a fórmula.
2 média O teste do CA1 não conferia o endereço do banco entre contêineres, que o cenário diz que segue como hoje. Corrigido O filtro do CA1 passou a conferir o DATABASE_URL do playwright.
3 baixa O teste do CA3 usa a imagem do storage, e o aceite e o plano dizem a do postgres. Não corrigido A troca veio da /simplify: a imagem do storage tem 84 MB, contra 645 MB do postgres, e o ambiente já a baixa. Conferi que ela tem sh, ls, test e grep, então a asserção não passa no vazio. O motivo vai na descrição do PR.
4 baixa O controle do CA3 passa com qualquer falha do docker run, e não prova que o motivo é a máscara. Não corrigido Logo antes do controle, o mesmo contêiner, na mesma imagem e com a mesma pasta, sobe com a máscara bind e passa. Se o Docker ou a imagem falhassem, essa asserção falharia antes. A única diferença que sobra no controle é a máscara.
Relatório do revisor, na íntegra

Achados

# Severidade Onde Achado Direção da correção
1 média /home/argen/ti/fullcycle-events/techweekia/techweekia16-live/AGENTS.md:49 O parágrafo novo sob a tabela de serviços repete a fórmula ("as portas da máquina somam slot × 10") e a regra de que a porta do storage entre contêineres segue o slot. O CA6 e o plano dizem que a fórmula e os nomes das variáveis têm um dono só, a LLD (seção 6, "Ambiente local por slot"), e o REVIEW.md (conferência 4) trata texto repetido como achado. Se a fórmula mudar na LLD, o AGENTS.md fica desatualizado. Reduzir o parágrafo a "a tabela é a do slot 0; as portas e os endereços de um slot estão no docs/lld.md, seção 6, 'Ambiente local por slot'", sem a fórmula.
2 média /home/argen/ti/fullcycle-events/techweekia/techweekia16-live/scripts/test/dev-env-slot.test.sh:68 O teste do CA1 é mais fraco que o cenário. O cenário diz que os endereços entre contêineres "(http://api:3000, http://web:3000, http://mailpit:8025, o banco) seguem como hoje". A asserção confere os três primeiros e a API_URL do playwright, mas nenhuma confere o banco (DATABASE_URL do playwright). Um slot que alterasse o endereço do banco passaria no teste. Acrescentar ao filtro do jq do CA1 a conferência de .services.playwright.environment.DATABASE_URL == "postgresql://app:app@postgres:5432/app".
3 baixa /home/argen/ti/fullcycle-events/techweekia/techweekia16-live/scripts/test/dev-env-slot.test.sh:83 O CA3 e o plano dizem que o contêiner do teste é o do postgres. O teste usa a imagem do storage (.services.storage.image). A imagem serve se tiver sh e grep, e o comentário a justifica como "a menor", mas é uma divergência do aceite congelado e do plano, sem registro. Se a imagem não tiver grep, o trecho ! grep -q gitdir passa sem provar nada. Usar a imagem do postgres, como o aceite descreve, ou registrar a troca e o motivo na descrição do PR.
4 baixa /home/argen/ti/fullcycle-events/techweekia/techweekia16-live/scripts/test/dev-env-slot.test.sh:113 O controle do CA3 (! in_container "$worktree" slot0) passa com qualquer falha da função: o docker run falhando por outro motivo, a imagem sem grep ou o Docker indisponível. Ele não prova que o motivo é a máscara tmpfs não montar sobre um .git que é arquivo, que é o defeito que o ticket corrige. Fazer o controle distinguir a causa, por exemplo conferindo a mensagem de erro do Docker sobre a montagem no .git, ou confirmando que o mesmo contêiner sobe com a máscara bind.

O que não consegui conferir

  • Não rodei código nem testes. Não confirmei que os testes de shell passam, nem a saída real do docker compose config no slot 1 e no slot 0.
  • No teste do CA3, o jq monta a máscara com if .source then ",source=...". Em jq, a string vazia é verdadeira. Se o Compose resolver o source do tmpfs como "" em vez de omiti-lo, a máscara do slot 0 sairia como type=tmpfs,...,source=. Não consegui verificar o que o Compose imprime.
  • O CA4 e o CA5 são revisão humana, com a evidência no PR. Não há evidência para ler aqui: dois ambientes no ar, o full em worktree, a varredura de segredos e o ls -A/cat do /repo/.git.
  • Os critérios do ticket no Linear (CA1 a CA6) batem com os títulos do aceite.md (LUI-184-CA1 a CA6). Não há critério acrescentado depois do commit de plano, e o aceite.md aparece no diff só como arquivo novo, sem alteração de aceite congelado.
  • Desvio do contrato: a visão diz que contrato e schema estão iguais ao do commit de plano c1d266a, e o diff não toca openapi.json nem schema.prisma. Isso é coerente com o ticket, que não pede mudança de rota nem de tabela.
  • Conferi que o ambiente do processo vale mais do que o apps/api/.env pelo uso de process.loadEnvFile, que não sobrescreve variável já definida, em apps/api/test/support/test-env.ts. Não conferi o caminho de inicialização da API fora dos testes.
  • Não vi, no diff, nenhuma mudança que registre o git-mount.test.sh e o dev-env-slot.test.sh no relatório. Pelo scripts/verify.sh:227, o executor roda scripts/test/*.test.sh, então eles entram por glob. Não confirmei o comportamento do executor quando um teste sai com código 2 (imagem ou ferramenta ausente no meio da execução).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

CA3’s acceptance criterion and test implementation disagree about using postgres versus storage.

1 open issue
What changed in this PR

Adds slot-based development environments, worktree-aware secret scanning, and updates to tests and documentation.

Changes:

  • Parameterizes Compose ports and service URLs by slot.
  • Supports Git mounts for worktrees.
  • Updates E2E tests, documentation, and environment examples.
File Summary
scripts/​verify.sh Mounts shared Git metadata during secret scanning.
scripts/​test/​git-mount.test.sh Tests Git mounting for worktrees.
scripts/​test/​dev-env-slot.test.sh Tests slot configuration; CA3 uses storage while its criterion specifies postgres.
scripts/​git-mount.sh Provides Docker arguments for worktree Git mounts.
README.md Documents default slot endpoints.
docs/​tickets/​LUI-182/​LUI-184/​aceite.md Defines acceptance criteria; CA3 references postgres instead of the implemented storage image.
docs/​lld.md Documents the slot environment contract.
compose.dev.yaml Configures slot-based ports, storage, and Git masking.
apps/​web/​playwright.config.ts Supports variable storage ports.
apps/​web/​e2e/​support/​storage.ts Resolves the slot-specific storage host.
apps/​web/​e2e/​storage-emulator.spec.ts Adapts storage tests to slots.
apps/​web/​e2e/​download.spec.ts Adapts download tests to slots.
apps/​web/​AGENTS.md Documents slot-aware web testing.
apps/​api/​test/​auth-password-recovery.e2e-spec.ts Uses configurable web origins in email links.
apps/​api/​AGENTS.md Documents the API port.
apps/​api/​.env.example Documents environment-variable precedence.
AGENTS.md Documents the slot-based development environment.
.gitignore Ignores the root .env.

Review effort: Lite


**Marca:** teste de unidade

**Cenário:** Dado um repositório temporário com o `.git` como pasta e um worktree temporário com o `.git` como arquivo, quando o contêiner sobe com a máscara que a configuração resolvida declara para o contêiner da API (a do `.env` de um worktree para o arquivo, a padrão para a pasta), então o `.git` visto dentro do contêiner fica vazio nos dois casos, sem o histórico, e o contêiner sobe. Como controle, a máscara padrão num `.git` que é arquivo não sobe, que é o defeito que o ticket corrige. O contêiner do teste é o do `postgres`, que o ambiente já baixou, com o mesmo `.:/repo` somente leitura do contêiner da API.
@argentinaluiz

Copy link
Copy Markdown
Contributor Author

Fechado: os PRs agora são publicados no repositório argentinaluiz/spec-pullrequest-ia. Este PR continua lá em argentinaluiz/spec-pullrequest-ia#22, com o mesmo corpo e o mesmo comentário da revisão de conformidade.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants