Repository navigation
Ambiente de desenvolvimento por slot (LUI-184) - #22
Merged
Merged
Conversation
…183) Acceptance file docs/tickets/LUI-182/LUI-183/aceite.md (CA1-CA4). No route, contract or schema changes: the ticket only changes harness scripts. Decision: the shell tests write their own report, apps/api/reports/harness/shell-tests.json, in the Vitest format the checker and check-report.cjs already read, and the checker receives it as one more vitest:<file> spec. It is not merged into node-tests.json because the Node reporter writes that one inside the api container, while the shell suites run on the machine (git and jq); two files keep each writer alone and need no change in the checker. Decision: the helpers record the cases, and a new runner assembles the report. check, expect_hook and the new record_case append "suite, status, name" to the file in SH_TEST_LOG when it is set, and the runner turns that log into the report with jq at the end of the run. Suites that print their own ok/FALHOU lines (stop-verify and record-touched) call record_case, so every shell case of the harness is in the report, not only the ones that use check. Without SH_TEST_LOG the helpers behave as today. Decision: the tag comes from the case name with the same pattern as the Node reporter (@<ticket>-CA<n>), and the title stays as written, so the checker's rule against an ID only in the title is satisfied the same way. The pattern lives in two places (JS and jq), which is accepted: it is one line, and the test of CA1 would catch a drift. Decision: the runner removes the report before anything runs and always writes a new one at the end, even when a suite fails, so a report from a previous run never covers a criterion whose case did not run now (CA3). It keeps going after a failing suite, which the old && chain did not, so the report lists every suite and the Aceite step does not add a missing-report error to a failure already shown. A suite that exits non-zero without a failed case, or that does not exist, becomes one failed case with the exit code, so a crash cannot pass as a report without failures. Decision: harness_tests runs the shell suites even when the Node tests fail, and then runs check_report on the shell report, so zero cases, a failed case or a skipped one fail the "Testes do harness" step as they do for the Node report. Decision: the tests of this ticket are shell tests too and carry the @LUI-183-CA<n> token, so they are in the new report and the ticket proves itself with the mechanism it builds. They run the checker for real, through scripts/node-in-api.sh, on a fake acceptance file and a report written under apps/api/reports/ (git-ignored), the only writable folder the api container shares with the machine, so the checker reads them under /repo. Files and interfaces the implementation will touch: - scripts/test/sh-helpers.sh: record_case <status> <name> (new) and its use in check and expect_hook; the header documents how to name an acceptance test and where the report is (CA4). - scripts/test/shell-tests.sh (new): shell-tests.sh <report> <suite>... removes the report, runs each suite with SH_TEST_LOG set, writes the report with jq, exits 1 if any suite failed. - scripts/test/stop-verify.test.sh and scripts/test/record-touched.test.sh: their own assertions call record_case. - scripts/test/shell-tests.test.sh (new): the tests of CA1 to CA3, with the new suite added to the list the runner receives. - scripts/verify.sh: HARNESS_SHELL_REPORT; harness_tests runs the shell suites through the runner and checks the shell report; acceptance adds it to the checker specs; the header comment of harness_tests says so. - AGENTS.md: the "Cobertura" bullet points to the helpers header for naming a shell acceptance test. Existing tests whose behavior changes: none (the shell suites print the same lines; the two with their own assertions also record them). Data changes: none. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ChFCP7iJd9oEJ81Mc5L2p
Shell suites now write a report in the Vitest format (apps/api/reports/harness/shell-tests.json) through a new runner, with the @<ticket>-CA<n> token of a case name as its tag, and the acceptance checker reads it as one more vitest report. The report is rebuilt on every run, so a case that did not run now never covers a criterion. The helpers record each case through pass_case and fail_case, with record_case behind them; the plan named only record_case. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ANcDtWf5vRMzv1r42Ax9ce
…-no-aceite Testes em shell do harness entram na cobertura do aceite (LUI-183)
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
Owner
Author
Revisão de conformidade: LUI-184
Relatório do revisor, na íntegraAchados
O que não consegui conferir
|
There was a problem hiding this comment.
🟡 Changes recommended
Diferenças de permissões entre máquina e contêiner impedem a criação dos relatórios e a leitura dos arquivos temporários na CI.
2 open issues
What changed in this PR
Permite ambientes de desenvolvimento simultâneos por slot e inclui a infraestrutura do LUI-183 para comprovar critérios de aceite com testes em shell.
Changes:
- Parametriza portas, endereços e máscara do
.gitpor checkout. - Adapta a varredura de segredos para worktrees e adiciona relatórios dos testes em shell.
- Atualiza testes e documentação para os endereços por slot.
| File | Description |
|---|---|
| scripts/verify.sh | Integra relatórios shell e montagem do Git. |
| scripts/test/stop-verify.test.sh | Registra resultados nos auxiliares compartilhados. |
| scripts/test/shell-tests.test.sh | Testa executor e cobertura de aceite. |
| scripts/test/shell-tests.sh | Executa suítes e gera relatório. |
| scripts/test/sh-helpers.sh | Registra casos para o relatório. |
| scripts/test/record-touched.test.sh | Adota registro compartilhado de resultados. |
| scripts/test/git-mount.test.sh | Confere argumentos para worktrees. |
| scripts/test/dev-env-slot.test.sh | Confere slots e máscaras do Git. |
| scripts/git-mount.sh | Resolve montagem do histórico em worktrees. |
| README.md | Aponta para configuração por slot. |
| docs/tickets/LUI-182/LUI-184/aceite.md | Define critérios do ambiente por slot. |
| docs/tickets/LUI-182/LUI-183/aceite.md | Define critérios dos relatórios shell. |
| docs/lld.md | Documenta contrato dos slots. |
| compose.dev.yaml | Parametriza serviços e máscara do Git. |
| apps/web/playwright.config.ts | Atualiza comentário sobre resolução do storage. |
| apps/web/e2e/support/storage.ts | Resolve host público por ambiente. |
| apps/web/e2e/storage-emulator.spec.ts | Confere URLs do slot configurado. |
| apps/web/e2e/download.spec.ts | Usa host configurado nas verificações. |
| apps/web/AGENTS.md | Documenta testes e endereços por slot. |
| apps/api/test/auth-password-recovery.e2e-spec.ts | Usa origem configurada nas expectativas. |
| apps/api/AGENTS.md | Identifica endereço do slot 0. |
| apps/api/.env.example | Explica precedência das variáveis do slot. |
| AGENTS.md | Documenta slots e aceite em shell. |
| .gitignore | Ignora .env da raiz. |
Review effort: Balanced
Comment on lines
+21
to
+22
| work="$(mktemp -d -p "$ROOT/apps/api/reports" shell-tests.XXXXXX)" | ||
| trap 'rm -rf "$work"' EXIT |
Comment on lines
+227
to
+228
| scripts/test/shell-tests.sh "apps/api/$HARNESS_SHELL_REPORT" scripts/test/*.test.sh && | ||
| check_report api vitest "$HARNESS_SHELL_REPORT" || code=1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Aceite
docs/tickets/LUI-182/LUI-184/aceite.md
Nota sobre este repositório
Este PR substitui o devfullcycle/spec-pullrequest-ia#18, fechado: a partir de agora os PRs são publicados aqui. O branch parte do commit que tem o LUI-183 (devfullcycle#17), que a
maindaqui não tem, então o diff também mostra os arquivos do LUI-183 (scripts/test/shell-tests*,sh-helpers.she overify.sh). O que é do LUI-184 são os commitsc1d266a(plano) e6a60e41.O que muda
O ambiente de desenvolvimento passa a seguir um slot (0 a 3), lido de um
.envna 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 oplaywrightrecebem os endereços do slot por variável sem valor; a máscara do.gitda API por tipo e origem (tmpfspor padrão,/dev/nullnum worktree).scripts/git-mount.sh(novo) escripts/verify.sh: a varredura de segredos monta o.gitcomum e aponta oGIT_DIRnum worktree.scripts/test/dev-env-slot.test.sh(CA1 a CA3) escripts/test/git-mount.test.sh(novos).localhost:3000elocalhost:4443.AGENTS.mdda raiz, da API e da web, README e.env.example.Desvios do plano
.gitde um worktree pode trazer caminho relativo (worktree.useRelativePaths), e a spec supunha absoluto. A varredura usaGIT_DIReGIT_WORK_TREE, como o plano já decidiu depois de testar.storage(84 MB), e não a dopostgres(645 MB) que o plano cita: é a menor do ambiente, oupjá a baixa, e ela temshegrep. Veio da/simplify(achado 3 da revisão de conformidade).STORAGE_EMULATOR_HOST), consequência de o emulador escutar na porta do slot.Harness
O PR altera
compose.dev.yaml,scripts/eAGENTS.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.gitde caminho relativo (gitdir: ../techweekia16-live/.git/worktrees/...) e o.envdo slot 1 feito à mão, como no contrato da LLD (seção 6, "Ambiente local por slot").CA4
healthy.scripts/verify.sh fullaprovou nos dois (96 critérios com teste cobertos, entre eles LUI-184-CA1 a CA3).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.uploadUrledownload-urlcom o hosthttp://localhost:4453; o PUT respondeu 200, a conclusão 200 e o GET da URL de download 200, com o conteúdoola!!.docker compose downno 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, todoshealthy).CA5
fullpassou (1 commit lido). Num worktree irmão com uma chave plantada num commit, oscripts/verify.sh prepushsaiu 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/...)./repo/.gitcom 0 bytes e semgitdir; variáveisWEB_ORIGIN,STORAGE_PUBLIC_URLeSTORAGE_EMULATOR_HOSTdo slot.🤖 Generated with Claude Code
https://claude.ai/code/session_01ANcDtWf5vRMzv1r42Ax9ce